From 62c90b29b975be9adfe56f4a6734fa27b7f6e5f7 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 22 Jun 2026 12:32:30 +0530 Subject: [PATCH 01/78] feat(bstock): add atomic backstop liquidator - bStock is RFQ-only (no AMM), so third-party liquidators may not show. Add a self-liquidation backstop that, in one tx, repays the borrow, seizes and redeems the bStock vToken, and sells it to USDT via a pre-signed Native RFQ quote with a hard minOut, so the protocol never holds the RFQ-only asset across a drift window. - Two funding modes: own USDT inventory, or a Venus flash loan repaid (+ premium) in the same tx. - Routes the repay through the pool-wide Venus Liquidator when that gate is set, else liquidates directly; the seized amount is read as a balance delta so the liquidator treasury cut is handled. - Operator-gated and router-allowlisted: it custodies funds and forwards caller-supplied swap calldata to an external router. - Extend the shared IComptroller with getAccountLiquidity and executeFlashLoan (additive); split storage-light interface into IBStockLiquidator. - Add mocks, a unit suite (both modes, gate + treasury-cut, all guards and error paths), and a bscmainnet fork test on real Core. --- contracts/BStock/BStockLiquidator.sol | 255 ++++++++++++++++ contracts/BStock/IBStockLiquidator.sol | 110 +++++++ contracts/InterfacesV8.sol | 10 + contracts/test/BStockLiquidationMocks.sol | 267 ++++++++++++++++ tests/hardhat/BStockLiquidator.ts | 334 +++++++++++++++++++++ tests/hardhat/Fork/BStockLiquidatorFork.ts | 171 +++++++++++ 6 files changed, 1147 insertions(+) create mode 100644 contracts/BStock/BStockLiquidator.sol create mode 100644 contracts/BStock/IBStockLiquidator.sol create mode 100644 contracts/test/BStockLiquidationMocks.sol create mode 100644 tests/hardhat/BStockLiquidator.ts create mode 100644 tests/hardhat/Fork/BStockLiquidatorFork.ts diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol new file mode 100644 index 000000000..f9582b375 --- /dev/null +++ b/contracts/BStock/BStockLiquidator.sol @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.25; + +import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import { ensureNonzeroAddress } from "@venusprotocol/solidity-utilities/contracts/validators.sol"; + +import { VToken } from "../Tokens/VTokens/VToken.sol"; +import { IFlashLoanReceiver } from "../FlashLoan/interfaces/IFlashLoanReceiver.sol"; + +// Shared Venus interfaces: IComptroller (Core diamond — gate, account liquidity, flash loan), +// IVBep20 (the bStock/debt market surface), IVToken (flash-loan asset array element), and ILiquidator +// (the pool-wide Venus Liquidator gate that pulls the repay and returns our share of the seized collateral). +import { IComptroller, IVBep20, IVToken, ILiquidator } from "../InterfacesV8.sol"; +import { IBStockLiquidator } from "./IBStockLiquidator.sol"; + +/** + * @title BStockLiquidator + * @author Venus + * @notice Atomic backstop liquidator for bStock (ERC-8056 tokenized stock) collateral. + * + * In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken, + * redeems it to the raw bStock, and sells that bStock to USDT through the Native RFQ router + * using a pre-fetched, MM-signed firm-quote `txRequest`. Because seize and sell happen in the + * same tx there is no price-drift window, and the realized USDT must clear `minOut` or the whole + * call reverts — the protocol never ends up holding the RFQ-only asset. + * + * Two funding modes share the same core (`_liquidate`): + * - INVENTORY: the contract is pre-funded with the debt asset (USDT) and repays from its own balance. + * - FLASH: the debt asset is flash-borrowed from Venus (`Comptroller.executeFlashLoan`) and repaid + * (+ premium) within the same tx; no capital is locked in the contract. + * + * Ownership / scope: this is Venus's OWN backstop tool, NOT a public utility — `liquidate` and + * `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock + * liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus + * path with their own funds and their own offload. This contract is intentionally gated because it + * custodies funds (USDT inventory / flash principal) and forwards a CALLER-SUPPLIED calldata blob to + * an external router — the swap's recipient (`to`) lives inside that calldata, so an open entrypoint + * would let anyone route the proceeds to themselves and drain the contract. The router allowlist and + * `minOut` bound the blast radius but cannot replace operator-gating. + * + * Security model: + * - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator). + * - the swap target must be allowlisted (`isRouter`) — defends the low-level `router.call(swapCalldata)`. + * - the bStock approval to the router is the exact seized amount, reset to 0 after the swap. + * - `executeOperation` accepts calls only from the Comptroller, mid-flight (`_flashActive`), with `initiator == this`. + * - realized USDT must clear `minOut` or the tx reverts. + * + * Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`. + * When it is set, a direct `vToken.liquidateBorrow` from an arbitrary caller reverts UNAUTHORIZED, so this + * contract reads the gate at call time and routes the repay through that Venus Liquidator (which is the + * permissionless entry anyone may call); when the gate is unset it liquidates directly. Routing through the + * gate needs no governance change, and no other Core market is affected. Note: setting THIS contract as `liquidatorContract` is + * NOT an option — the gate is pool-wide, so every other market's liquidations would be forced through here. + */ +contract BStockLiquidator is + Ownable2StepUpgradeable, + ReentrancyGuardUpgradeable, + IBStockLiquidator, + IFlashLoanReceiver +{ + using SafeERC20Upgradeable for IERC20Upgradeable; + + /// @notice Core Comptroller (diamond): reads the liquidation gate / account liquidity and provides + /// the flash loan via `executeFlashLoan`. + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + IComptroller public immutable comptroller; + + /// @notice Addresses allowed to trigger a liquidation. + mapping(address => bool) public isOperator; + + /// @notice Routers allowed as the swap target (defends the low-level call). + mapping(address => bool) public isRouter; + + /// @dev True only while one of our own flash loans is mid-flight. + bool private _flashActive; + + /// @dev Reserved storage to allow new state variables in future upgrades without layout clashes. + uint256[49] private __gap; + + modifier onlyOperator() { + if (msg.sender != owner() && !isOperator[msg.sender]) revert NotOperator(); + _; + } + + /// @notice Constructor for the implementation contract. Sets the immutable Comptroller and locks initializers. + /// @param comptroller_ Venus Core Comptroller (diamond) — gates liquidation and provides flash loans. + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(IComptroller comptroller_) { + ensureNonzeroAddress(address(comptroller_)); + comptroller = comptroller_; + _disableInitializers(); + } + + /// @notice Initializes the proxy: sets the owner and the reentrancy guard. + /// @param initialOwner Address that owns the contract (admin + default operator). + function initialize(address initialOwner) external initializer { + ensureNonzeroAddress(initialOwner); + __Ownable2Step_init(); + __ReentrancyGuard_init(); + _transferOwnership(initialOwner); + } + + // --------------------------------------------------------------------- // + // Admin // + // --------------------------------------------------------------------- // + + /// @inheritdoc IBStockLiquidator + function setOperator(address operator, bool allowed) external override onlyOwner { + ensureNonzeroAddress(operator); + isOperator[operator] = allowed; + emit OperatorSet(operator, allowed); + } + + /// @inheritdoc IBStockLiquidator + function setRouter(address router, bool allowed) external override onlyOwner { + ensureNonzeroAddress(router); + isRouter[router] = allowed; + emit RouterSet(router, allowed); + } + + /// @inheritdoc IBStockLiquidator + function sweep(address token, address to, uint256 amount) external override onlyOwner { + IERC20Upgradeable(token).safeTransfer(to, amount); + emit Swept(token, to, amount); + } + + // --------------------------------------------------------------------- // + // INVENTORY mode // + // --------------------------------------------------------------------- // + + /// @inheritdoc IBStockLiquidator + function liquidate( + LiquidationParams calldata p + ) external override onlyOperator nonReentrant returns (uint256 usdtOut) { + _check(p); + uint256 seizedBStock; + (usdtOut, seizedBStock) = _liquidate(p); + emit Liquidated(p.borrower, address(p.vBStock), p.repayAmount, seizedBStock, usdtOut, false); + } + + // --------------------------------------------------------------------- // + // FLASH mode // + // --------------------------------------------------------------------- // + + /// @inheritdoc IBStockLiquidator + function flashLiquidate(LiquidationParams calldata p) external override onlyOperator nonReentrant { + _check(p); + + IVToken[] memory vTokens = new IVToken[](1); + vTokens[0] = p.vDebt; + uint256[] memory amounts = new uint256[](1); + amounts[0] = p.repayAmount; + + _flashActive = true; + comptroller.executeFlashLoan(payable(address(this)), payable(address(this)), vTokens, amounts, abi.encode(p)); + _flashActive = false; + } + + /// @inheritdoc IFlashLoanReceiver + function executeOperation( + VToken[] calldata vTokens, + uint256[] calldata amounts, + uint256[] calldata premiums, + address initiator, + address /* onBehalf */, + bytes calldata param + ) external override returns (bool success, uint256[] memory repayAmounts) { + if (msg.sender != address(comptroller)) revert OnlyComptroller(); + if (!_flashActive) revert NoFlashInFlight(); + if (initiator != address(this)) revert BadInitiator(initiator); + + LiquidationParams memory p = abi.decode(param, (LiquidationParams)); + if (address(vTokens[0]) != address(p.vDebt)) revert WrongFlashAsset(); + + // Repay was just funded by the flash loan; run the liquidation + swap. + (uint256 usdtOut, uint256 seizedBStock) = _liquidate(p); + + // The swap proceeds alone MUST cover principal + premium. Without this, any USDT inventory + // held by the contract would silently backfill an underwater swap (a real loss), since the + // flash repayment is pulled from the total balance, not just the swap output. + repayAmounts = new uint256[](1); + repayAmounts[0] = amounts[0] + premiums[0]; + if (usdtOut < repayAmounts[0]) revert InsufficientOut(usdtOut, repayAmounts[0]); + + // Approve the flashed vToken to pull back principal + premium. + IERC20Upgradeable(p.vDebt.underlying()).forceApprove(address(vTokens[0]), repayAmounts[0]); + + emit Liquidated(p.borrower, address(p.vBStock), p.repayAmount, seizedBStock, usdtOut, true); + return (true, repayAmounts); + } + + // --------------------------------------------------------------------- // + // Core // + // --------------------------------------------------------------------- // + + /// @dev Pre-flight: router must be allowlisted and the borrower must have a shortfall. + function _check(LiquidationParams calldata p) private view { + if (!isRouter[p.router]) revert RouterNotAllowed(p.router); + (, , uint256 shortfall) = comptroller.getAccountLiquidity(p.borrower); + if (shortfall == 0) revert NotLiquidatable(p.borrower); + } + + /** + * @dev The atomic sequence shared by both funding modes: + * approve debt -> liquidateBorrow (seize vBStock) -> redeem (raw bStock) + * -> approve router -> swap to USDT via signed calldata -> assert minOut. + * A `calldata` struct from `liquidate` is copied to memory on entry; `executeOperation` + * already holds it in memory (decoded from the flash callback). + * @param p Liquidation parameters. + * @return usdtOut Debt-asset proceeds realized by the swap (reverts if below `minOut`). + * @return seizedBStock Raw bStock redeemed and sold (balance delta). + */ + function _liquidate(LiquidationParams memory p) private returns (uint256 usdtOut, uint256 seizedBStock) { + IERC20Upgradeable debt = IERC20Upgradeable(p.vDebt.underlying()); + IERC20Upgradeable bStock = IERC20Upgradeable(p.vBStock.underlying()); + + // 1. Repay the borrow, seizing the bStock vToken to this contract. + // Core has a POOL-WIDE liquidator gate: if `liquidatorContract` is set, a direct + // `vToken.liquidateBorrow` reverts UNAUTHORIZED, so we route the repay through that + // Venus Liquidator (it pulls our repay and sends us our share of the seized collateral, + // treasury keeps a cut). If the gate is unset, we liquidate directly. Either way the + // seized amount is read as a BALANCE DELTA, so the Liquidator's cut is handled correctly. + uint256 vBefore = p.vBStock.balanceOf(address(this)); + address gate = comptroller.liquidatorContract(); + if (gate == address(0)) { + debt.forceApprove(address(p.vDebt), p.repayAmount); + uint256 errCode = p.vDebt.liquidateBorrow(p.borrower, p.repayAmount, p.vBStock); + if (errCode != 0) revert LiquidateBorrowFailed(errCode); + } else { + debt.forceApprove(gate, p.repayAmount); + ILiquidator(gate).liquidateBorrow(address(p.vDebt), p.borrower, p.repayAmount, p.vBStock); + } + uint256 seizedV = p.vBStock.balanceOf(address(this)) - vBefore; + + // 2. Redeem the seized vBStock for raw bStock. Measure by DELTA so any pre-existing bStock + // (dust or a stray transfer) is excluded — we only sell what this redeem actually returned. + uint256 rawBefore = bStock.balanceOf(address(this)); + uint256 redeemErr = p.vBStock.redeem(seizedV); + if (redeemErr != 0) revert RedeemFailed(redeemErr); + seizedBStock = bStock.balanceOf(address(this)) - rawBefore; + + // 3. Sell the bStock to USDT via the allowlisted Native router (MM-signed calldata). + uint256 usdtBefore = debt.balanceOf(address(this)); + bStock.forceApprove(p.router, seizedBStock); + (bool ok, ) = p.router.call(p.swapCalldata); + if (!ok) revert SwapFailed(); + bStock.forceApprove(p.router, 0); // never leave a standing approval + + usdtOut = debt.balanceOf(address(this)) - usdtBefore; + if (usdtOut < p.minOut) revert InsufficientOut(usdtOut, p.minOut); + } +} diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol new file mode 100644 index 000000000..7ad20693c --- /dev/null +++ b/contracts/BStock/IBStockLiquidator.sol @@ -0,0 +1,110 @@ +// 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. + 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) + uint256 repayAmount; // debt underlying to repay (its own decimals) + address router; // Native router = firm-quote txRequest.target (must be allowlisted) + bytes swapCalldata; // firm-quote txRequest.calldata (MM-signed order) + uint256 minOut; // minimum debt-asset (USDT) the swap must yield, else revert + } + + /// @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 on a successful liquidation. + /// @param borrower The liquidated account. + /// @param vBStock The seized bStock collateral market. + /// @param repayAmount Debt underlying repaid. + /// @param seizedBStock Raw bStock redeemed and sold. + /// @param usdtOut Debt-asset (USDT) 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, + uint256 repayAmount, + uint256 seizedBStock, + uint256 usdtOut, + bool flash + ); + + /// @notice Emitted when the owner withdraws a token. + event Swept(address indexed token, address indexed to, 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 the borrower has no shortfall (nothing to liquidate). + error NotLiquidatable(address borrower); + + /// @notice Thrown when `vDebt.liquidateBorrow` returns a non-zero error code. + error LiquidateBorrowFailed(uint256 errCode); + + /// @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 `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 `executeOperation` is called outside one of our own flash loans. + error NoFlashInFlight(); + + /// @notice Thrown when the flashed asset does not match `params.vDebt`. + error WrongFlashAsset(); + + /// @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). + /// @param router Address to allowlist or remove. + /// @param allowed True to allow, false to remove. + function setRouter(address router, bool allowed) 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 Liquidate using the contract's own debt-asset (USDT) inventory. + /// @dev The contract must already hold >= `repayAmount` of `vDebt.underlying()`. + /// Profit (proceeds - repay) stays in the contract; withdraw it with `sweep`. + /// @param p Liquidation parameters (borrower, markets, repay, router, signed swap calldata, minOut). + /// @return usdtOut Debt-asset proceeds realized by the swap. + function liquidate(LiquidationParams calldata p) external returns (uint256 usdtOut); + + /// @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 p Liquidation parameters (borrower, markets, repay, router, signed swap calldata, minOut). + function flashLiquidate(LiquidationParams calldata p) external; +} diff --git a/contracts/InterfacesV8.sol b/contracts/InterfacesV8.sol index f74ca8174..ae3ec56d7 100644 --- a/contracts/InterfacesV8.sol +++ b/contracts/InterfacesV8.sol @@ -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); diff --git a/contracts/test/BStockLiquidationMocks.sol b/contracts/test/BStockLiquidationMocks.sol new file mode 100644 index 000000000..61e289d15 --- /dev/null +++ b/contracts/test/BStockLiquidationMocks.sol @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.25; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/// @notice Test-only mocks for exercising the BStockLiquidator contract (and the off-chain +/// scripts) on a local network, mimicking the Venus Core + Native interfaces they call. +/// Not for production. + +contract MockMintableERC20 is ERC20 { + uint8 private _decimals; + + constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) { + _decimals = decimals_; + } + + function decimals() public view override returns (uint8) { + return _decimals; + } + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} + +/// @dev Receiver surface the flash comptroller calls back into. `address[]` matches the +/// `VToken[]` selector (a contract type canonicalizes to `address`). +interface IFlashReceiverLike { + function executeOperation( + address[] calldata vTokens, + uint256[] calldata amounts, + uint256[] calldata premiums, + address initiator, + address onBehalf, + bytes calldata param + ) external returns (bool, uint256[] memory); +} + +/// @dev Minimal Comptroller surface the script + BStockLiquidator read, plus a flash lender. +contract MockComptrollerLite { + uint256 public shortfall; + uint256 public closeFactorMantissa = 0.5e18; + uint256 public liquidationIncentiveMantissa = 1.1e18; + uint256 public flashPremiumMantissa; // fee on flash principal, 1e18-scaled (default 0) + address public liquidatorContract; // pool-wide gate; 0 = permissionless (direct liquidateBorrow) + uint256 public treasuryPercent; // redeem fee, 1e18-scaled (default 0) + + function setShortfall(uint256 s) external { + shortfall = s; + } + + function setLiquidatorContract(address l) external { + liquidatorContract = l; + } + + function setFlashPremium(uint256 m) external { + flashPremiumMantissa = m; + } + + function getAccountLiquidity(address) external view returns (uint256, uint256, uint256) { + return (0, 0, shortfall); + } + + /// @dev Mirrors the seize math: seizeTokens = repay * incentive (1:1 collateral rate here). + function liquidateCalculateSeizeTokens( + address, + address, + uint256 repayAmount + ) external view returns (uint256, uint256) { + return (0, (repayAmount * liquidationIncentiveMantissa) / 1e18); + } + + /// @dev Flash lender: send principal from the (pre-funded) debt vToken, call back, pull repay. + function executeFlashLoan( + address payable /* onBehalf */, + address payable receiver, + address[] calldata vTokens, + uint256[] calldata amounts, + bytes calldata param + ) external { + uint256[] memory premiums = new uint256[](1); + premiums[0] = (amounts[0] * flashPremiumMantissa) / 1e18; + + MockVTokenDebt(vTokens[0]).flashOut(receiver, amounts[0]); + (bool ok, uint256[] memory repay) = IFlashReceiverLike(receiver).executeOperation( + vTokens, + amounts, + premiums, + msg.sender, + msg.sender, + param + ); + require(ok, "executeOperation failed"); + MockVTokenDebt(vTokens[0]).flashPull(receiver, repay[0]); + } +} + +/// @dev Collateral vToken mock. Tracks vToken balances; redeem() burns vTokens +/// and pays out the underlying bStock 1:1 (exchangeRate = 1 for the test). +contract MockVTokenCollateral { + address public immutable underlying; + address public immutable comptroller; + mapping(address => uint256) public balanceOf; + + constructor(address underlying_, address comptroller_) { + underlying = underlying_; + comptroller = comptroller_; + } + + /// Called by the debt vToken to credit seized collateral to the liquidator. + function creditSeize(address to, uint256 vAmount) external { + balanceOf[to] += vAmount; + } + + uint256 public redeemError; // non-zero -> redeem returns this code (exercises RedeemFailed) + + function setRedeemError(uint256 e) external { + redeemError = e; + } + + function redeem(uint256 redeemTokens) external returns (uint256) { + if (redeemError != 0) return redeemError; + require(balanceOf[msg.sender] >= redeemTokens, "insufficient vTokens"); + balanceOf[msg.sender] -= redeemTokens; + // 1:1 exchange rate for the test + require(ERC20(underlying).transfer(msg.sender, redeemTokens), "redeem transfer failed"); + return 0; + } +} + +/// @dev Debt vToken mock. liquidateBorrow pulls `repayAmount` of the debt +/// underlying from the caller and credits seized collateral (repay + 10%). +contract MockVTokenDebt { + address public immutable underlying; + address public immutable comptroller; + uint256 public incentiveMantissa = 1.1e18; + uint256 public liquidateError; // non-zero -> liquidateBorrow returns this code (exercises LiquidateBorrowFailed) + bool public constant isFlashLoanEnabled = true; + + constructor(address underlying_, address comptroller_) { + underlying = underlying_; + comptroller = comptroller_; + } + + function setLiquidateError(uint256 e) external { + liquidateError = e; + } + + function liquidateBorrow( + address /* borrower */, + uint256 repayAmount, + address vTokenCollateral + ) external returns (uint256) { + if (liquidateError != 0) return liquidateError; + require(ERC20(underlying).transferFrom(msg.sender, address(this), repayAmount), "repay pull failed"); + uint256 seizeV = (repayAmount * incentiveMantissa) / 1e18; + MockVTokenCollateral(vTokenCollateral).creditSeize(msg.sender, seizeV); + return 0; + } + + /// @dev Flash helpers, driven by MockComptrollerLite. Pre-fund this contract with `underlying`. + function flashOut(address to, uint256 amount) external { + require(msg.sender == comptroller, "only comptroller"); + require(ERC20(underlying).transfer(to, amount), "flash out failed"); + } + + function flashPull(address from, uint256 amount) external { + require(msg.sender == comptroller, "only comptroller"); + // `from` (the receiver) approved THIS vToken as spender, per the real flash interface. + require(ERC20(underlying).transferFrom(from, address(this), amount), "flash pull failed"); + } +} + +/// @dev Stand-in for the Native router. Pulls tokenIn from the caller and pays +/// tokenOut at a configurable rate (1e18 = 1:1). Pre-fund it with tokenOut. +contract MockNativeRouter { + uint256 public rate = 1e18; // tokenOut per tokenIn, 18-dec fixed point + + function setRate(uint256 r) external { + rate = r; + } + + function swap(address tokenIn, uint256 amountIn, address tokenOut, address to) external { + require(ERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), "in pull failed"); + uint256 amountOut = (amountIn * rate) / 1e18; + require(ERC20(tokenOut).transfer(to, amountOut), "out transfer failed"); + } + + /// @dev Pulls the caller's ENTIRE tokenIn balance (what the liquidator actually holds after + /// redeem) and pays tokenOut at `rate`. Avoids fixed-amount rounding mismatches on forks. + function swapAll(address tokenIn, address tokenOut, address to) external { + uint256 amountIn = ERC20(tokenIn).balanceOf(msg.sender); + require(ERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), "in pull failed"); + uint256 amountOut = (amountIn * rate) / 1e18; + require(ERC20(tokenOut).transfer(to, amountOut), "out transfer failed"); + } +} + +/// @dev Stand-in for the pool-wide Venus Liquidator (`liquidatorContract`). Pulls the repay from the +/// caller and credits the seized collateral (repay * incentive) to the caller, minus a treasury cut. +contract MockVenusLiquidator { + uint256 public incentiveMantissa = 1.1e18; + uint256 public treasuryCutMantissa; // cut of the seized collateral kept by the liquidator (default 0) + + function setTreasuryCut(uint256 m) external { + treasuryCutMantissa = m; + } + + function liquidateBorrow( + address vToken, + address /* borrower */, + uint256 repayAmount, + address vTokenCollateral + ) external payable { + address underlying = MockVTokenDebt(vToken).underlying(); + require(ERC20(underlying).transferFrom(msg.sender, address(this), repayAmount), "repay pull failed"); + uint256 seizeV = (repayAmount * incentiveMantissa) / 1e18; + uint256 toCaller = seizeV - (seizeV * treasuryCutMantissa) / 1e18; + MockVTokenCollateral(vTokenCollateral).creditSeize(msg.sender, toCaller); + } +} + +/// @dev Comptroller stand-in that drives the flash callback with deliberately wrong arguments, so the +/// receiver's mid-flight guards can be exercised. `mode` selects which invariant to violate; the +/// contract under test is deployed with this as its immutable comptroller. `getAccountLiquidity` +/// always reports a shortfall so the pre-flight `_check` passes and the callback is reached. +contract MockMaliciousFlashComptroller { + enum Mode { + BadInitiator, // report an initiator != the receiver + WrongAsset // flash a vToken != params.vDebt + } + + Mode public mode; + address public liquidatorContract; // unset: the receiver would liquidate directly + + function setMode(Mode mode_) external { + mode = mode_; + } + + function getAccountLiquidity(address) external pure returns (uint256, uint256, uint256) { + return (0, 0, 1); + } + + function executeFlashLoan( + address payable /* onBehalf */, + address payable receiver, + address[] calldata vTokens, + uint256[] calldata amounts, + bytes calldata param + ) external { + uint256[] memory premiums = new uint256[](1); + if (mode == Mode.BadInitiator) { + IFlashReceiverLike(receiver).executeOperation( + vTokens, + amounts, + premiums, + address(uint160(0xbad)), // initiator != receiver + msg.sender, + param + ); + } else { + address[] memory wrongAsset = new address[](1); + wrongAsset[0] = address(uint160(0xdead)); // != params.vDebt + IFlashReceiverLike(receiver).executeOperation(wrongAsset, amounts, premiums, receiver, msg.sender, param); + } + } +} diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts new file mode 100644 index 000000000..2479fac5e --- /dev/null +++ b/tests/hardhat/BStockLiquidator.ts @@ -0,0 +1,334 @@ +import { impersonateAccount, setBalance } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { BigNumber, Contract } from "ethers"; +import { ethers, upgrades } from "hardhat"; + +// Atomic BStockLiquidator — exercised against the BStock mocks. Covers both funding modes +// (inventory + Venus-style flash loan), the seize->redeem->sell pipeline, the pool-gate routing +// (with and without a treasury cut), the admin surface (operator/router/sweep/init), and every +// guard (operator gating, router allowlist, shortfall, minOut, and the flash-callback checks). +// Position state is "manipulated" through the mock comptroller's shortfall and seize incentive. + +const U = (n: string) => ethers.utils.parseUnits(n, 18); +const ZERO = ethers.constants.AddressZero; +const INCENTIVE = U("1.1"); // 10% — mock seizes repay * 1.1 + +describe("BStockLiquidator (atomic)", () => { + let owner: any, operator: any, borrower: any, stranger: any; + let usdt: Contract, bStock: Contract; + let comptroller: Contract, vBStock: Contract, vDebt: Contract, router: Contract; + let liq: Contract; + + const REPAY = U("5000"); + const SEIZED = REPAY.mul(INCENTIVE).div(U("1")); // 5500 bStock at 1:1 redeem + const OUT = SEIZED; // router rate 1:1 -> 5500 USDT + const MIN_OUT = U("5400"); + + async function deploy() { + [owner, operator, borrower, stranger] = await ethers.getSigners(); + + const ERC20 = await ethers.getContractFactory("MockMintableERC20"); + usdt = await ERC20.deploy("Tether", "USDT", 18); + bStock = await ERC20.deploy("Tesla bStock", "TSLAB", 18); + + comptroller = await (await ethers.getContractFactory("MockComptrollerLite")).deploy(); + vBStock = await ( + await ethers.getContractFactory("MockVTokenCollateral") + ).deploy(bStock.address, comptroller.address); + vDebt = await (await ethers.getContractFactory("MockVTokenDebt")).deploy(usdt.address, comptroller.address); + router = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); + + liq = await deployLiquidator(comptroller.address); + await liq.connect(owner).setRouter(router.address, true); + + // Liquidity for the pipeline: collateral market holds bStock to pay redeem; router holds USDT to pay swap. + await bStock.mint(vBStock.address, SEIZED); + await usdt.mint(router.address, OUT); + + // Borrower is underwater. + await comptroller.setShortfall(U("800")); + } + + async function deployLiquidator(comptrollerAddr: string) { + const Factory = await ethers.getContractFactory("BStockLiquidator"); + return upgrades.deployProxy(Factory, [owner.address], { + constructorArgs: [comptrollerAddr], + unsafeAllow: ["constructor", "state-variable-immutable"], + }); + } + + // swap(tokenIn, amountIn, tokenOut, to): fixed-amount calldata forwarded to the allowlisted router. + function swapCalldata(amountIn: BigNumber, to: string) { + return router.interface.encodeFunctionData("swap", [bStock.address, amountIn, usdt.address, to]); + } + + // swapAll(tokenIn, tokenOut, to): pulls the contract's whole bStock balance (used when the exact + // seized amount isn't known up front, e.g. when a treasury cut shrinks it). + function swapAllCalldata(to: string) { + return router.interface.encodeFunctionData("swapAll", [bStock.address, usdt.address, to]); + } + + function params(over: Partial = {}) { + return { + borrower: borrower.address, + vDebt: vDebt.address, + vBStock: vBStock.address, + repayAmount: REPAY, + router: router.address, + swapCalldata: swapCalldata(SEIZED, liq.address), + minOut: MIN_OUT, + ...over, + }; + } + + beforeEach(deploy); + + describe("inventory mode", () => { + it("repays, seizes, redeems, sells, and keeps the incentive as profit", async () => { + await usdt.mint(liq.address, REPAY); // pre-funded inventory + + expect(await liq.connect(owner).callStatic.liquidate(params())).to.equal(OUT); + + await expect(liq.connect(owner).liquidate(params())) + .to.emit(liq, "Liquidated") + .withArgs(borrower.address, vBStock.address, REPAY, SEIZED, OUT, false); + + // Started with REPAY, repaid REPAY, received OUT -> ends at OUT (profit = OUT - REPAY = 500). + expect(await usdt.balanceOf(liq.address)).to.equal(OUT); + // No standing bStock approval, no leftover bStock. + expect(await bStock.allowance(liq.address, router.address)).to.equal(0); + expect(await bStock.balanceOf(liq.address)).to.equal(0); + }); + + it("lets an allowlisted operator (not just owner) trigger it", async () => { + await usdt.mint(liq.address, REPAY); + await liq.connect(owner).setOperator(operator.address, true); + await expect(liq.connect(operator).liquidate(params())).to.emit(liq, "Liquidated"); + }); + + it("routes the repay through the Venus Liquidator when the pool gate is set", async () => { + const venusLiq = await (await ethers.getContractFactory("MockVenusLiquidator")).deploy(); + await comptroller.setLiquidatorContract(venusLiq.address); + await usdt.mint(liq.address, REPAY); + + await expect(liq.connect(owner).liquidate(params())) + .to.emit(liq, "Liquidated") + .withArgs(borrower.address, vBStock.address, REPAY, SEIZED, OUT, false); + + // The repay went to the gated Venus Liquidator, not to vDebt directly. + expect(await usdt.balanceOf(venusLiq.address)).to.equal(REPAY); + }); + + it("handles the Venus Liquidator treasury cut via delta accounting (sells only what was credited)", async () => { + const venusLiq = await (await ethers.getContractFactory("MockVenusLiquidator")).deploy(); + await venusLiq.setTreasuryCut(U("0.1")); // liquidator keeps back 10% of the seized collateral + await comptroller.setLiquidatorContract(venusLiq.address); + await usdt.mint(liq.address, REPAY); + + const seizedAfterCut = SEIZED.mul(9).div(10); // 4950 credited to the contract + // Use swapAll: the exact seized amount is only known on-chain after the cut. + await expect( + liq.connect(owner).liquidate(params({ swapCalldata: swapAllCalldata(liq.address), minOut: U("4900") })), + ) + .to.emit(liq, "Liquidated") + .withArgs(borrower.address, vBStock.address, REPAY, seizedAfterCut, seizedAfterCut, false); + + // 5000 inventory - 5000 repaid + 4950 proceeds = 4950, nothing stuck. + expect(await usdt.balanceOf(liq.address)).to.equal(seizedAfterCut); + expect(await bStock.balanceOf(liq.address)).to.equal(0); + }); + + it("excludes pre-existing bStock from the seize (sells only what it just seized)", async () => { + await usdt.mint(liq.address, REPAY); + await bStock.mint(liq.address, U("100")); // stray bStock sitting in the contract + // seizedBStock in the event is the DELTA (SEIZED), not SEIZED + 100. + await expect(liq.connect(owner).liquidate(params())) + .to.emit(liq, "Liquidated") + .withArgs(borrower.address, vBStock.address, REPAY, SEIZED, OUT, false); + expect(await bStock.balanceOf(liq.address)).to.equal(U("100")); // stray untouched + }); + }); + + describe("flash mode", () => { + it("flash-borrows the repay, settles atomically, repays principal + premium", async () => { + await usdt.mint(vDebt.address, REPAY); // the vToken lends the principal + await comptroller.setFlashPremium(U("0.001")); // 0.1% premium + const premium = REPAY.mul(U("0.001")).div(U("1")); // 5 USDT + + await expect(liq.connect(owner).flashLiquidate(params())) + .to.emit(liq, "Liquidated") + .withArgs(borrower.address, vBStock.address, REPAY, SEIZED, OUT, true); + + // Contract keeps proceeds minus principal minus premium; no inventory was used. + expect(await usdt.balanceOf(liq.address)).to.equal(OUT.sub(REPAY).sub(premium)); + // vToken received the liquidateBorrow repay AND the flash repayment (principal + premium). + expect(await usdt.balanceOf(vDebt.address)).to.equal(REPAY.mul(2).add(premium)); + }); + + it("reverts the whole tx (flash unwinds) when the sale underdelivers", async () => { + await usdt.mint(vDebt.address, REPAY); + await router.setRate(U("0.5")); // 5500 bStock -> 2750 USDT, below minOut + await expect(liq.connect(owner).flashLiquidate(params())).to.be.revertedWithCustomError(liq, "InsufficientOut"); + expect(await usdt.balanceOf(liq.address)).to.equal(0); // nothing stuck + }); + + it("will not let USDT inventory mask a swap that fails to cover principal + premium", async () => { + await usdt.mint(vDebt.address, REPAY); // flash principal + await usdt.mint(liq.address, U("2000")); // inventory that could silently backfill + await comptroller.setFlashPremium(U("0.001")); // premium -> obligation 5005 + await router.setRate(U("0.8")); // 5500 -> 4400 USDT: clears a loose minOut but < 5005 + const p = params({ minOut: U("4000") }); + await expect(liq.connect(owner).flashLiquidate(p)).to.be.revertedWithCustomError(liq, "InsufficientOut"); + expect(await usdt.balanceOf(liq.address)).to.equal(U("2000")); // inventory untouched + }); + }); + + describe("flash callback guards (executeOperation)", () => { + it("rejects a caller other than the Comptroller", async () => { + await expect( + liq.connect(stranger).executeOperation([vDebt.address], [REPAY], [0], liq.address, liq.address, "0x"), + ).to.be.revertedWithCustomError(liq, "OnlyComptroller"); + }); + + it("rejects the Comptroller calling outside an in-flight flash", async () => { + // msg.sender == comptroller but no flash is active (_flashActive == false). + await impersonateAccount(comptroller.address); + await setBalance(comptroller.address, U("1")); + const asComptroller = await ethers.getSigner(comptroller.address); + await expect( + liq.connect(asComptroller).executeOperation([vDebt.address], [REPAY], [0], liq.address, liq.address, "0x"), + ).to.be.revertedWithCustomError(liq, "NoFlashInFlight"); + }); + + it("rejects a flash callback that reports a foreign initiator", async () => { + const evil = await (await ethers.getContractFactory("MockMaliciousFlashComptroller")).deploy(); + await evil.setMode(0); // BadInitiator + const evilLiq = await deployLiquidator(evil.address); + await evilLiq.connect(owner).setRouter(router.address, true); + await expect(evilLiq.connect(owner).flashLiquidate(params())).to.be.revertedWithCustomError( + evilLiq, + "BadInitiator", + ); + }); + + it("rejects a flash callback whose flashed asset != params.vDebt", async () => { + const evil = await (await ethers.getContractFactory("MockMaliciousFlashComptroller")).deploy(); + await evil.setMode(1); // WrongAsset + const evilLiq = await deployLiquidator(evil.address); + await evilLiq.connect(owner).setRouter(router.address, true); + await expect(evilLiq.connect(owner).flashLiquidate(params())).to.be.revertedWithCustomError( + evilLiq, + "WrongFlashAsset", + ); + }); + }); + + describe("liquidation guards", () => { + beforeEach(async () => { + await usdt.mint(liq.address, REPAY); + }); + + it("rejects a non-operator caller", async () => { + await expect(liq.connect(stranger).liquidate(params())).to.be.revertedWithCustomError(liq, "NotOperator"); + }); + + it("rejects a non-allowlisted router (defends the low-level call)", async () => { + await expect(liq.connect(owner).liquidate(params({ router: stranger.address }))).to.be.revertedWithCustomError( + liq, + "RouterNotAllowed", + ); + }); + + it("rejects a healthy (no-shortfall) borrower", async () => { + await comptroller.setShortfall(0); + await expect(liq.connect(owner).liquidate(params())).to.be.revertedWithCustomError(liq, "NotLiquidatable"); + }); + + it("enforces minOut", async () => { + await router.setRate(U("0.9")); // 4950 USDT < 5400 minOut + await expect(liq.connect(owner).liquidate(params())).to.be.revertedWithCustomError(liq, "InsufficientOut"); + }); + + it("bubbles up a non-zero liquidateBorrow error code", async () => { + await vDebt.setLiquidateError(99); + await expect(liq.connect(owner).liquidate(params())) + .to.be.revertedWithCustomError(liq, "LiquidateBorrowFailed") + .withArgs(99); + }); + + it("bubbles up a non-zero redeem error code", async () => { + await vBStock.setRedeemError(7); + await expect(liq.connect(owner).liquidate(params())) + .to.be.revertedWithCustomError(liq, "RedeemFailed") + .withArgs(7); + }); + + it("reverts SwapFailed when the router call reverts", async () => { + // amountIn exceeds what the contract holds/approves, so the router's transferFrom reverts. + await expect( + liq.connect(owner).liquidate(params({ swapCalldata: swapCalldata(SEIZED.mul(2), liq.address) })), + ).to.be.revertedWithCustomError(liq, "SwapFailed"); + }); + }); + + describe("admin", () => { + it("locks the initializer and rejects a zero owner / zero comptroller", async () => { + await expect(liq.initialize(owner.address)).to.be.revertedWith("Initializable: contract is already initialized"); + const Factory = await ethers.getContractFactory("BStockLiquidator"); + // zero comptroller -> implementation constructor reverts + await expect(Factory.deploy(ZERO)).to.be.revertedWithCustomError(Factory, "ZeroAddressNotAllowed"); + // zero owner -> initializer reverts during proxy deploy + await expect( + upgrades.deployProxy(Factory, [ZERO], { + constructorArgs: [comptroller.address], + unsafeAllow: ["constructor", "state-variable-immutable"], + }), + ).to.be.revertedWithCustomError(Factory, "ZeroAddressNotAllowed"); + }); + + it("setOperator: owner-only, non-zero, emits, and toggles access", async () => { + await expect(liq.connect(stranger).setOperator(operator.address, true)).to.be.revertedWith( + "Ownable: caller is not the owner", + ); + await expect(liq.connect(owner).setOperator(ZERO, true)).to.be.revertedWithCustomError( + liq, + "ZeroAddressNotAllowed", + ); + await expect(liq.connect(owner).setOperator(operator.address, true)) + .to.emit(liq, "OperatorSet") + .withArgs(operator.address, true); + expect(await liq.isOperator(operator.address)).to.equal(true); + + // Revoking takes effect: the operator can no longer trigger a liquidation. + await usdt.mint(liq.address, REPAY); + await liq.connect(owner).setOperator(operator.address, false); + await expect(liq.connect(operator).liquidate(params())).to.be.revertedWithCustomError(liq, "NotOperator"); + }); + + it("setRouter: owner-only, non-zero, emits", async () => { + await expect(liq.connect(stranger).setRouter(stranger.address, true)).to.be.revertedWith( + "Ownable: caller is not the owner", + ); + await expect(liq.connect(owner).setRouter(ZERO, true)).to.be.revertedWithCustomError( + liq, + "ZeroAddressNotAllowed", + ); + await expect(liq.connect(owner).setRouter(stranger.address, true)) + .to.emit(liq, "RouterSet") + .withArgs(stranger.address, true); + expect(await liq.isRouter(stranger.address)).to.equal(true); + }); + + it("sweep: owner withdraws tokens and emits, non-owner reverts", async () => { + await usdt.mint(liq.address, U("123")); + await expect(liq.connect(stranger).sweep(usdt.address, stranger.address, U("1"))).to.be.revertedWith( + "Ownable: caller is not the owner", + ); + await expect(liq.connect(owner).sweep(usdt.address, owner.address, U("123"))) + .to.emit(liq, "Swept") + .withArgs(usdt.address, owner.address, U("123")); + expect(await usdt.balanceOf(owner.address)).to.equal(U("123")); + expect(await usdt.balanceOf(liq.address)).to.equal(0); + }); + }); +}); diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts new file mode 100644 index 000000000..913ec6808 --- /dev/null +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -0,0 +1,171 @@ +import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { expect } from "chai"; +import { BigNumber, Contract } from "ethers"; +import { parseEther, parseUnits } from "ethers/lib/utils"; +import { ethers, upgrades } from "hardhat"; + +import { IAccessControlManagerV8__factory } from "../../../typechain"; +import { FORK_MAINNET, forking, initMainnetUser } from "./utils"; + +/** + * Real bscmainnet fork test for the atomic BStockLiquidator. + * + * The bStock markets (vTSLAB/vNVDAB) are DEPLOYED (PR #679) but NOT YET LISTED in Core on mainnet + * (VIP #718 hasn't executed), so this exercises the contract's atomic pipeline against REAL Venus + * Core mechanics (liquidateBorrow -> seize -> redeem) using liquid Core markets as stand-ins for the + * bStock collateral: vUSDC (collateral) / vUSDT (debt). The contract is collateral-agnostic; retarget + * to vTSLAB/vUSDT via FORK_VCOLLATERAL / FORK_VDEBT / FORK_COLLATERAL_WHALE once the market is listed. + * + * Setup, end to end: + * 1. Build a genuine borrow position (supply collateral -> enterMarkets -> borrow). + * 2. MANIPULATE it into a real shortfall by dropping the collateral's liquidation threshold via the + * timelock (a governance parameter change) — no balance is faked. + * 3. Deploy the liquidator and run `liquidate`. Core's `liquidatorContract` is already set to the real + * Venus Liquidator on mainnet, so the repay is routed through it automatically (no gate config here). + * The Native swap is mocked (a signed firm-quote can't be obtained for synthetic fork amounts), exactly + * as it would be on any fork; everything else is real Core. + */ + +const A = { + COMPTROLLER: "0xfD36E2c2a6789Db23113685031d7F16329158384", + VCOLLATERAL: process.env.FORK_VCOLLATERAL || "0xecA88125a5ADbe82614ffC12D0DB554E2e2867C8", // vUSDC + VDEBT: process.env.FORK_VDEBT || "0xfD5840Cd36d94D7229439859C0112a4185BC0255", // vUSDT + COLLATERAL_WHALE: process.env.FORK_COLLATERAL_WHALE || "0xF977814e90dA44bFA03b6295A0616a897441aceC", + DEBT_WHALE: process.env.FORK_DEBT_WHALE || "0xF977814e90dA44bFA03b6295A0616a897441aceC", + TIMELOCK: "0x939bD8d64c0A9583A7Dcea9933f7b21697ab6396", + ACM: "0x4788629ABc6cFCA10F9f969efdEAa1cF70c23555", +}; + +const VTOKEN_ABI = [ + "function underlying() view returns (address)", + "function mint(uint256) returns (uint256)", + "function borrow(uint256) returns (uint256)", + "function balanceOf(address) view returns (uint256)", + "function borrowBalanceCurrent(address) returns (uint256)", + "function exchangeRateStored() view returns (uint256)", +]; +const COMPTROLLER_ABI = [ + "function enterMarkets(address[]) returns (uint256[])", + "function getAccountLiquidity(address) view returns (uint256,uint256,uint256)", + "function liquidateCalculateSeizeTokens(address,address,uint256) view returns (uint256,uint256)", + "function setCollateralFactor(address,uint256,uint256) returns (uint256)", +]; +const ERC20_ABI = [ + "function approve(address,uint256) returns (bool)", + "function transfer(address,uint256) returns (bool)", + "function balanceOf(address) view returns (uint256)", + "function decimals() view returns (uint8)", +]; + +const test = () => { + describe("BStockLiquidator — bscmainnet fork (real Venus Core)", () => { + let owner: SignerWithAddress; + let borrower: SignerWithAddress; + let timelock: SignerWithAddress; + let comptroller: Contract, vCol: Contract, vDebt: Contract, col: Contract, debt: Contract; + let router: Contract, liq: Contract; + let colDec: number, debtDec: number; + + const REPAY = parseUnits("1000", 18); + + before(async () => { + [owner, borrower] = await ethers.getSigners(); + timelock = await initMainnetUser(A.TIMELOCK, parseEther("10")); + + comptroller = new ethers.Contract(A.COMPTROLLER, COMPTROLLER_ABI, owner); + vCol = new ethers.Contract(A.VCOLLATERAL, VTOKEN_ABI, owner); + vDebt = new ethers.Contract(A.VDEBT, VTOKEN_ABI, owner); + col = new ethers.Contract(await vCol.underlying(), ERC20_ABI, owner); + debt = new ethers.Contract(await vDebt.underlying(), ERC20_ABI, owner); + colDec = await col.decimals(); + debtDec = await debt.decimals(); + + // --- access: grant the timelock the ACM perm to drop the collateral factor (for shortfall) --- + const acm = IAccessControlManagerV8__factory.connect(A.ACM, timelock); + await acm.giveCallPermission(A.COMPTROLLER, "setCollateralFactor(address,uint256,uint256)", A.TIMELOCK); + + // --- build a real borrow position --- + const colWhale = await initMainnetUser(A.COLLATERAL_WHALE, parseEther("10")); + const supply = parseUnits("10000", colDec); + await col.connect(colWhale).transfer(borrower.address, supply); + await col.connect(borrower).approve(vCol.address, supply); + expect(await vCol.connect(borrower).callStatic.mint(supply)).to.equal(0); + await vCol.connect(borrower).mint(supply); + await comptroller.connect(borrower).enterMarkets([vCol.address]); + await vDebt.connect(borrower).borrow(parseUnits("8000", debtDec)); + + // --- MANIPULATE: drop the liquidation threshold so the account is underwater --- + // setCollateralFactor(vToken, CF, LT): LT 0.50 -> max borrow 5000 < 8000 borrowed => shortfall. + await comptroller + .connect(timelock) + .setCollateralFactor(vCol.address, parseUnits("0.5", 18), parseUnits("0.5", 18)); + const [, , shortfall] = await comptroller.getAccountLiquidity(borrower.address); + expect(shortfall).to.be.gt(0); // position manipulated into shortfall + + // --- deploy the liquidator (routes through Core's existing Venus Liquidator automatically) --- + const Factory = await ethers.getContractFactory("BStockLiquidator"); + liq = await upgrades.deployProxy(Factory, [owner.address], { + constructorArgs: [A.COMPTROLLER], + unsafeAllow: ["constructor", "state-variable-immutable"], + }); + + // --- deploy + fund the mock Native router and the liquidator inventory --- + router = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); + await liq.connect(owner).setRouter(router.address, true); + const debtWhale = await initMainnetUser(A.DEBT_WHALE, parseEther("10")); + await debt.connect(debtWhale).transfer(router.address, parseUnits("2000", debtDec)); // USDT to pay the swap + await debt.connect(debtWhale).transfer(liq.address, REPAY); // USDT inventory to fund the repay + }); + + it("atomically repays, seizes, redeems, sells, and books a profit", async () => { + // Precompute the seized collateral -> raw underlying the contract will hold after redeem (for minOut). + const [, seizeTokens]: BigNumber[] = await comptroller.liquidateCalculateSeizeTokens( + vDebt.address, + vCol.address, + REPAY, + ); + const rate: BigNumber = await vCol.exchangeRateStored(); + const redeemed = seizeTokens.mul(rate).div(parseUnits("1", 18)); + + // swapAll sells whatever the contract actually holds post-redeem (no fixed-amount mismatch). + const swapCalldata = router.interface.encodeFunctionData("swapAll", [col.address, debt.address, liq.address]); + const params = { + borrower: borrower.address, + vDebt: vDebt.address, + vBStock: vCol.address, + repayAmount: REPAY, + router: router.address, + swapCalldata, + // Loose floor: the real Venus Liquidator keeps a treasury cut of the seized collateral, so + // proceeds land a few % under the full redeemed value. Profit is asserted strictly below. + minOut: redeemed.mul(90).div(100), + }; + + const borrowBefore = await vDebt.connect(owner).callStatic.borrowBalanceCurrent(borrower.address); + const debtBefore = await debt.balanceOf(liq.address); // = REPAY inventory + const out = await liq.connect(owner).callStatic.liquidate(params); + expect(out).to.be.gte(params.minOut); + + await expect(liq.connect(owner).liquidate(params)) + .to.emit(liq, "Liquidated") + .withArgs(borrower.address, vCol.address, REPAY, anyValue, anyValue, false); + + // Proceeds match the simulated `out` (dust differs by one block of accrual). + const debtAfter = await debt.balanceOf(liq.address); + const proceeds = debtAfter.sub(debtBefore).add(REPAY); + expect(proceeds).to.be.closeTo(out, parseUnits("1", 15)); + + // Strict: a profit was booked, the borrow shrank by the repay, nothing is left stuck. + expect(proceeds).to.be.gt(REPAY); // incentive captured (proceeds exceed the repay funded) + expect(debtAfter).to.be.gt(debtBefore); + const borrowAfter = await vDebt.connect(owner).callStatic.borrowBalanceCurrent(borrower.address); + expect(borrowBefore.sub(borrowAfter)).to.be.closeTo(REPAY, parseUnits("1", 15)); + expect(await col.balanceOf(liq.address)).to.equal(0); // no collateral left stuck + }); + }); +}; + +if (FORK_MAINNET) { + forking(104930000, test); +} From fc0cbbd27722d58fc1021214e4c163f0cc95a262 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 22 Jun 2026 14:33:12 +0530 Subject: [PATCH 02/78] feat(bstock): add offchain liquidation scripts and driver test - Native RFQ client (orderbook / firm-quote) plus a read-only quote smoke test for eyeballing price, depth and TTL - atomic liquidation driver: precompute the seize, fetch the signed firm-quote with the contract as taker, then call liquidate or flashLiquidate; warn early if inventory cannot cover the repay - mock-based test driving the script end to end: inventory and flash paths, plus the healthy-borrower and router-allowlist guards - add scripts/ to the eslint tsconfig include (it was uncovered) and add an exchangeRateStored getter to the collateral mock that the driver reads --- .eslinttsconfigrc | 2 +- contracts/test/BStockLiquidationMocks.sol | 5 + scripts/bstock/atomic-liquidate.ts | 199 ++++++++++++++++++++++ scripts/bstock/lib/native.ts | 145 ++++++++++++++++ scripts/bstock/native-quote.ts | 54 ++++++ tests/hardhat/BStockAtomicLiquidate.ts | 144 ++++++++++++++++ 6 files changed, 548 insertions(+), 1 deletion(-) create mode 100644 scripts/bstock/atomic-liquidate.ts create mode 100644 scripts/bstock/lib/native.ts create mode 100644 scripts/bstock/native-quote.ts create mode 100644 tests/hardhat/BStockAtomicLiquidate.ts diff --git a/.eslinttsconfigrc b/.eslinttsconfigrc index 0f5ac5170..c459504aa 100644 --- a/.eslinttsconfigrc +++ b/.eslinttsconfigrc @@ -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"] } diff --git a/contracts/test/BStockLiquidationMocks.sol b/contracts/test/BStockLiquidationMocks.sol index 61e289d15..fc15b3256 100644 --- a/contracts/test/BStockLiquidationMocks.sol +++ b/contracts/test/BStockLiquidationMocks.sol @@ -126,6 +126,11 @@ contract MockVTokenCollateral { require(ERC20(underlying).transfer(msg.sender, redeemTokens), "redeem transfer failed"); return 0; } + + /// @dev 1:1, matching `redeem` above. Read by the off-chain script to precompute the seize. + function exchangeRateStored() external pure returns (uint256) { + return 1e18; + } } /// @dev Debt vToken mock. liquidateBorrow pulls `repayAmount` of the debt diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts new file mode 100644 index 000000000..92efe28c5 --- /dev/null +++ b/scripts/bstock/atomic-liquidate.ts @@ -0,0 +1,199 @@ +/** + * bStock ATOMIC liquidation — drives the on-chain `BStockLiquidator` contract. + * + * One tx: the contract repays the borrow, seizes + redeems the bStock, and sells it to + * USDT via a pre-fetched Native firm-quote. This script does the OFF-CHAIN half (precompute + * the seize amount, fetch the MM-signed firm-quote with `from_address = the contract`) and + * then calls `liquidate` (inventory mode) or `flashLiquidate` (Venus flash-loan mode). + * + * 1. Comptroller.liquidateCalculateSeizeTokens(vDebt, vBStock, repay) -> seize vTokens + * 2. seizeTokens * vBStock.exchangeRateStored() / 1e18 -> raw bStock (floor) + * 3. Native firm-quote (from_address = LIQUIDATOR contract) -> txRequest + amountOut + * 4. BStockLiquidator.liquidate / flashLiquidate(params) -> atomic settle + * + * Usage: + * NATIVE_API_KEY=... LIQUIDATOR=0x.. BORROWER=0x.. VBSTOCK=0x.. VDEBT=0x.. REPAY_AMOUNT=5000 \ + * npx hardhat run scripts/bstock/atomic-liquidate.ts --network bscmainnet + * + * Env: + * LIQUIDATOR (req) deployed BStockLiquidator address + * BORROWER (req) account to liquidate + * VBSTOCK (req) bStock collateral market (e.g. vTSLAB) + * VDEBT (req) borrowed market to repay (e.g. vUSDT) + * REPAY_AMOUNT (req) repay in DEBT underlying, human units + * MODE "inventory" (default) | "flash" + * SLIPPAGE Native slippage %, default 0.5 + * MIN_OUT_BUFFER extra haircut on minOut beyond slippage, default 0.5 (%) + * DRY_RUN "1" -> callStatic only, send nothing + * MOCK_NATIVE router addr + calldata source for fork tests (see below) + */ +import { BigNumber, Contract, Signer } from "ethers"; +import { ethers } from "hardhat"; + +import { BSC_USDT, getFirmQuote, quoteDeadline } from "./lib/native"; + +const LIQUIDATOR_ABI = [ + "function liquidate((address borrower,address vDebt,address vBStock,uint256 repayAmount,address router,bytes swapCalldata,uint256 minOut)) returns (uint256)", + "function flashLiquidate((address borrower,address vDebt,address vBStock,uint256 repayAmount,address router,bytes swapCalldata,uint256 minOut))", + "function isRouter(address) view returns (bool)", +]; +const VTOKEN_ABI = [ + "function underlying() view returns (address)", + "function comptroller() view returns (address)", + "function exchangeRateStored() view returns (uint256)", +]; +const ERC20_ABI = [ + "function decimals() view returns (uint8)", + "function symbol() view returns (string)", + "function balanceOf(address) view returns (uint256)", +]; +const COMPTROLLER_ABI = [ + "function getAccountLiquidity(address) view returns (uint256,uint256,uint256)", + "function liquidateCalculateSeizeTokens(address,address,uint256) view returns (uint256,uint256)", + "function treasuryPercent() view returns (uint256)", +]; + +function env(name: string, required = true): string { + const v = process.env[name]; + if (required && !v) throw new Error(`Missing env ${name}`); + return v || ""; +} + +export async function atomicLiquidate(signer: Signer) { + const dryRun = process.env.DRY_RUN === "1"; + const mode = (process.env.MODE || "inventory").toLowerCase(); + const slippage = Number(process.env.SLIPPAGE || "0.5"); + const minOutBufferPct = Number(process.env.MIN_OUT_BUFFER || "0.5"); + + const liquidator = new Contract(env("LIQUIDATOR"), LIQUIDATOR_ABI, signer); + const borrower = ethers.utils.getAddress(env("BORROWER")); + const vBStock = new Contract(env("VBSTOCK"), VTOKEN_ABI, signer); + const vDebt = new Contract(env("VDEBT"), VTOKEN_ABI, signer); + + const comptroller = new Contract(await vBStock.comptroller(), COMPTROLLER_ABI, signer); + const debt = new Contract(await vDebt.underlying(), ERC20_ABI, signer); + const bStock = new Contract(await vBStock.underlying(), ERC20_ABI, signer); + const [debtDec, debtSym, bStockDec, bStockSym] = await Promise.all([ + debt.decimals(), + debt.symbol(), + bStock.decimals(), + bStock.symbol(), + ]); + const repay = ethers.utils.parseUnits(env("REPAY_AMOUNT"), debtDec); + + // 0. liquidatable? + const [, , shortfall]: BigNumber[] = await comptroller.getAccountLiquidity(borrower); + if (shortfall.eq(0)) throw new Error(`${borrower} has no shortfall — not liquidatable`); + console.log(`borrower ${borrower} shortfall=${ethers.utils.formatEther(shortfall)} (USD-scaled)`); + + // 1 + 2. precompute the exact seize so the quote amount matches what redeem() yields. + const [seizeErr, seizeTokens]: BigNumber[] = await comptroller.liquidateCalculateSeizeTokens( + vDebt.address, + vBStock.address, + repay, + ); + if (!seizeErr.eq(0)) throw new Error(`liquidateCalculateSeizeTokens error ${seizeErr}`); + const exchangeRate: BigNumber = await vBStock.exchangeRateStored(); + const ONE = BigNumber.from(10).pow(18); + // Core redeem routes `treasuryPercent` of the redeemed underlying to the treasury, so the contract + // ends up holding LESS than seizeTokens*rate. The quote must match what we actually hold, else the + // Native router pull (fixed amountIn) reverts. 0 today, but governance-settable. + const treasuryPercent: BigNumber = await comptroller.treasuryPercent(); + const seizedRaw = seizeTokens.mul(exchangeRate).div(ONE).mul(ONE.sub(treasuryPercent)).div(ONE); + const seizedHuman = ethers.utils.formatUnits(seizedRaw, bStockDec); + console.log(`seize ${ethers.utils.formatUnits(seizeTokens, 8)} v${bStockSym} -> ~${seizedHuman} ${bStockSym}`); + + // 3. firm-quote, taker = the LIQUIDATOR CONTRACT (so it can submit + receive USDT). + const usdtAddr = process.env.USDT_ADDR || BSC_USDT; + let router: string; + let swapCalldata: string; + let amountOut: BigNumber; + + if (process.env.MOCK_NATIVE) { + // Fork-test path: MOCK_NATIVE = ":" pre-encoded against a MockNativeRouter. + const [r, data] = process.env.MOCK_NATIVE.split(":"); + router = ethers.utils.getAddress(r); + swapCalldata = data; + amountOut = BigNumber.from(process.env.MOCK_OUT || "0"); + } else { + const q = await getFirmQuote({ + fromAddress: liquidator.address, + tokenIn: bStock.address, + tokenOut: usdtAddr, + amount: seizedHuman, + slippage, + }); + const ttl = quoteDeadline(q) - Math.floor(Date.now() / 1000); + if (ttl <= 0) throw new Error("quote already expired — refetch"); + router = q.txRequest.target; + swapCalldata = q.txRequest.calldata; + amountOut = BigNumber.from(q.amountOut); + console.log( + `Native quote: ${seizedHuman} ${bStockSym} -> ${ethers.utils.formatUnits(amountOut, debtDec)} ${debtSym} (TTL ${ttl}s, router ${router})`, + ); + } + + if (!(await liquidator.isRouter(router))) { + throw new Error(`router ${router} is not allowlisted on the liquidator — call setRouter first`); + } + + // minOut = amountOut minus an extra safety buffer on top of the quote slippage. + const minOut = amountOut.mul(Math.round((100 - minOutBufferPct) * 100)).div(10000); + + const params = { + borrower, + vDebt: vDebt.address, + vBStock: vBStock.address, + repayAmount: repay, + router, + swapCalldata, + minOut, + }; + + // Inventory mode spends the contract's own debt-asset balance; warn early if it can't cover the + // repay so the failure is legible off-chain instead of a bare on-chain revert. (Flash mode borrows.) + if (mode !== "flash") { + const inventory: BigNumber = await debt.balanceOf(liquidator.address); + if (inventory.lt(repay)) { + console.warn( + `WARN: liquidator holds ${ethers.utils.formatUnits(inventory, debtDec)} ${debtSym} < repay ` + + `${env("REPAY_AMOUNT")} ${debtSym} — fund it or use MODE=flash, else liquidate() will revert.`, + ); + } + } + + // 4. settle. + const fn = mode === "flash" ? "flashLiquidate" : "liquidate"; + console.log(`mode=${mode} -> ${fn}(...) minOut=${ethers.utils.formatUnits(minOut, debtDec)} ${debtSym}`); + if (dryRun) { + await liquidator.callStatic[fn](params); + console.log(" [dry-run] would succeed"); + return; + } + const tx = await liquidator[fn](params); + const rcpt = await tx.wait(); + console.log(` ${fn} mined: ${rcpt.transactionHash} (gas ${rcpt.gasUsed.toString()})`); +} + +async function main() { + let signer: Signer; + if (process.env.IMPERSONATE) { + const { impersonateAccount, setBalance } = await import("@nomicfoundation/hardhat-network-helpers"); + await impersonateAccount(process.env.IMPERSONATE); + await setBalance(process.env.IMPERSONATE, ethers.utils.parseEther("10")); + signer = await ethers.getSigner(process.env.IMPERSONATE); + } else { + [signer] = await ethers.getSigners(); + } + console.log(`caller: ${await signer.getAddress()} (dryRun=${process.env.DRY_RUN === "1"})`); + await atomicLiquidate(signer); +} + +if (require.main === module) { + main() + .then(() => process.exit(0)) + .catch(e => { + console.error(e); + process.exit(1); + }); +} diff --git a/scripts/bstock/lib/native.ts b/scripts/bstock/lib/native.ts new file mode 100644 index 000000000..6e7fc43d0 --- /dev/null +++ b/scripts/bstock/lib/native.ts @@ -0,0 +1,145 @@ +/** + * Native (nativefi) Swap API client — same-chain RFQ ("FirmQuote"). + * + * Native is the designated market maker for bStock (ERC-8056 tokenized stocks) + * on BNB Chain. It quotes bStock <-> USDT directly. A `firm-quote` returns a + * ready-to-send `txRequest` (target/calldata/value) plus the MM's signed + * order(s) with a `deadlineTimestamp` (TTL). The taker (our liquidator EOA or + * contract) submits `txRequest` on-chain to settle. + * + * Auth: HTTP header `api_key`. Provide via env `NATIVE_API_KEY` — never commit it. + * + * Endpoints (base `https://v2.api.native.org/swap-api-v2/v1`): + * GET /orderbook?chain=bsc supported pairs + * GET /indicative-quote price only (no executable tx) + * GET /firm-quote executable order + txRequest + * + * Note: the `/bridge/*` variants are cross-chain and use a different auth scope. + */ + +const DEFAULT_BASE = "https://v2.api.native.org/swap-api-v2/v1"; + +export interface NativeOrder { + pool: string; + signer: string; + recipient: string; + sellerToken: string; + buyerToken: string; + sellerTokenAmount: string; + buyerTokenAmount: string; + deadlineTimestamp: number; + nonce: string; + quoteId: string; + signature: string; + externalSwapCalldata?: string; +} + +export interface NativeTxRequest { + target: string; + calldata: string; + value: string; + function?: string; +} + +export interface NativeFirmQuote { + success: boolean; + recipient: string; + amountIn: string; // wei of token_in + amountOut: string; // wei of token_out + amountOutBeforeFee?: string; + orders: NativeOrder[]; + txRequest: NativeTxRequest; + errorMessage?: string; + router_version?: string; +} + +export interface FirmQuoteParams { + chain?: string; // default "bsc" + fromAddress: string; // taker submitting txRequest + tokenIn: string; + tokenOut: string; + amount: string; // human units (e.g. "1.5"), NOT wei + slippage?: number; // percent, e.g. 0.5 + refundTo?: string; // default fromAddress + version?: number; // default 4 +} + +function baseUrl(): string { + return process.env.NATIVE_API_BASE || DEFAULT_BASE; +} + +function apiKey(): string { + const key = process.env.NATIVE_API_KEY; + if (!key) { + throw new Error("NATIVE_API_KEY env var is required (Native Swap API key). Do not hardcode it."); + } + return key; +} + +async function get(path: string, params: Record): Promise { + const qs = new URLSearchParams(params).toString(); + const url = `${baseUrl()}${path}?${qs}`; + const res = await fetch(url, { headers: { api_key: apiKey() } }); + const body = await res.json(); + // Native returns 200 with an error envelope on failure; surface both forms. + if (body && body.code !== undefined && body.message !== undefined && body.success === undefined) { + throw new Error(`Native API error ${body.code}: ${body.message} (${path})`); + } + return body; +} + +/** List supported pairs for a chain (default bsc). */ +export async function getOrderbook(chain = "bsc"): Promise { + return get("/orderbook", { chain }); +} + +/** Indicative (non-executable) price for a pair. */ +export async function getIndicativeQuote(p: FirmQuoteParams): Promise { + const chain = p.chain || "bsc"; + return get("/indicative-quote", { + from_address: p.fromAddress, + src_chain: chain, + dst_chain: chain, + token_in: p.tokenIn, + token_out: p.tokenOut, + amount: p.amount, + version: String(p.version ?? 4), + }); +} + +/** + * Firm (executable) quote. The returned `txRequest` is sent on-chain by the + * taker; it is only valid until `orders[].deadlineTimestamp`. + */ +export async function getFirmQuote(p: FirmQuoteParams): Promise { + const chain = p.chain || "bsc"; + const q: NativeFirmQuote = await get("/firm-quote", { + from_address: p.fromAddress, + src_chain: chain, + dst_chain: chain, + token_in: p.tokenIn, + token_out: p.tokenOut, + amount: p.amount, + slippage: String(p.slippage ?? 0.5), + refund_to: p.refundTo || p.fromAddress, + version: String(p.version ?? 4), + }); + if (!q.success) { + throw new Error(`Native firm-quote failed: ${q.errorMessage || "unknown"}`); + } + return q; +} + +/** Earliest order deadline (unix seconds) across a firm quote, or 0 if none. */ +export function quoteDeadline(q: NativeFirmQuote): number { + const ds = (q.orders || []).map(o => Number(o.deadlineTimestamp)).filter(n => n > 0); + return ds.length ? Math.min(...ds) : 0; +} + +/** Known bStock token addresses on BNB Chain (18 decimals). */ +export const BSTOCK_TOKENS = { + TSLAB: "0x5b1910eAaD6450E50f816082Aa078C41F10C292f", + NVDAB: "0x02Fca66C1D1aFB4E2A7884261eB00F63598a7436", +} as const; + +export const BSC_USDT = "0x55d398326f99059fF775485246999027B3197955"; diff --git a/scripts/bstock/native-quote.ts b/scripts/bstock/native-quote.ts new file mode 100644 index 000000000..ccb8a1675 --- /dev/null +++ b/scripts/bstock/native-quote.ts @@ -0,0 +1,54 @@ +/** + * Smoke test for the Native Swap API integration (no chain interaction). + * + * Prints the BSC orderbook entries for our bStock tokens and fetches a live + * firm-quote so we can eyeball price, spread, TTL and the returned txRequest. + * + * Usage: + * NATIVE_API_KEY=... npx hardhat run scripts/bstock/native-quote.ts + * + * Options (env): + * NATIVE_API_KEY=... TOKEN=NVDAB AMOUNT=5 npx hardhat run scripts/bstock/native-quote.ts + * + * Env: + * NATIVE_API_KEY (required) Native Swap API key + * TOKEN bStock symbol to quote: TSLAB | NVDAB (default TSLAB) + * AMOUNT amount of bStock to sell, human units (default 1) + * FROM taker address (default 0x..dEaD) + */ +import { BSC_USDT, BSTOCK_TOKENS, getFirmQuote, getOrderbook, quoteDeadline } from "./lib/native"; + +async function main() { + const symbol = (process.env.TOKEN || "TSLAB") as keyof typeof BSTOCK_TOKENS; + const tokenIn = BSTOCK_TOKENS[symbol]; + if (!tokenIn) throw new Error(`Unknown TOKEN ${symbol}; use TSLAB or NVDAB`); + const amount = process.env.AMOUNT || "1"; + const from = process.env.FROM || "0x000000000000000000000000000000000000dEaD"; + + console.log(`\nOrderbook (bsc) entries for ${symbol}:`); + const ob = await getOrderbook("bsc"); + for (const r of ob) { + if (`${r.base_symbol}${r.quote_symbol}`.includes(symbol)) { + console.log(` ${r.base_symbol} <-> ${r.quote_symbol}`); + } + } + + console.log(`\nFirm quote: ${amount} ${symbol} -> USDT`); + const q = await getFirmQuote({ fromAddress: from, tokenIn, tokenOut: BSC_USDT, amount, slippage: 0.5 }); + const amtIn = Number(q.amountIn) / 1e18; + const amtOut = Number(q.amountOut) / 1e18; // USDT is 18 decimals on BSC + console.log(` amountIn : ${amtIn} ${symbol}`); + console.log(` amountOut: ${amtOut} USDT`); + console.log(` px/token : ${(amtOut / amtIn).toFixed(4)} USDT`); + const dl = quoteDeadline(q); + console.log(` deadline : ${dl} (${dl ? Math.max(0, dl - Math.floor(Date.now() / 1000)) : "?"}s TTL)`); + console.log(` router : ${q.txRequest?.target}`); + console.log(` orders : ${q.orders?.length}`); +} + +main() + .then(() => process.exit(0)) + .catch(e => { + console.error(e); + process.exit(1); + }); diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts new file mode 100644 index 000000000..30c08c8d9 --- /dev/null +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -0,0 +1,144 @@ +import { expect } from "chai"; +import { Contract } from "ethers"; +import { ethers, upgrades } from "hardhat"; + +import { atomicLiquidate } from "../../scripts/bstock/atomic-liquidate"; + +// Exercises scripts/bstock/atomic-liquidate.ts atomicLiquidate() end-to-end against the real +// BStockLiquidator proxy + the BStock mocks. No fork, no funds, no live Native API: the swap leg is +// driven through MOCK_NATIVE (":") so the script's off-chain half (shortfall check, +// seize precompute, treasury-fee adjustment, router-allowlist guard, minOut, mode dispatch) runs for +// real while the on-chain settle hits the same contract the dedicated contract suite covers. + +const U = (n: string) => ethers.utils.parseUnits(n, 18); +const INCENTIVE = U("1.1"); // mock seizes repay * 1.1 + +const REPAY = U("5000"); +const SEIZED = REPAY.mul(INCENTIVE).div(U("1")); // 5500 bStock at 1:1 redeem +const OUT = SEIZED; // router rate 1:1 -> 5500 USDT + +// Env keys the script reads; cleared between tests so one case can't leak into the next. +const SCRIPT_ENV = [ + "LIQUIDATOR", + "BORROWER", + "VBSTOCK", + "VDEBT", + "REPAY_AMOUNT", + "USDT_ADDR", + "MOCK_NATIVE", + "MOCK_OUT", + "MODE", + "DRY_RUN", + "SLIPPAGE", + "MIN_OUT_BUFFER", +] as const; + +describe("bStock atomic liquidation script", () => { + let owner: any, borrower: any; + let usdt: Contract, bStock: Contract; + let comptroller: Contract, vBStock: Contract, vDebt: Contract, router: Contract, liq: Contract; + + async function deployLiquidator(comptrollerAddr: string) { + const Factory = await ethers.getContractFactory("BStockLiquidator"); + return upgrades.deployProxy(Factory, [owner.address], { + constructorArgs: [comptrollerAddr], + unsafeAllow: ["constructor", "state-variable-immutable"], + }); + } + + // swapAll(tokenIn, tokenOut, to): the router pulls the contract's whole bStock balance. Used so the + // pre-encoded calldata doesn't have to match the exact seized amount the script computes on-chain. + function swapAllCalldata(to: string) { + return router.interface.encodeFunctionData("swapAll", [bStock.address, usdt.address, to]); + } + + function setEnv(over: Record = {}) { + Object.assign(process.env, { + LIQUIDATOR: liq.address, + BORROWER: borrower.address, + VBSTOCK: vBStock.address, + VDEBT: vDebt.address, + REPAY_AMOUNT: "5000", + USDT_ADDR: usdt.address, + MOCK_NATIVE: `${router.address}:${swapAllCalldata(liq.address)}`, + MOCK_OUT: OUT.toString(), + MODE: "inventory", + DRY_RUN: "", + SLIPPAGE: "0.5", + MIN_OUT_BUFFER: "0.5", + ...over, + }); + } + + beforeEach(async () => { + [owner, borrower] = await ethers.getSigners(); + + const ERC20 = await ethers.getContractFactory("MockMintableERC20"); + usdt = await ERC20.deploy("Tether", "USDT", 18); + bStock = await ERC20.deploy("Tesla bStock", "TSLAB", 18); + + comptroller = await (await ethers.getContractFactory("MockComptrollerLite")).deploy(); + vBStock = await ( + await ethers.getContractFactory("MockVTokenCollateral") + ).deploy(bStock.address, comptroller.address); + vDebt = await (await ethers.getContractFactory("MockVTokenDebt")).deploy(usdt.address, comptroller.address); + router = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); + + liq = await deployLiquidator(comptroller.address); + await liq.connect(owner).setRouter(router.address, true); + + // Liquidity for the pipeline: collateral market holds bStock to pay redeem; router holds USDT to pay swap. + await bStock.mint(vBStock.address, SEIZED); + await usdt.mint(router.address, OUT); + + // Borrower is underwater. + await comptroller.setShortfall(U("800")); + }); + + afterEach(() => { + for (const k of SCRIPT_ENV) delete process.env[k]; + }); + + it("inventory mode: precomputes the seize, settles via the contract, keeps the proceeds", async () => { + await usdt.mint(liq.address, REPAY); // pre-funded inventory + setEnv(); + + await atomicLiquidate(owner); + + // Started with REPAY, repaid REPAY, received OUT -> ends at OUT (profit = OUT - REPAY = 500). + expect(await usdt.balanceOf(liq.address)).to.equal(OUT); + expect(await bStock.balanceOf(liq.address)).to.equal(0); + }); + + it("flash mode: routes through flashLiquidate, repays principal + premium, keeps the rest", async () => { + await usdt.mint(vDebt.address, REPAY); // the vToken lends the principal + await comptroller.setFlashPremium(U("0.001")); // 0.1% premium + const premium = REPAY.mul(U("0.001")).div(U("1")); // 5 USDT + setEnv({ MODE: "flash" }); + + await atomicLiquidate(owner); + + // No inventory used: contract keeps proceeds minus principal minus premium. + expect(await usdt.balanceOf(liq.address)).to.equal(OUT.sub(REPAY).sub(premium)); + }); + + it("refuses a healthy (no-shortfall) borrower before sending anything", async () => { + await usdt.mint(liq.address, REPAY); + await comptroller.setShortfall(0); + setEnv(); + + await expect(atomicLiquidate(owner)).to.be.rejectedWith("no shortfall"); + expect(await usdt.balanceOf(liq.address)).to.equal(REPAY); // untouched + }); + + it("refuses a router that is not allowlisted on the contract", async () => { + await usdt.mint(liq.address, REPAY); + // Deploy a second router that was never allowlisted, and point the (mock) quote at it. + const stray = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); + const strayCalldata = stray.interface.encodeFunctionData("swapAll", [bStock.address, usdt.address, liq.address]); + setEnv({ MOCK_NATIVE: `${stray.address}:${strayCalldata}` }); + + await expect(atomicLiquidate(owner)).to.be.rejectedWith("not allowlisted"); + expect(await usdt.balanceOf(liq.address)).to.equal(REPAY); // untouched + }); +}); From 46d9558ad4080e784ab907fa0e3f97aa93958aec Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 22 Jun 2026 14:33:19 +0530 Subject: [PATCH 03/78] feat(bstock): add Safe fallback liquidation batch generator - Safe Transaction Builder JSON helper (encode calls, build batch) - read-only script emitting a 4-tx batch (approve, liquidateBorrow, redeem, transfer raw bStock) for a multisig to settle a liquidation when the RFQ path is unavailable; seize amount snapshotted from chain, sends nothing --- scripts/bstock/lib/safe.ts | 67 ++++++++++++ scripts/bstock/safe-fallback.ts | 181 ++++++++++++++++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 scripts/bstock/lib/safe.ts create mode 100644 scripts/bstock/safe-fallback.ts diff --git a/scripts/bstock/lib/safe.ts b/scripts/bstock/lib/safe.ts new file mode 100644 index 000000000..ecab0bd22 --- /dev/null +++ b/scripts/bstock/lib/safe.ts @@ -0,0 +1,67 @@ +/** + * Minimal Safe{Wallet} Transaction Builder JSON helper. + * + * Produces the exact JSON shape the Safe UI "Transaction Builder" app imports + * (Settings → Apps → Transaction Builder → "Load" a batch). Each entry is a raw + * call (`to` + hex `data`); we encode calldata ourselves with ethers so the + * batch needs no ABI upload in the UI. + * + * Schema ref: https://github.com/safe-global/safe-react-apps (tx-builder batch). + */ +import { utils } from "ethers"; + +export interface SafeTx { + to: string; + value: string; // wei, decimal string + data: string; // 0x-prefixed calldata + contractMethod: null; + contractInputsValues: null; +} + +export interface SafeBatch { + version: "1.0"; + chainId: string; + createdAt: number; // unix ms + meta: { + name: string; + description: string; + txBuilderVersion: "1.16.5"; + createdFromSafeAddress: string; + }; + transactions: SafeTx[]; +} + +/** Encode a single call: `tx(to, encode(signature, args))`. */ +export function call(to: string, signature: string, args: unknown[]): SafeTx { + const iface = new utils.Interface([`function ${signature}`]); + const fn = signature.slice(0, signature.indexOf("(")); + return { + to: utils.getAddress(to), + value: "0", + data: iface.encodeFunctionData(fn, args), + contractMethod: null, + contractInputsValues: null, + }; +} + +export function buildBatch(p: { + chainId: number; + safe: string; + name: string; + description: string; + createdAt: number; // pass Date.now() from caller + transactions: SafeTx[]; +}): SafeBatch { + return { + version: "1.0", + chainId: String(p.chainId), + createdAt: p.createdAt, + meta: { + name: p.name, + description: p.description, + txBuilderVersion: "1.16.5", + createdFromSafeAddress: utils.getAddress(p.safe), + }, + transactions: p.transactions, + }; +} diff --git a/scripts/bstock/safe-fallback.ts b/scripts/bstock/safe-fallback.ts new file mode 100644 index 000000000..93f05080b --- /dev/null +++ b/scripts/bstock/safe-fallback.ts @@ -0,0 +1,181 @@ +/** + * bStock backstop liquidation — Safe multisig FALLBACK flow (no Native swap). + * + * When the Native RFQ path is unavailable (API down, halt, weekend, thin depth), the liquidation is + * settled manually: a Safe{Wallet} multisig repays the bad debt with its OWN funds, seizes the + * bStock, and ships the raw bStock to a custody/Binance top-up address where finance offloads it on + * the CEX. + * + * This script does NOT send anything. It READS chain state and EMITS a Safe Transaction Builder + * batch JSON for the signers to review and execute. + * + * Two logical actions, four transactions in one atomic batch: + * Action 1 — fund + repay + seize: + * 1. debtUnderlying.approve(vDebt, repay) // let liquidateBorrow pull Safe funds + * 2. vDebt.liquidateBorrow(borrower, repay, vBStock) // seize vBStock to the Safe + * 3. vBStock.redeem(seizeTokens) // vBStock -> raw bStock + * Action 2 — hand off: + * 4. bStock.transfer(TARGET, seizedRaw) // raw bStock -> Binance top-up + * + * Seize amount is the on-chain truth from `Comptroller.liquidateCalculateSeizeTokens`, snapshotted at + * the current block. If the borrower's position changes before the Safe executes, REGENERATE — a + * stale `redeem(seizeTokens)` that exceeds the seized balance reverts the batch. + * + * Usage: + * RPC_URL=https://bsc-dataseed.bnbchain.org \ + * BORROWER=0x.. VBSTOCK=0x.. VDEBT=0x.. REPAY_AMOUNT=5000 TARGET=0x.. \ + * npx ts-node scripts/bstock/safe-fallback.ts + * + * Env: + * RPC_URL BSC RPC (default public dataseed) + * SAFE executing Safe (default 0xdc6E…2029) + * BORROWER (req) account to liquidate + * VBSTOCK (req) vToken market of the bStock collateral + * VDEBT (req) vToken market of the borrowed asset to repay + * REPAY_AMOUNT (req) repay amount in DEBT underlying, human units + * TARGET (req) Binance top-up / custody address to receive bStock + * — set ALLOW_PLACEHOLDER=1 to emit with a zero target (DRAFT) + * OUT output path (default out/bstock-safe-fallback.json) + */ +import { BigNumber, Contract, providers, utils } from "ethers"; +import { promises as fs } from "fs"; +import * as path from "path"; + +import { buildBatch, call } from "./lib/safe"; + +const DEFAULT_SAFE = "0xdc6E047f665c3Db94292Bb7fB412B25370db2029"; +const DEFAULT_RPC = "https://bsc-dataseed.bnbchain.org"; +const CHAIN_ID = 56; + +const ERC20_ABI = [ + "function balanceOf(address) view returns (uint256)", + "function decimals() view returns (uint8)", + "function symbol() view returns (string)", +]; +const VTOKEN_ABI = [ + "function underlying() view returns (address)", + "function comptroller() view returns (address)", + "function exchangeRateStored() view returns (uint256)", +]; +const COMPTROLLER_ABI = [ + "function getAccountLiquidity(address) view returns (uint256,uint256,uint256)", + "function closeFactorMantissa() view returns (uint256)", + "function liquidationIncentiveMantissa() view returns (uint256)", + "function liquidatorContract() view returns (address)", + "function liquidateCalculateSeizeTokens(address,address,uint256) view returns (uint256,uint256)", +]; + +const ZERO = "0x0000000000000000000000000000000000000000"; + +function env(name: string, required = true): string { + const v = process.env[name]; + if (required && !v) throw new Error(`Missing env ${name}`); + return v || ""; +} + +async function main() { + const provider = new providers.JsonRpcProvider(process.env.RPC_URL || DEFAULT_RPC); + const safe = utils.getAddress(process.env.SAFE || DEFAULT_SAFE); + const borrower = utils.getAddress(env("BORROWER")); + + const vBStock = new Contract(env("VBSTOCK"), VTOKEN_ABI, provider); + const vDebt = new Contract(env("VDEBT"), VTOKEN_ABI, provider); + const comptroller = new Contract(await vBStock.comptroller(), COMPTROLLER_ABI, provider); + const debt = new Contract(await vDebt.underlying(), ERC20_ABI, provider); + const bStock = new Contract(await vBStock.underlying(), ERC20_ABI, provider); + + const [debtDec, debtSym, bStockDec, bStockSym] = await Promise.all([ + debt.decimals(), + debt.symbol(), + bStock.decimals(), + bStock.symbol(), + ]); + const repay = utils.parseUnits(env("REPAY_AMOUNT"), debtDec); + + // --- read-only sanity checks (warn, do not block) --- + const [, , shortfall]: BigNumber[] = await comptroller.getAccountLiquidity(borrower); + if (shortfall.eq(0)) console.warn(`WARN: ${borrower} has NO shortfall right now — not liquidatable yet.`); + else console.log(`shortfall: ${utils.formatEther(shortfall)} (USD-scaled)`); + + const closeFactor: BigNumber = await comptroller.closeFactorMantissa(); + console.log(`repay: ${env("REPAY_AMOUNT")} ${debtSym} (closeFactor=${utils.formatEther(closeFactor)})`); + + // Direct liquidateBorrow is rejected (UNAUTHORIZED) when a liquidatorContract is set and the caller + // isn't it. The Safe must be that contract, or it must be unset. + const liquidatorContract: string = await comptroller.liquidatorContract(); + if (liquidatorContract !== ZERO && utils.getAddress(liquidatorContract) !== safe) { + console.warn( + `WARN: Comptroller.liquidatorContract = ${liquidatorContract} (!= Safe). ` + + `Direct liquidateBorrow from the Safe will revert UNAUTHORIZED — route through that contract instead.`, + ); + } + + const safeDebtBal: BigNumber = await debt.balanceOf(safe); + if (safeDebtBal.lt(repay)) { + console.warn( + `WARN: Safe ${safe} holds ${utils.formatUnits(safeDebtBal, debtDec)} ${debtSym} < repay ` + + `${env("REPAY_AMOUNT")} — fund the Safe before executing.`, + ); + } + + // --- seize math from on-chain truth --- + const [seizeErr, seizeTokens]: BigNumber[] = await comptroller.liquidateCalculateSeizeTokens( + vDebt.address, + vBStock.address, + repay, + ); + if (!seizeErr.eq(0)) throw new Error(`liquidateCalculateSeizeTokens error code ${seizeErr}`); + + // Raw bStock from redeeming seizeTokens, FLOORED at the current exchange rate (rate only grows, so + // transferring this floor never exceeds what we hold). + const exchangeRate: BigNumber = await vBStock.exchangeRateStored(); + const seizedRaw = seizeTokens.mul(exchangeRate).div(BigNumber.from(10).pow(18)); + console.log( + `seize: ${utils.formatUnits(seizeTokens, 8)} v${bStockSym} -> ~${utils.formatUnits(seizedRaw, bStockDec)} ` + + `${bStockSym} (floor)`, + ); + + // --- target (Binance top-up) --- + let target: string; + if (process.env.TARGET) { + target = utils.getAddress(process.env.TARGET); + } else if (process.env.ALLOW_PLACEHOLDER === "1") { + target = ZERO; + console.warn("WARN: TARGET unset — emitting DRAFT with zero address. DO NOT EXECUTE; replace tx #4 `to`/arg."); + } else { + throw new Error("Missing env TARGET (Binance top-up address). Set it, or ALLOW_PLACEHOLDER=1 for a draft."); + } + + // --- build batch --- + const txs = [ + call(debt.address, "approve(address,uint256)", [vDebt.address, repay]), + call(vDebt.address, "liquidateBorrow(address,uint256,address)", [borrower, repay, vBStock.address]), + call(vBStock.address, "redeem(uint256)", [seizeTokens]), + call(bStock.address, "transfer(address,uint256)", [target, seizedRaw]), + ]; + + const batch = buildBatch({ + chainId: CHAIN_ID, + safe, + name: `bStock fallback liquidation - ${bStockSym}`, + description: + `Repay ${utils.formatUnits(repay, debtDec)} ${debtSym} of ${borrower}, ` + + `seize ~${utils.formatUnits(seizedRaw, bStockDec)} ${bStockSym}, ship to ${target}. ` + + `Snapshot @ block ${await provider.getBlockNumber()}; regenerate if the position changed.`, + createdAt: Date.now(), + transactions: txs, + }); + + const out = process.env.OUT || path.join("out", "bstock-safe-fallback.json"); + await fs.mkdir(path.dirname(out), { recursive: true }); + await fs.writeFile(out, JSON.stringify(batch, null, 2)); + console.log(`\nwrote Safe batch (${txs.length} txs) -> ${out}`); + console.log("Import in Safe -> Apps -> Transaction Builder -> Load."); +} + +main() + .then(() => process.exit(0)) + .catch(e => { + console.error(e); + process.exit(1); + }); From 684675809e716d420fa0104918bc6ed8a52285f4 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 1 Jul 2026 14:15:33 +0530 Subject: [PATCH 04/78] refactor(bstock): drop shortfall pre-check, rename validation helper Remove the shortfall guard from the liquidation pre-flight: it was redundant with Core's liquidateBorrowAllowed and wrongly blocked forced liquidations (which liquidate healthy, zero-shortfall accounts). Keep the router allowlist and split it into _validateRouter(address). Rename the struct param p -> params and drop the now-unused NotLiquidatable error --- contracts/BStock/BStockLiquidator.sol | 79 +++++++++++++++----------- contracts/BStock/IBStockLiquidator.sol | 11 ++-- tests/hardhat/BStockLiquidator.ts | 6 +- 3 files changed, 52 insertions(+), 44 deletions(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index f9582b375..be1c7730c 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -133,12 +133,12 @@ contract BStockLiquidator is /// @inheritdoc IBStockLiquidator function liquidate( - LiquidationParams calldata p + LiquidationParams calldata params ) external override onlyOperator nonReentrant returns (uint256 usdtOut) { - _check(p); + _validateRouter(params.router); uint256 seizedBStock; - (usdtOut, seizedBStock) = _liquidate(p); - emit Liquidated(p.borrower, address(p.vBStock), p.repayAmount, seizedBStock, usdtOut, false); + (usdtOut, seizedBStock) = _liquidate(params); + emit Liquidated(params.borrower, address(params.vBStock), params.repayAmount, seizedBStock, usdtOut, false); } // --------------------------------------------------------------------- // @@ -146,16 +146,22 @@ contract BStockLiquidator is // --------------------------------------------------------------------- // /// @inheritdoc IBStockLiquidator - function flashLiquidate(LiquidationParams calldata p) external override onlyOperator nonReentrant { - _check(p); + function flashLiquidate(LiquidationParams calldata params) external override onlyOperator nonReentrant { + _validateRouter(params.router); IVToken[] memory vTokens = new IVToken[](1); - vTokens[0] = p.vDebt; + vTokens[0] = params.vDebt; uint256[] memory amounts = new uint256[](1); - amounts[0] = p.repayAmount; + amounts[0] = params.repayAmount; _flashActive = true; - comptroller.executeFlashLoan(payable(address(this)), payable(address(this)), vTokens, amounts, abi.encode(p)); + comptroller.executeFlashLoan( + payable(address(this)), + payable(address(this)), + vTokens, + amounts, + abi.encode(params) + ); _flashActive = false; } @@ -172,11 +178,11 @@ contract BStockLiquidator is if (!_flashActive) revert NoFlashInFlight(); if (initiator != address(this)) revert BadInitiator(initiator); - LiquidationParams memory p = abi.decode(param, (LiquidationParams)); - if (address(vTokens[0]) != address(p.vDebt)) revert WrongFlashAsset(); + LiquidationParams memory params = abi.decode(param, (LiquidationParams)); + if (address(vTokens[0]) != address(params.vDebt)) revert WrongFlashAsset(); // Repay was just funded by the flash loan; run the liquidation + swap. - (uint256 usdtOut, uint256 seizedBStock) = _liquidate(p); + (uint256 usdtOut, uint256 seizedBStock) = _liquidate(params); // The swap proceeds alone MUST cover principal + premium. Without this, any USDT inventory // held by the contract would silently backfill an underwater swap (a real loss), since the @@ -186,9 +192,9 @@ contract BStockLiquidator is if (usdtOut < repayAmounts[0]) revert InsufficientOut(usdtOut, repayAmounts[0]); // Approve the flashed vToken to pull back principal + premium. - IERC20Upgradeable(p.vDebt.underlying()).forceApprove(address(vTokens[0]), repayAmounts[0]); + IERC20Upgradeable(params.vDebt.underlying()).forceApprove(address(vTokens[0]), repayAmounts[0]); - emit Liquidated(p.borrower, address(p.vBStock), p.repayAmount, seizedBStock, usdtOut, true); + emit Liquidated(params.borrower, address(params.vBStock), params.repayAmount, seizedBStock, usdtOut, true); return (true, repayAmounts); } @@ -196,11 +202,11 @@ contract BStockLiquidator is // Core // // --------------------------------------------------------------------- // - /// @dev Pre-flight: router must be allowlisted and the borrower must have a shortfall. - function _check(LiquidationParams calldata p) private view { - if (!isRouter[p.router]) revert RouterNotAllowed(p.router); - (, , uint256 shortfall) = comptroller.getAccountLiquidity(p.borrower); - if (shortfall == 0) revert NotLiquidatable(p.borrower); + /// @dev Pre-flight: the swap router must be allowlisted. Liquidatability itself is not + /// pre-checked here — Core's `liquidateBorrowAllowed` already enforces it, and pre-checking + /// shortfall would wrongly block forced liquidations (which liquidate healthy accounts). + function _validateRouter(address router) private view { + if (!isRouter[router]) revert RouterNotAllowed(router); } /** @@ -209,13 +215,13 @@ contract BStockLiquidator is * -> approve router -> swap to USDT via signed calldata -> assert minOut. * A `calldata` struct from `liquidate` is copied to memory on entry; `executeOperation` * already holds it in memory (decoded from the flash callback). - * @param p Liquidation parameters. + * @param params Liquidation parameters. * @return usdtOut Debt-asset proceeds realized by the swap (reverts if below `minOut`). * @return seizedBStock Raw bStock redeemed and sold (balance delta). */ - function _liquidate(LiquidationParams memory p) private returns (uint256 usdtOut, uint256 seizedBStock) { - IERC20Upgradeable debt = IERC20Upgradeable(p.vDebt.underlying()); - IERC20Upgradeable bStock = IERC20Upgradeable(p.vBStock.underlying()); + function _liquidate(LiquidationParams memory params) private returns (uint256 usdtOut, uint256 seizedBStock) { + IERC20Upgradeable debt = IERC20Upgradeable(params.vDebt.underlying()); + IERC20Upgradeable bStock = IERC20Upgradeable(params.vBStock.underlying()); // 1. Repay the borrow, seizing the bStock vToken to this contract. // Core has a POOL-WIDE liquidator gate: if `liquidatorContract` is set, a direct @@ -223,33 +229,38 @@ contract BStockLiquidator is // Venus Liquidator (it pulls our repay and sends us our share of the seized collateral, // treasury keeps a cut). If the gate is unset, we liquidate directly. Either way the // seized amount is read as a BALANCE DELTA, so the Liquidator's cut is handled correctly. - uint256 vBefore = p.vBStock.balanceOf(address(this)); + uint256 vBefore = params.vBStock.balanceOf(address(this)); address gate = comptroller.liquidatorContract(); if (gate == address(0)) { - debt.forceApprove(address(p.vDebt), p.repayAmount); - uint256 errCode = p.vDebt.liquidateBorrow(p.borrower, p.repayAmount, p.vBStock); + debt.forceApprove(address(params.vDebt), params.repayAmount); + uint256 errCode = params.vDebt.liquidateBorrow(params.borrower, params.repayAmount, params.vBStock); if (errCode != 0) revert LiquidateBorrowFailed(errCode); } else { - debt.forceApprove(gate, p.repayAmount); - ILiquidator(gate).liquidateBorrow(address(p.vDebt), p.borrower, p.repayAmount, p.vBStock); + debt.forceApprove(gate, params.repayAmount); + ILiquidator(gate).liquidateBorrow( + address(params.vDebt), + params.borrower, + params.repayAmount, + params.vBStock + ); } - uint256 seizedV = p.vBStock.balanceOf(address(this)) - vBefore; + uint256 seizedV = params.vBStock.balanceOf(address(this)) - vBefore; // 2. Redeem the seized vBStock for raw bStock. Measure by DELTA so any pre-existing bStock // (dust or a stray transfer) is excluded — we only sell what this redeem actually returned. uint256 rawBefore = bStock.balanceOf(address(this)); - uint256 redeemErr = p.vBStock.redeem(seizedV); + uint256 redeemErr = params.vBStock.redeem(seizedV); if (redeemErr != 0) revert RedeemFailed(redeemErr); seizedBStock = bStock.balanceOf(address(this)) - rawBefore; // 3. Sell the bStock to USDT via the allowlisted Native router (MM-signed calldata). uint256 usdtBefore = debt.balanceOf(address(this)); - bStock.forceApprove(p.router, seizedBStock); - (bool ok, ) = p.router.call(p.swapCalldata); + bStock.forceApprove(params.router, seizedBStock); + (bool ok, ) = params.router.call(params.swapCalldata); if (!ok) revert SwapFailed(); - bStock.forceApprove(p.router, 0); // never leave a standing approval + bStock.forceApprove(params.router, 0); // never leave a standing approval usdtOut = debt.balanceOf(address(this)) - usdtBefore; - if (usdtOut < p.minOut) revert InsufficientOut(usdtOut, p.minOut); + if (usdtOut < params.minOut) revert InsufficientOut(usdtOut, params.minOut); } } diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol index 7ad20693c..382e8c2ca 100644 --- a/contracts/BStock/IBStockLiquidator.sol +++ b/contracts/BStock/IBStockLiquidator.sol @@ -52,9 +52,6 @@ interface IBStockLiquidator { /// @notice Thrown when the supplied swap router is not allowlisted. error RouterNotAllowed(address router); - /// @notice Thrown when the borrower has no shortfall (nothing to liquidate). - error NotLiquidatable(address borrower); - /// @notice Thrown when `vDebt.liquidateBorrow` returns a non-zero error code. error LiquidateBorrowFailed(uint256 errCode); @@ -98,13 +95,13 @@ interface IBStockLiquidator { /// @notice Liquidate using the contract's own debt-asset (USDT) inventory. /// @dev The contract must already hold >= `repayAmount` of `vDebt.underlying()`. /// Profit (proceeds - repay) stays in the contract; withdraw it with `sweep`. - /// @param p Liquidation parameters (borrower, markets, repay, router, signed swap calldata, minOut). + /// @param params Liquidation parameters (borrower, markets, repay, router, signed swap calldata, minOut). /// @return usdtOut Debt-asset proceeds realized by the swap. - function liquidate(LiquidationParams calldata p) external returns (uint256 usdtOut); + function liquidate(LiquidationParams calldata params) external returns (uint256 usdtOut); /// @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 p Liquidation parameters (borrower, markets, repay, router, signed swap calldata, minOut). - function flashLiquidate(LiquidationParams calldata p) external; + /// @param params Liquidation parameters (borrower, markets, repay, router, signed swap calldata, minOut). + function flashLiquidate(LiquidationParams calldata params) external; } diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index 2479fac5e..ceb20095f 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -6,7 +6,7 @@ import { ethers, upgrades } from "hardhat"; // Atomic BStockLiquidator — exercised against the BStock mocks. Covers both funding modes // (inventory + Venus-style flash loan), the seize->redeem->sell pipeline, the pool-gate routing // (with and without a treasury cut), the admin surface (operator/router/sweep/init), and every -// guard (operator gating, router allowlist, shortfall, minOut, and the flash-callback checks). +// guard (operator gating, router allowlist, minOut, and the flash-callback checks). // Position state is "manipulated" through the mock comptroller's shortfall and seize incentive. const U = (n: string) => ethers.utils.parseUnits(n, 18); @@ -239,9 +239,9 @@ describe("BStockLiquidator (atomic)", () => { ); }); - it("rejects a healthy (no-shortfall) borrower", async () => { + it("no longer pre-blocks a zero-shortfall borrower (supports forced liquidations)", async () => { await comptroller.setShortfall(0); - await expect(liq.connect(owner).liquidate(params())).to.be.revertedWithCustomError(liq, "NotLiquidatable"); + await expect(liq.connect(owner).liquidate(params())).to.emit(liq, "Liquidated"); }); it("enforces minOut", async () => { From a1ffe53a1cfa8b30dc5a39c4d10ac68b04553efd Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 1 Jul 2026 16:11:47 +0530 Subject: [PATCH 05/78] refactor(bstock): always route liquidation through the gate The Core liquidator gate is always configured on target networks, so drop the direct-liquidateBorrow fallback and always route through the Venus Liquidator, guarding against an unset gate. Removes the now-unused LiquidateBorrowFailed error; tests set the gate by default. --- contracts/BStock/BStockLiquidator.sol | 28 +++++++++----------------- contracts/BStock/IBStockLiquidator.sol | 3 --- tests/hardhat/BStockAtomicLiquidate.ts | 4 ++++ tests/hardhat/BStockLiquidator.ts | 26 ++++++++++-------------- 4 files changed, 25 insertions(+), 36 deletions(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index be1c7730c..1fa5a020a 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -224,26 +224,18 @@ contract BStockLiquidator is IERC20Upgradeable bStock = IERC20Upgradeable(params.vBStock.underlying()); // 1. Repay the borrow, seizing the bStock vToken to this contract. - // Core has a POOL-WIDE liquidator gate: if `liquidatorContract` is set, a direct - // `vToken.liquidateBorrow` reverts UNAUTHORIZED, so we route the repay through that - // Venus Liquidator (it pulls our repay and sends us our share of the seized collateral, - // treasury keeps a cut). If the gate is unset, we liquidate directly. Either way the - // seized amount is read as a BALANCE DELTA, so the Liquidator's cut is handled correctly. + // Core has a POOL-WIDE liquidator gate (`liquidatorContract`), which is always configured on + // the networks this contract targets. While it is set, a direct `vToken.liquidateBorrow` + // reverts UNAUTHORIZED, so we route the repay through that Venus Liquidator (it pulls our + // repay and sends us our share of the seized collateral; treasury keeps a cut). The seized + // amount is read as a BALANCE DELTA, so the Liquidator's cut is handled correctly. We guard + // against an unset gate so a misconfig fails loudly instead of silently no-op'ing a call to + // address(0) (a low-level call to a codeless address returns success). uint256 vBefore = params.vBStock.balanceOf(address(this)); address gate = comptroller.liquidatorContract(); - if (gate == address(0)) { - debt.forceApprove(address(params.vDebt), params.repayAmount); - uint256 errCode = params.vDebt.liquidateBorrow(params.borrower, params.repayAmount, params.vBStock); - if (errCode != 0) revert LiquidateBorrowFailed(errCode); - } else { - debt.forceApprove(gate, params.repayAmount); - ILiquidator(gate).liquidateBorrow( - address(params.vDebt), - params.borrower, - params.repayAmount, - params.vBStock - ); - } + ensureNonzeroAddress(gate); + debt.forceApprove(gate, params.repayAmount); + ILiquidator(gate).liquidateBorrow(address(params.vDebt), params.borrower, params.repayAmount, params.vBStock); uint256 seizedV = params.vBStock.balanceOf(address(this)) - vBefore; // 2. Redeem the seized vBStock for raw bStock. Measure by DELTA so any pre-existing bStock diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol index 382e8c2ca..a28bace98 100644 --- a/contracts/BStock/IBStockLiquidator.sol +++ b/contracts/BStock/IBStockLiquidator.sol @@ -52,9 +52,6 @@ interface IBStockLiquidator { /// @notice Thrown when the supplied swap router is not allowlisted. error RouterNotAllowed(address router); - /// @notice Thrown when `vDebt.liquidateBorrow` returns a non-zero error code. - error LiquidateBorrowFailed(uint256 errCode); - /// @notice Thrown when `vBStock.redeem` returns a non-zero error code. error RedeemFailed(uint256 errCode); diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index 30c08c8d9..ca5c1bf14 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -84,6 +84,10 @@ describe("bStock atomic liquidation script", () => { vDebt = await (await ethers.getContractFactory("MockVTokenDebt")).deploy(usdt.address, comptroller.address); router = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); + // Core's pool-wide liquidator gate is always configured; every liquidation routes through it. + const venusLiq = await (await ethers.getContractFactory("MockVenusLiquidator")).deploy(); + await comptroller.setLiquidatorContract(venusLiq.address); + liq = await deployLiquidator(comptroller.address); await liq.connect(owner).setRouter(router.address, true); diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index ceb20095f..52cf7350e 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -16,7 +16,7 @@ const INCENTIVE = U("1.1"); // 10% — mock seizes repay * 1.1 describe("BStockLiquidator (atomic)", () => { let owner: any, operator: any, borrower: any, stranger: any; let usdt: Contract, bStock: Contract; - let comptroller: Contract, vBStock: Contract, vDebt: Contract, router: Contract; + let comptroller: Contract, vBStock: Contract, vDebt: Contract, router: Contract, venusLiq: Contract; let liq: Contract; const REPAY = U("5000"); @@ -38,6 +38,11 @@ describe("BStockLiquidator (atomic)", () => { vDebt = await (await ethers.getContractFactory("MockVTokenDebt")).deploy(usdt.address, comptroller.address); router = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); + // Core's pool-wide liquidator gate is always configured on the networks this contract targets, + // so every liquidation routes through the Venus Liquidator. Default treasury cut = 0. + venusLiq = await (await ethers.getContractFactory("MockVenusLiquidator")).deploy(); + await comptroller.setLiquidatorContract(venusLiq.address); + liq = await deployLiquidator(comptroller.address); await liq.connect(owner).setRouter(router.address, true); @@ -106,9 +111,7 @@ describe("BStockLiquidator (atomic)", () => { await expect(liq.connect(operator).liquidate(params())).to.emit(liq, "Liquidated"); }); - it("routes the repay through the Venus Liquidator when the pool gate is set", async () => { - const venusLiq = await (await ethers.getContractFactory("MockVenusLiquidator")).deploy(); - await comptroller.setLiquidatorContract(venusLiq.address); + it("routes the repay through the Venus Liquidator (the pool gate)", async () => { await usdt.mint(liq.address, REPAY); await expect(liq.connect(owner).liquidate(params())) @@ -120,9 +123,7 @@ describe("BStockLiquidator (atomic)", () => { }); it("handles the Venus Liquidator treasury cut via delta accounting (sells only what was credited)", async () => { - const venusLiq = await (await ethers.getContractFactory("MockVenusLiquidator")).deploy(); await venusLiq.setTreasuryCut(U("0.1")); // liquidator keeps back 10% of the seized collateral - await comptroller.setLiquidatorContract(venusLiq.address); await usdt.mint(liq.address, REPAY); const seizedAfterCut = SEIZED.mul(9).div(10); // 4950 credited to the contract @@ -161,8 +162,10 @@ describe("BStockLiquidator (atomic)", () => { // Contract keeps proceeds minus principal minus premium; no inventory was used. expect(await usdt.balanceOf(liq.address)).to.equal(OUT.sub(REPAY).sub(premium)); - // vToken received the liquidateBorrow repay AND the flash repayment (principal + premium). - expect(await usdt.balanceOf(vDebt.address)).to.equal(REPAY.mul(2).add(premium)); + // The liquidateBorrow repay went to the gated Venus Liquidator. + expect(await usdt.balanceOf(venusLiq.address)).to.equal(REPAY); + // vToken received only the flash repayment (principal + premium). + expect(await usdt.balanceOf(vDebt.address)).to.equal(REPAY.add(premium)); }); it("reverts the whole tx (flash unwinds) when the sale underdelivers", async () => { @@ -249,13 +252,6 @@ describe("BStockLiquidator (atomic)", () => { await expect(liq.connect(owner).liquidate(params())).to.be.revertedWithCustomError(liq, "InsufficientOut"); }); - it("bubbles up a non-zero liquidateBorrow error code", async () => { - await vDebt.setLiquidateError(99); - await expect(liq.connect(owner).liquidate(params())) - .to.be.revertedWithCustomError(liq, "LiquidateBorrowFailed") - .withArgs(99); - }); - it("bubbles up a non-zero redeem error code", async () => { await vBStock.setRedeemError(7); await expect(liq.connect(owner).liquidate(params())) From 7df4df2dbf8b695e23118e2e16f4befb80f639e3 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 1 Jul 2026 16:26:36 +0530 Subject: [PATCH 06/78] refactor(bstock): drop redundant flash guard, harden sweep Remove _flashActive: the FlashLoanFacet passes the executeFlashLoan caller as initiator, and only flashLiquidate calls it, so initiator == this already proves the callback is ours. Saves two SSTOREs plus an SLOAD and drops the NoFlashInFlight error. Add zero-address guards to sweep for a clear revert. --- contracts/BStock/BStockLiquidator.sol | 14 ++++++-------- contracts/BStock/IBStockLiquidator.sol | 3 --- tests/hardhat/BStockLiquidator.ts | 19 ++++++++----------- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index 1fa5a020a..9695cfc12 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -45,7 +45,7 @@ import { IBStockLiquidator } from "./IBStockLiquidator.sol"; * - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator). * - the swap target must be allowlisted (`isRouter`) — defends the low-level `router.call(swapCalldata)`. * - the bStock approval to the router is the exact seized amount, reset to 0 after the swap. - * - `executeOperation` accepts calls only from the Comptroller, mid-flight (`_flashActive`), with `initiator == this`. + * - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started). * - realized USDT must clear `minOut` or the tx reverts. * * Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`. @@ -74,11 +74,8 @@ contract BStockLiquidator is /// @notice Routers allowed as the swap target (defends the low-level call). mapping(address => bool) public isRouter; - /// @dev True only while one of our own flash loans is mid-flight. - bool private _flashActive; - /// @dev Reserved storage to allow new state variables in future upgrades without layout clashes. - uint256[49] private __gap; + uint256[50] private __gap; modifier onlyOperator() { if (msg.sender != owner() && !isOperator[msg.sender]) revert NotOperator(); @@ -123,6 +120,8 @@ contract BStockLiquidator is /// @inheritdoc IBStockLiquidator function sweep(address token, address to, uint256 amount) external override onlyOwner { + ensureNonzeroAddress(token); + ensureNonzeroAddress(to); IERC20Upgradeable(token).safeTransfer(to, amount); emit Swept(token, to, amount); } @@ -154,7 +153,6 @@ contract BStockLiquidator is uint256[] memory amounts = new uint256[](1); amounts[0] = params.repayAmount; - _flashActive = true; comptroller.executeFlashLoan( payable(address(this)), payable(address(this)), @@ -162,7 +160,6 @@ contract BStockLiquidator is amounts, abi.encode(params) ); - _flashActive = false; } /// @inheritdoc IFlashLoanReceiver @@ -175,7 +172,8 @@ contract BStockLiquidator is bytes calldata param ) external override returns (bool success, uint256[] memory repayAmounts) { if (msg.sender != address(comptroller)) revert OnlyComptroller(); - if (!_flashActive) revert NoFlashInFlight(); + // initiator == this proves the flash was started by our own flashLiquidate: the FlashLoanFacet + // passes msg.sender (the executeFlashLoan caller) as `initiator`, and only flashLiquidate calls it. if (initiator != address(this)) revert BadInitiator(initiator); LiquidationParams memory params = abi.decode(param, (LiquidationParams)); diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol index a28bace98..9f93ed995 100644 --- a/contracts/BStock/IBStockLiquidator.sol +++ b/contracts/BStock/IBStockLiquidator.sol @@ -67,9 +67,6 @@ interface IBStockLiquidator { /// @notice Thrown when the flash-loan initiator is not this contract. error BadInitiator(address initiator); - /// @notice Thrown when `executeOperation` is called outside one of our own flash loans. - error NoFlashInFlight(); - /// @notice Thrown when the flashed asset does not match `params.vDebt`. error WrongFlashAsset(); diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index 52cf7350e..687ef96f0 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -1,4 +1,3 @@ -import { impersonateAccount, setBalance } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; import { BigNumber, Contract } from "ethers"; import { ethers, upgrades } from "hardhat"; @@ -193,16 +192,6 @@ describe("BStockLiquidator (atomic)", () => { ).to.be.revertedWithCustomError(liq, "OnlyComptroller"); }); - it("rejects the Comptroller calling outside an in-flight flash", async () => { - // msg.sender == comptroller but no flash is active (_flashActive == false). - await impersonateAccount(comptroller.address); - await setBalance(comptroller.address, U("1")); - const asComptroller = await ethers.getSigner(comptroller.address); - await expect( - liq.connect(asComptroller).executeOperation([vDebt.address], [REPAY], [0], liq.address, liq.address, "0x"), - ).to.be.revertedWithCustomError(liq, "NoFlashInFlight"); - }); - it("rejects a flash callback that reports a foreign initiator", async () => { const evil = await (await ethers.getContractFactory("MockMaliciousFlashComptroller")).deploy(); await evil.setMode(0); // BadInitiator @@ -320,6 +309,14 @@ describe("BStockLiquidator (atomic)", () => { await expect(liq.connect(stranger).sweep(usdt.address, stranger.address, U("1"))).to.be.revertedWith( "Ownable: caller is not the owner", ); + await expect(liq.connect(owner).sweep(ZERO, owner.address, U("1"))).to.be.revertedWithCustomError( + liq, + "ZeroAddressNotAllowed", + ); + await expect(liq.connect(owner).sweep(usdt.address, ZERO, U("1"))).to.be.revertedWithCustomError( + liq, + "ZeroAddressNotAllowed", + ); await expect(liq.connect(owner).sweep(usdt.address, owner.address, U("123"))) .to.emit(liq, "Swept") .withArgs(usdt.address, owner.address, U("123")); From 8db39462188f6d03fffc3b9b6f41165cfa8c6238 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 1 Jul 2026 17:02:28 +0530 Subject: [PATCH 07/78] refactor(bstock): rename usdt* vars to debt* for clarity The swap output is the generic debt asset (vDebt.underlying()), not literally USDT, so rename usdtOut/usdtBefore to debtOut/debtBefore. Naming only; the output-must-equal-debt invariant is unchanged. --- contracts/BStock/BStockLiquidator.sol | 22 +++++++++++----------- contracts/BStock/IBStockLiquidator.sol | 8 ++++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index 9695cfc12..bc2123c81 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -133,11 +133,11 @@ contract BStockLiquidator is /// @inheritdoc IBStockLiquidator function liquidate( LiquidationParams calldata params - ) external override onlyOperator nonReentrant returns (uint256 usdtOut) { + ) external override onlyOperator nonReentrant returns (uint256 debtOut) { _validateRouter(params.router); uint256 seizedBStock; - (usdtOut, seizedBStock) = _liquidate(params); - emit Liquidated(params.borrower, address(params.vBStock), params.repayAmount, seizedBStock, usdtOut, false); + (debtOut, seizedBStock) = _liquidate(params); + emit Liquidated(params.borrower, address(params.vBStock), params.repayAmount, seizedBStock, debtOut, false); } // --------------------------------------------------------------------- // @@ -180,19 +180,19 @@ contract BStockLiquidator is if (address(vTokens[0]) != address(params.vDebt)) revert WrongFlashAsset(); // Repay was just funded by the flash loan; run the liquidation + swap. - (uint256 usdtOut, uint256 seizedBStock) = _liquidate(params); + (uint256 debtOut, uint256 seizedBStock) = _liquidate(params); // The swap proceeds alone MUST cover principal + premium. Without this, any USDT inventory // held by the contract would silently backfill an underwater swap (a real loss), since the // flash repayment is pulled from the total balance, not just the swap output. repayAmounts = new uint256[](1); repayAmounts[0] = amounts[0] + premiums[0]; - if (usdtOut < repayAmounts[0]) revert InsufficientOut(usdtOut, repayAmounts[0]); + if (debtOut < repayAmounts[0]) revert InsufficientOut(debtOut, repayAmounts[0]); // Approve the flashed vToken to pull back principal + premium. IERC20Upgradeable(params.vDebt.underlying()).forceApprove(address(vTokens[0]), repayAmounts[0]); - emit Liquidated(params.borrower, address(params.vBStock), params.repayAmount, seizedBStock, usdtOut, true); + emit Liquidated(params.borrower, address(params.vBStock), params.repayAmount, seizedBStock, debtOut, true); return (true, repayAmounts); } @@ -214,10 +214,10 @@ contract BStockLiquidator is * A `calldata` struct from `liquidate` is copied to memory on entry; `executeOperation` * already holds it in memory (decoded from the flash callback). * @param params Liquidation parameters. - * @return usdtOut Debt-asset proceeds realized by the swap (reverts if below `minOut`). + * @return debtOut Debt-asset proceeds realized by the swap (reverts if below `minOut`). * @return seizedBStock Raw bStock redeemed and sold (balance delta). */ - function _liquidate(LiquidationParams memory params) private returns (uint256 usdtOut, uint256 seizedBStock) { + function _liquidate(LiquidationParams memory params) private returns (uint256 debtOut, uint256 seizedBStock) { IERC20Upgradeable debt = IERC20Upgradeable(params.vDebt.underlying()); IERC20Upgradeable bStock = IERC20Upgradeable(params.vBStock.underlying()); @@ -244,13 +244,13 @@ contract BStockLiquidator is seizedBStock = bStock.balanceOf(address(this)) - rawBefore; // 3. Sell the bStock to USDT via the allowlisted Native router (MM-signed calldata). - uint256 usdtBefore = debt.balanceOf(address(this)); + uint256 debtBefore = debt.balanceOf(address(this)); bStock.forceApprove(params.router, seizedBStock); (bool ok, ) = params.router.call(params.swapCalldata); if (!ok) revert SwapFailed(); bStock.forceApprove(params.router, 0); // never leave a standing approval - usdtOut = debt.balanceOf(address(this)) - usdtBefore; - if (usdtOut < params.minOut) revert InsufficientOut(usdtOut, params.minOut); + debtOut = debt.balanceOf(address(this)) - debtBefore; + if (debtOut < params.minOut) revert InsufficientOut(debtOut, params.minOut); } } diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol index 9f93ed995..632665aaf 100644 --- a/contracts/BStock/IBStockLiquidator.sol +++ b/contracts/BStock/IBStockLiquidator.sol @@ -32,14 +32,14 @@ interface IBStockLiquidator { /// @param vBStock The seized bStock collateral market. /// @param repayAmount Debt underlying repaid. /// @param seizedBStock Raw bStock redeemed and sold. - /// @param usdtOut Debt-asset (USDT) proceeds of the swap. + /// @param debtOut Debt-asset (USDT) 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, uint256 repayAmount, uint256 seizedBStock, - uint256 usdtOut, + uint256 debtOut, bool flash ); @@ -90,8 +90,8 @@ interface IBStockLiquidator { /// @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, router, signed swap calldata, minOut). - /// @return usdtOut Debt-asset proceeds realized by the swap. - function liquidate(LiquidationParams calldata params) external returns (uint256 usdtOut); + /// @return debtOut Debt-asset proceeds realized by the swap. + 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. From 7d9d1e04a68bdecb4045c55cc6c75a2d5bbf5231 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 1 Jul 2026 17:19:30 +0530 Subject: [PATCH 08/78] fix(bstock): deduct Venus Liquidator cut in seize precompute The repay always routes through the pool-wide Venus Liquidator, which keeps a treasury cut of the liquidation bonus, so the contract receives fewer vTokens than seizeTokens. Deduct it before sizing the RFQ quote, else the overstated amountIn makes the fixed-amount router pull revert. Add mock getters so the fork test resolves the reads. --- contracts/test/BStockLiquidationMocks.sol | 10 ++++++++ scripts/bstock/atomic-liquidate.ts | 29 +++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/contracts/test/BStockLiquidationMocks.sol b/contracts/test/BStockLiquidationMocks.sol index fc15b3256..8aaa5d0bb 100644 --- a/contracts/test/BStockLiquidationMocks.sol +++ b/contracts/test/BStockLiquidationMocks.sol @@ -70,6 +70,11 @@ contract MockComptrollerLite { return (0, (repayAmount * liquidationIncentiveMantissa) / 1e18); } + /// @dev Read by the off-chain script to size the Venus Liquidator's bonus cut. + function getEffectiveLiquidationIncentive(address, address) external view returns (uint256) { + return liquidationIncentiveMantissa; + } + /// @dev Flash lender: send principal from the (pre-funded) debt vToken, call back, pull repay. function executeFlashLoan( address payable /* onBehalf */, @@ -211,6 +216,11 @@ contract MockVenusLiquidator { treasuryCutMantissa = m; } + /// @dev Mirrors the real Venus Liquidator getter the off-chain script reads to precompute the cut. + function treasuryPercentMantissa() external view returns (uint256) { + return treasuryCutMantissa; + } + function liquidateBorrow( address vToken, address /* borrower */, diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 92efe28c5..5f347a04d 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -51,7 +51,10 @@ const COMPTROLLER_ABI = [ "function getAccountLiquidity(address) view returns (uint256,uint256,uint256)", "function liquidateCalculateSeizeTokens(address,address,uint256) view returns (uint256,uint256)", "function treasuryPercent() view returns (uint256)", + "function liquidatorContract() view returns (address)", + "function getEffectiveLiquidationIncentive(address,address) view returns (uint256)", ]; +const VENUS_LIQUIDATOR_ABI = ["function treasuryPercentMantissa() view returns (uint256)"]; function env(name: string, required = true): string { const v = process.env[name]; @@ -95,11 +98,29 @@ export async function atomicLiquidate(signer: Signer) { if (!seizeErr.eq(0)) throw new Error(`liquidateCalculateSeizeTokens error ${seizeErr}`); const exchangeRate: BigNumber = await vBStock.exchangeRateStored(); const ONE = BigNumber.from(10).pow(18); - // Core redeem routes `treasuryPercent` of the redeemed underlying to the treasury, so the contract - // ends up holding LESS than seizeTokens*rate. The quote must match what we actually hold, else the - // Native router pull (fixed amountIn) reverts. 0 today, but governance-settable. + + // The seize is routed through the pool-wide Venus Liquidator, which keeps a treasury cut of the + // liquidation BONUS (see Liquidator._splitLiquidationIncentive), so this contract receives fewer + // vTokens than `seizeTokens`. Deduct that cut, else the precomputed amount overstates our holdings + // and the fixed-amountIn router pull reverts. 0 today, but governance-settable. + let vReceived = seizeTokens; + const gate: string = await comptroller.liquidatorContract(); + if (gate !== ethers.constants.AddressZero) { + const venusLiquidator = new Contract(gate, VENUS_LIQUIDATOR_ABI, signer); + const liqTreasuryPct: BigNumber = await venusLiquidator.treasuryPercentMantissa(); + if (!liqTreasuryPct.eq(0)) { + const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); + const bonusAmount = seizeTokens.mul(totalIncentive.sub(ONE)).div(totalIncentive); + const ours = bonusAmount.mul(liqTreasuryPct).div(ONE); + vReceived = seizeTokens.sub(ours); + } + } + + // Core redeem then routes `treasuryPercent` of the redeemed underlying to the treasury, so we hold + // LESS still. The quote must match what we actually hold, else the Native router pull (fixed + // amountIn) reverts. 0 today, but governance-settable. const treasuryPercent: BigNumber = await comptroller.treasuryPercent(); - const seizedRaw = seizeTokens.mul(exchangeRate).div(ONE).mul(ONE.sub(treasuryPercent)).div(ONE); + const seizedRaw = vReceived.mul(exchangeRate).div(ONE).mul(ONE.sub(treasuryPercent)).div(ONE); const seizedHuman = ethers.utils.formatUnits(seizedRaw, bStockDec); console.log(`seize ${ethers.utils.formatUnits(seizeTokens, 8)} v${bStockSym} -> ~${seizedHuman} ${bStockSym}`); From 81fa8df81bd995224081c40dadc660aa427a3482 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 1 Jul 2026 17:44:20 +0530 Subject: [PATCH 09/78] fix(bstock): route safe-fallback batch through the Liquidator gate A direct vDebt.liquidateBorrow reverts UNAUTHORIZED while the pool gate is set, so the emitted batch was unexecutable on mainnet. Route the repay through the Venus Liquidator when set, redeem only the credited amount (net of its bonus cut), and apply the redeem treasuryPercent so the transfer never exceeds what the Safe holds. Add a driver test --- contracts/test/BStockLiquidationMocks.sol | 4 + scripts/bstock/safe-fallback.ts | 98 +++++++++++----- tests/hardhat/BStockSafeFallback.ts | 132 ++++++++++++++++++++++ 3 files changed, 203 insertions(+), 31 deletions(-) create mode 100644 tests/hardhat/BStockSafeFallback.ts diff --git a/contracts/test/BStockLiquidationMocks.sol b/contracts/test/BStockLiquidationMocks.sol index 8aaa5d0bb..8a41c99a3 100644 --- a/contracts/test/BStockLiquidationMocks.sol +++ b/contracts/test/BStockLiquidationMocks.sol @@ -49,6 +49,10 @@ contract MockComptrollerLite { shortfall = s; } + function setTreasuryPercent(uint256 p) external { + treasuryPercent = p; + } + function setLiquidatorContract(address l) external { liquidatorContract = l; } diff --git a/scripts/bstock/safe-fallback.ts b/scripts/bstock/safe-fallback.ts index 93f05080b..e24bf220a 100644 --- a/scripts/bstock/safe-fallback.ts +++ b/scripts/bstock/safe-fallback.ts @@ -9,17 +9,21 @@ * This script does NOT send anything. It READS chain state and EMITS a Safe Transaction Builder * batch JSON for the signers to review and execute. * - * Two logical actions, four transactions in one atomic batch: + * Two logical actions, four transactions in one atomic batch. The repay is routed through the pool-wide + * Venus Liquidator gate when one is set (a direct vDebt.liquidateBorrow would revert UNAUTHORIZED); when + * unset, the Safe liquidates directly: * Action 1 — fund + repay + seize: - * 1. debtUnderlying.approve(vDebt, repay) // let liquidateBorrow pull Safe funds - * 2. vDebt.liquidateBorrow(borrower, repay, vBStock) // seize vBStock to the Safe - * 3. vBStock.redeem(seizeTokens) // vBStock -> raw bStock + * 1. debtUnderlying.approve(gate | vDebt, repay) // let liquidateBorrow pull Safe funds + * 2. gate.liquidateBorrow(vDebt, borrower, repay, vBStock) // routed; or vDebt.liquidateBorrow(borrower, repay, vBStock) + * 3. vBStock.redeem(received) // the vBStock the Safe was credited -> raw bStock * Action 2 — hand off: - * 4. bStock.transfer(TARGET, seizedRaw) // raw bStock -> Binance top-up + * 4. bStock.transfer(TARGET, seizedRaw) // raw bStock -> Binance top-up * - * Seize amount is the on-chain truth from `Comptroller.liquidateCalculateSeizeTokens`, snapshotted at - * the current block. If the borrower's position changes before the Safe executes, REGENERATE — a - * stale `redeem(seizeTokens)` that exceeds the seized balance reverts the batch. + * Seize amount is the on-chain truth from `Comptroller.liquidateCalculateSeizeTokens`. When routed, the + * Liquidator keeps a treasury cut of the liquidation bonus, so the Safe is credited fewer vTokens than + * `seizeTokens`; we redeem only that credited amount (`received`). Snapshotted at the current block — if + * the borrower's position changes before the Safe executes, REGENERATE, else a stale redeem/transfer + * that exceeds the seized balance reverts the batch. * * Usage: * RPC_URL=https://bsc-dataseed.bnbchain.org \ @@ -63,7 +67,10 @@ const COMPTROLLER_ABI = [ "function liquidationIncentiveMantissa() view returns (uint256)", "function liquidatorContract() view returns (address)", "function liquidateCalculateSeizeTokens(address,address,uint256) view returns (uint256,uint256)", + "function treasuryPercent() view returns (uint256)", + "function getEffectiveLiquidationIncentive(address,address) view returns (uint256)", ]; +const LIQUIDATOR_ABI = ["function treasuryPercentMantissa() view returns (uint256)"]; const ZERO = "0x0000000000000000000000000000000000000000"; @@ -73,8 +80,7 @@ function env(name: string, required = true): string { return v || ""; } -async function main() { - const provider = new providers.JsonRpcProvider(process.env.RPC_URL || DEFAULT_RPC); +export async function buildSafeFallbackBatch(provider: providers.Provider) { const safe = utils.getAddress(process.env.SAFE || DEFAULT_SAFE); const borrower = utils.getAddress(env("BORROWER")); @@ -100,15 +106,15 @@ async function main() { const closeFactor: BigNumber = await comptroller.closeFactorMantissa(); console.log(`repay: ${env("REPAY_AMOUNT")} ${debtSym} (closeFactor=${utils.formatEther(closeFactor)})`); - // Direct liquidateBorrow is rejected (UNAUTHORIZED) when a liquidatorContract is set and the caller - // isn't it. The Safe must be that contract, or it must be unset. - const liquidatorContract: string = await comptroller.liquidatorContract(); - if (liquidatorContract !== ZERO && utils.getAddress(liquidatorContract) !== safe) { - console.warn( - `WARN: Comptroller.liquidatorContract = ${liquidatorContract} (!= Safe). ` + - `Direct liquidateBorrow from the Safe will revert UNAUTHORIZED — route through that contract instead.`, - ); - } + // Pool-wide Venus Liquidator gate: when set, a direct vDebt.liquidateBorrow from the Safe reverts + // UNAUTHORIZED, so we route the repay through that contract (its permissionless entry). When unset, + // the Safe liquidates directly. `repaySpender` is whichever pulls the repay. + const gate: string = await comptroller.liquidatorContract(); + const routed = gate !== ZERO; + const repaySpender = routed ? utils.getAddress(gate) : vDebt.address; + console.log( + routed ? `routing repay through Venus Liquidator ${gate}` : "liquidatorContract unset — liquidating directly", + ); const safeDebtBal: BigNumber = await debt.balanceOf(safe); if (safeDebtBal.lt(repay)) { @@ -119,6 +125,7 @@ async function main() { } // --- seize math from on-chain truth --- + const ONE = BigNumber.from(10).pow(18); const [seizeErr, seizeTokens]: BigNumber[] = await comptroller.liquidateCalculateSeizeTokens( vDebt.address, vBStock.address, @@ -126,10 +133,25 @@ async function main() { ); if (!seizeErr.eq(0)) throw new Error(`liquidateCalculateSeizeTokens error code ${seizeErr}`); - // Raw bStock from redeeming seizeTokens, FLOORED at the current exchange rate (rate only grows, so - // transferring this floor never exceeds what we hold). + // When routed, the Venus Liquidator keeps a treasury cut of the liquidation BONUS (see + // Liquidator._splitLiquidationIncentive), so the Safe is credited fewer vTokens than seizeTokens. + // Redeem only the credited amount, else the batch reverts. + let vReceived = seizeTokens; + if (routed) { + const liqTreasuryPct: BigNumber = await new Contract(gate, LIQUIDATOR_ABI, provider).treasuryPercentMantissa(); + if (!liqTreasuryPct.eq(0)) { + const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); + const bonusAmount = seizeTokens.mul(totalIncentive.sub(ONE)).div(totalIncentive); + const ours = bonusAmount.mul(liqTreasuryPct).div(ONE); + vReceived = seizeTokens.sub(ours); + } + } + + // Raw bStock from redeeming vReceived, after Core's redeem treasuryPercent fee, FLOORED at the current + // exchange rate (rate only grows, so transferring this floor never exceeds what we hold). const exchangeRate: BigNumber = await vBStock.exchangeRateStored(); - const seizedRaw = seizeTokens.mul(exchangeRate).div(BigNumber.from(10).pow(18)); + const treasuryPercent: BigNumber = await comptroller.treasuryPercent(); + const seizedRaw = vReceived.mul(exchangeRate).div(ONE).mul(ONE.sub(treasuryPercent)).div(ONE); console.log( `seize: ${utils.formatUnits(seizeTokens, 8)} v${bStockSym} -> ~${utils.formatUnits(seizedRaw, bStockDec)} ` + `${bStockSym} (floor)`, @@ -147,10 +169,14 @@ async function main() { } // --- build batch --- + const liquidateTx = routed + ? call(gate, "liquidateBorrow(address,address,uint256,address)", [vDebt.address, borrower, repay, vBStock.address]) + : call(vDebt.address, "liquidateBorrow(address,uint256,address)", [borrower, repay, vBStock.address]); + const txs = [ - call(debt.address, "approve(address,uint256)", [vDebt.address, repay]), - call(vDebt.address, "liquidateBorrow(address,uint256,address)", [borrower, repay, vBStock.address]), - call(vBStock.address, "redeem(uint256)", [seizeTokens]), + call(debt.address, "approve(address,uint256)", [repaySpender, repay]), + liquidateTx, + call(vBStock.address, "redeem(uint256)", [vReceived]), call(bStock.address, "transfer(address,uint256)", [target, seizedRaw]), ]; @@ -166,6 +192,13 @@ async function main() { transactions: txs, }); + return { batch, txs, routed, gate, seizeTokens, vReceived, seizedRaw, target }; +} + +async function main() { + const provider = new providers.JsonRpcProvider(process.env.RPC_URL || DEFAULT_RPC); + const { batch, txs } = await buildSafeFallbackBatch(provider); + const out = process.env.OUT || path.join("out", "bstock-safe-fallback.json"); await fs.mkdir(path.dirname(out), { recursive: true }); await fs.writeFile(out, JSON.stringify(batch, null, 2)); @@ -173,9 +206,12 @@ async function main() { console.log("Import in Safe -> Apps -> Transaction Builder -> Load."); } -main() - .then(() => process.exit(0)) - .catch(e => { - console.error(e); - process.exit(1); - }); +// Only run when executed directly (not when imported by a test). +if (require.main === module) { + main() + .then(() => process.exit(0)) + .catch(e => { + console.error(e); + process.exit(1); + }); +} diff --git a/tests/hardhat/BStockSafeFallback.ts b/tests/hardhat/BStockSafeFallback.ts new file mode 100644 index 000000000..07ca770fa --- /dev/null +++ b/tests/hardhat/BStockSafeFallback.ts @@ -0,0 +1,132 @@ +// Drives the off-chain Safe fallback batch generator (scripts/bstock/safe-fallback.ts) against the +// BStock mocks and asserts the emitted Transaction Builder batch. Covers the gate routing (T1), the +// Venus Liquidator bonus-cut deduction, and the redeem treasuryPercent fee (T2). +import { expect } from "chai"; +import { Contract } from "ethers"; +import { ethers } from "hardhat"; + +import { buildSafeFallbackBatch } from "../../scripts/bstock/safe-fallback"; + +const U = (n: string) => ethers.utils.parseUnits(n, 18); +const ONE = U("1"); +const REPAY = U("5000"); +const INCENTIVE = U("1.1"); +const SEIZE = REPAY.mul(INCENTIVE).div(ONE); // 5500 vBStock at the mock's 1.1x incentive + +// liquidateBorrow selectors: 4-arg (routed, ILiquidator) vs 3-arg (direct, VBep20). +const SEL_ROUTED = ethers.utils.id("liquidateBorrow(address,address,uint256,address)").slice(0, 10); +const SEL_DIRECT = ethers.utils.id("liquidateBorrow(address,uint256,address)").slice(0, 10); + +describe("BStock safe-fallback batch generator", () => { + let owner: any, borrower: any, target: any; + let usdt: Contract, bStock: Contract; + let comptroller: Contract, vBStock: Contract, vDebt: Contract, venusLiq: Contract; + + async function deploy() { + [owner, borrower, target] = await ethers.getSigners(); + + const ERC20 = await ethers.getContractFactory("MockMintableERC20"); + usdt = await ERC20.deploy("Tether", "USDT", 18); + bStock = await ERC20.deploy("Tesla bStock", "TSLAB", 18); + + comptroller = await (await ethers.getContractFactory("MockComptrollerLite")).deploy(); + vBStock = await ( + await ethers.getContractFactory("MockVTokenCollateral") + ).deploy(bStock.address, comptroller.address); + vDebt = await (await ethers.getContractFactory("MockVTokenDebt")).deploy(usdt.address, comptroller.address); + venusLiq = await (await ethers.getContractFactory("MockVenusLiquidator")).deploy(); + + await comptroller.setShortfall(U("800")); + await comptroller.setLiquidatorContract(venusLiq.address); // gate set: the mainnet-like default + } + + function setEnv(over: Record = {}) { + Object.assign(process.env, { + SAFE: owner.address, + BORROWER: borrower.address, + VBSTOCK: vBStock.address, + VDEBT: vDebt.address, + REPAY_AMOUNT: "5000", + TARGET: target.address, + ...over, + }); + } + + beforeEach(async () => { + await deploy(); + // wipe any env leakage between tests + for (const k of ["SAFE", "BORROWER", "VBSTOCK", "VDEBT", "REPAY_AMOUNT", "TARGET", "ALLOW_PLACEHOLDER"]) { + delete process.env[k]; + } + }); + + it("routes the repay through the Venus Liquidator gate (T1)", async () => { + setEnv(); + const { txs, routed, gate, vReceived, seizedRaw } = await buildSafeFallbackBatch(ethers.provider); + + expect(routed).to.equal(true); + expect(ethers.utils.getAddress(gate)).to.equal(venusLiq.address); + expect(txs).to.have.length(4); + + // 1. approve → the GATE (not vDebt) is the repay spender + expect(ethers.utils.getAddress(txs[0].to)).to.equal(usdt.address); + const approve = ethers.utils.defaultAbiCoder.decode(["address", "uint256"], "0x" + txs[0].data.slice(10)); + expect(approve[0]).to.equal(venusLiq.address); + expect(approve[1]).to.equal(REPAY); + + // 2. liquidateBorrow → the gate, 4-arg selector + expect(ethers.utils.getAddress(txs[1].to)).to.equal(venusLiq.address); + expect(txs[1].data.slice(0, 10)).to.equal(SEL_ROUTED); + + // 3. redeem → the credited amount (cut = 0 here, so == full seize) + expect(ethers.utils.getAddress(txs[2].to)).to.equal(vBStock.address); + const redeem = ethers.utils.defaultAbiCoder.decode(["uint256"], txs[2].data.slice(0, 2) + txs[2].data.slice(10)); + expect(redeem[0]).to.equal(SEIZE); + expect(vReceived).to.equal(SEIZE); + + // 4. transfer raw bStock to the target + expect(ethers.utils.getAddress(txs[3].to)).to.equal(bStock.address); + const xfer = ethers.utils.defaultAbiCoder.decode(["address", "uint256"], "0x" + txs[3].data.slice(10)); + expect(xfer[0]).to.equal(target.address); + expect(xfer[1]).to.equal(seizedRaw); + expect(seizedRaw).to.equal(SEIZE); // cut 0, treasuryPercent 0 → full seize at 1:1 rate + }); + + it("falls back to a direct liquidateBorrow when the gate is unset", async () => { + await comptroller.setLiquidatorContract(ethers.constants.AddressZero); + setEnv(); + const { txs, routed } = await buildSafeFallbackBatch(ethers.provider); + + expect(routed).to.equal(false); + // approve spender is vDebt, liquidateBorrow is the 3-arg VBep20 form on vDebt + const approve = ethers.utils.defaultAbiCoder.decode(["address", "uint256"], "0x" + txs[0].data.slice(10)); + expect(approve[0]).to.equal(vDebt.address); + expect(ethers.utils.getAddress(txs[1].to)).to.equal(vDebt.address); + expect(txs[1].data.slice(0, 10)).to.equal(SEL_DIRECT); + }); + + it("deducts the Liquidator bonus-cut so redeem matches what the Safe is credited", async () => { + await venusLiq.setTreasuryCut(U("0.1")); // 10% of the bonus + setEnv(); + const { vReceived, txs } = await buildSafeFallbackBatch(ethers.provider); + + // ours = seize·(incentive-1)/incentive · cut ; theirs = seize - ours + const bonusAmount = SEIZE.mul(INCENTIVE.sub(ONE)).div(INCENTIVE); + const ours = bonusAmount.mul(U("0.1")).div(ONE); + const expected = SEIZE.sub(ours); + + expect(vReceived).to.equal(expected); + const redeem = ethers.utils.defaultAbiCoder.decode(["uint256"], txs[2].data.slice(0, 2) + txs[2].data.slice(10)); + expect(redeem[0]).to.equal(expected); + }); + + it("applies the redeem treasuryPercent to the transferred amount (T2)", async () => { + await comptroller.setTreasuryPercent(U("0.2")); // 20% redeem fee to treasury + setEnv(); + const { seizedRaw } = await buildSafeFallbackBatch(ethers.provider); + + // seizedRaw = seize · rate(1:1) · (1 - treasuryPercent) + const expected = SEIZE.mul(ONE.sub(U("0.2"))).div(ONE); + expect(seizedRaw).to.equal(expected); + }); +}); From 14729b771c9f047829c6457ae8d399235c13c5ab Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 1 Jul 2026 19:21:19 +0530 Subject: [PATCH 10/78] docs(bstock): generalize debt-asset wording, drop stale comments --- contracts/BStock/BStockLiquidator.sol | 47 +++++++++++++------------- contracts/BStock/IBStockLiquidator.sol | 6 ++-- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index bc2123c81..cb62648e2 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -10,10 +10,10 @@ import { ensureNonzeroAddress } from "@venusprotocol/solidity-utilities/contract import { VToken } from "../Tokens/VTokens/VToken.sol"; import { IFlashLoanReceiver } from "../FlashLoan/interfaces/IFlashLoanReceiver.sol"; -// Shared Venus interfaces: IComptroller (Core diamond — gate, account liquidity, flash loan), -// IVBep20 (the bStock/debt market surface), IVToken (flash-loan asset array element), and ILiquidator -// (the pool-wide Venus Liquidator gate that pulls the repay and returns our share of the seized collateral). -import { IComptroller, IVBep20, IVToken, ILiquidator } from "../InterfacesV8.sol"; +// Shared Venus interfaces: IComptroller (Core diamond — liquidator gate + flash loan), IVToken +// (flash-loan asset array element), and ILiquidator (the pool-wide Venus Liquidator gate that pulls +// the repay and returns our share of the seized collateral). +import { IComptroller, IVToken, ILiquidator } from "../InterfacesV8.sol"; import { IBStockLiquidator } from "./IBStockLiquidator.sol"; /** @@ -22,13 +22,13 @@ import { IBStockLiquidator } from "./IBStockLiquidator.sol"; * @notice Atomic backstop liquidator for bStock (ERC-8056 tokenized stock) collateral. * * In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken, - * redeems it to the raw bStock, and sells that bStock to USDT through the Native RFQ router - * using a pre-fetched, MM-signed firm-quote `txRequest`. Because seize and sell happen in the - * same tx there is no price-drift window, and the realized USDT must clear `minOut` or the whole - * call reverts — the protocol never ends up holding the RFQ-only asset. + * redeems it to the raw bStock, and sells that bStock to the debt asset through the Native RFQ + * router using a pre-fetched, MM-signed firm-quote `txRequest`. Because seize and sell + * happen in the same tx there is no price-drift window, and the realized debt-asset amount must clear + * `minOut` or the whole call reverts — the protocol never ends up holding the RFQ-only asset. * * Two funding modes share the same core (`_liquidate`): - * - INVENTORY: the contract is pre-funded with the debt asset (USDT) and repays from its own balance. + * - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance. * - FLASH: the debt asset is flash-borrowed from Venus (`Comptroller.executeFlashLoan`) and repaid * (+ premium) within the same tx; no capital is locked in the contract. * @@ -36,7 +36,7 @@ import { IBStockLiquidator } from "./IBStockLiquidator.sol"; * `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock * liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus * path with their own funds and their own offload. This contract is intentionally gated because it - * custodies funds (USDT inventory / flash principal) and forwards a CALLER-SUPPLIED calldata blob to + * custodies funds (debt-asset inventory / flash principal) and forwards a CALLER-SUPPLIED calldata blob to * an external router — the swap's recipient (`to`) lives inside that calldata, so an open entrypoint * would let anyone route the proceeds to themselves and drain the contract. The router allowlist and * `minOut` bound the blast radius but cannot replace operator-gating. @@ -46,14 +46,15 @@ import { IBStockLiquidator } from "./IBStockLiquidator.sol"; * - the swap target must be allowlisted (`isRouter`) — defends the low-level `router.call(swapCalldata)`. * - the bStock approval to the router is the exact seized amount, reset to 0 after the swap. * - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started). - * - realized USDT must clear `minOut` or the tx reverts. + * - the realized debt-asset amount must clear `minOut` or the tx reverts. * - * Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`. - * When it is set, a direct `vToken.liquidateBorrow` from an arbitrary caller reverts UNAUTHORIZED, so this - * contract reads the gate at call time and routes the repay through that Venus Liquidator (which is the - * permissionless entry anyone may call); when the gate is unset it liquidates directly. Routing through the - * gate needs no governance change, and no other Core market is affected. Note: setting THIS contract as `liquidatorContract` is - * NOT an option — the gate is pool-wide, so every other market's liquidations would be forced through here. + * Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`, + * which is always configured on the networks this contract targets. While it is set, a direct + * `vToken.liquidateBorrow` from an arbitrary caller reverts UNAUTHORIZED, so this contract reads the gate + * at call time and routes the repay through that Venus Liquidator (the permissionless entry anyone may + * call), reverting if the gate is ever unset. Routing through the gate needs no governance change, and no + * other Core market is affected. Note: setting THIS contract as `liquidatorContract` is NOT an option — + * the gate is pool-wide, so every other market's liquidations would be forced through here. */ contract BStockLiquidator is Ownable2StepUpgradeable, @@ -63,8 +64,8 @@ contract BStockLiquidator is { using SafeERC20Upgradeable for IERC20Upgradeable; - /// @notice Core Comptroller (diamond): reads the liquidation gate / account liquidity and provides - /// the flash loan via `executeFlashLoan`. + /// @notice Core Comptroller (diamond): reads the liquidation gate and provides the flash loan + /// via `executeFlashLoan`. /// @custom:oz-upgrades-unsafe-allow state-variable-immutable IComptroller public immutable comptroller; @@ -170,7 +171,7 @@ contract BStockLiquidator is address initiator, address /* onBehalf */, bytes calldata param - ) external override returns (bool success, uint256[] memory repayAmounts) { + ) external override returns (bool, uint256[] memory repayAmounts) { if (msg.sender != address(comptroller)) revert OnlyComptroller(); // initiator == this proves the flash was started by our own flashLiquidate: the FlashLoanFacet // passes msg.sender (the executeFlashLoan caller) as `initiator`, and only flashLiquidate calls it. @@ -182,7 +183,7 @@ contract BStockLiquidator is // Repay was just funded by the flash loan; run the liquidation + swap. (uint256 debtOut, uint256 seizedBStock) = _liquidate(params); - // The swap proceeds alone MUST cover principal + premium. Without this, any USDT inventory + // The swap proceeds alone MUST cover principal + premium. Without this, any debt-asset inventory // held by the contract would silently backfill an underwater swap (a real loss), since the // flash repayment is pulled from the total balance, not just the swap output. repayAmounts = new uint256[](1); @@ -210,7 +211,7 @@ contract BStockLiquidator is /** * @dev The atomic sequence shared by both funding modes: * approve debt -> liquidateBorrow (seize vBStock) -> redeem (raw bStock) - * -> approve router -> swap to USDT via signed calldata -> assert minOut. + * -> approve router -> swap to the debt asset via signed calldata -> assert minOut. * A `calldata` struct from `liquidate` is copied to memory on entry; `executeOperation` * already holds it in memory (decoded from the flash callback). * @param params Liquidation parameters. @@ -243,7 +244,7 @@ contract BStockLiquidator is if (redeemErr != 0) revert RedeemFailed(redeemErr); seizedBStock = bStock.balanceOf(address(this)) - rawBefore; - // 3. Sell the bStock to USDT via the allowlisted Native router (MM-signed calldata). + // 3. Sell the bStock to the debt asset via the allowlisted Native router (MM-signed calldata). uint256 debtBefore = debt.balanceOf(address(this)); bStock.forceApprove(params.router, seizedBStock); (bool ok, ) = params.router.call(params.swapCalldata); diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol index 632665aaf..7b405fde4 100644 --- a/contracts/BStock/IBStockLiquidator.sol +++ b/contracts/BStock/IBStockLiquidator.sol @@ -18,7 +18,7 @@ interface IBStockLiquidator { uint256 repayAmount; // debt underlying to repay (its own decimals) address router; // Native router = firm-quote txRequest.target (must be allowlisted) bytes swapCalldata; // firm-quote txRequest.calldata (MM-signed order) - uint256 minOut; // minimum debt-asset (USDT) the swap must yield, else revert + uint256 minOut; // minimum debt-asset amount the swap must yield, else revert } /// @notice Emitted when an operator is allowlisted or removed. @@ -32,7 +32,7 @@ interface IBStockLiquidator { /// @param vBStock The seized bStock collateral market. /// @param repayAmount Debt underlying repaid. /// @param seizedBStock Raw bStock redeemed and sold. - /// @param debtOut Debt-asset (USDT) proceeds of the swap. + /// @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, @@ -86,7 +86,7 @@ interface IBStockLiquidator { /// @param amount Amount to withdraw. function sweep(address token, address to, uint256 amount) external; - /// @notice Liquidate using the contract's own debt-asset (USDT) inventory. + /// @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, router, signed swap calldata, minOut). From 08eff13e4a0c4630988189f2f820814afef07305 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 11:59:40 +0530 Subject: [PATCH 11/78] fix(bstock): quote the debt asset, assert it matches Native output atomic-liquidate hardcoded the firm-quote output to USDT while the contract measures proceeds and enforces minOut in vDebt.underlying(). A non-USDT debt market would then fail with a cryptic on-chain InsufficientOut(0, minOut) revert. Request the quote in the debt underlying and fail fast off-chain when it isn't the Native output token (USDT on BSC, the only bStock quote asset), pointing operators at the Safe fallback for non-USDT debt. --- scripts/bstock/atomic-liquidate.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 5f347a04d..2cd71c16c 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -124,8 +124,13 @@ export async function atomicLiquidate(signer: Signer) { const seizedHuman = ethers.utils.formatUnits(seizedRaw, bStockDec); console.log(`seize ${ethers.utils.formatUnits(seizeTokens, 8)} v${bStockSym} -> ~${seizedHuman} ${bStockSym}`); - // 3. firm-quote, taker = the LIQUIDATOR CONTRACT (so it can submit + receive USDT). - const usdtAddr = process.env.USDT_ADDR || BSC_USDT; + // 3. firm-quote, taker = the LIQUIDATOR CONTRACT (so it can submit + receive the debt asset). + // The contract measures swap proceeds in the DEBT asset and enforces `minOut` in it, so the quote + // output token MUST be the debt underlying, not a hardcoded USDT. Native only quotes bStock->USDT on + // BSC (every bStock pair in the live orderbook is *<->USDT, no other quote token), so the debt market + // has to be USDT for this atomic path. Assert that up front instead of eating a cryptic on-chain + // InsufficientOut(0, minOut) revert; non-USDT debt is settled off-chain via the Safe fallback. + const nativeOut = ethers.utils.getAddress(process.env.USDT_ADDR || BSC_USDT); let router: string; let swapCalldata: string; let amountOut: BigNumber; @@ -137,10 +142,17 @@ export async function atomicLiquidate(signer: Signer) { swapCalldata = data; amountOut = BigNumber.from(process.env.MOCK_OUT || "0"); } else { + if (debt.address.toLowerCase() !== nativeOut.toLowerCase()) { + throw new Error( + `debt underlying ${debtSym} (${debt.address}) is not the Native RFQ output token (${nativeOut}). ` + + `Native only quotes bStock->USDT and the contract requires swap output == debt asset. ` + + `Use a USDT debt market, or the Safe fallback (safe-fallback.ts) for non-USDT debt.`, + ); + } const q = await getFirmQuote({ fromAddress: liquidator.address, tokenIn: bStock.address, - tokenOut: usdtAddr, + tokenOut: debt.address, amount: seizedHuman, slippage, }); From d17b5ba98f1ab8fcc783c494ad3cc7dcf7cdb430 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 12:21:13 +0530 Subject: [PATCH 12/78] refactor(bstock): resolve bStock token from orderbook, drop hardcoded map The smoke test hardcoded a two-entry TSLAB/NVDAB address map, but Native lists many more bStock tokens (SPCXB, CRCLB, MSTRB, ...) and keeps adding them. Resolve the token address from the live orderbook instead, so the script stays correct with no edits as the listing grows. --- scripts/bstock/lib/native.ts | 6 ------ scripts/bstock/native-quote.ts | 14 +++++++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/scripts/bstock/lib/native.ts b/scripts/bstock/lib/native.ts index 6e7fc43d0..49cd1a1d5 100644 --- a/scripts/bstock/lib/native.ts +++ b/scripts/bstock/lib/native.ts @@ -136,10 +136,4 @@ export function quoteDeadline(q: NativeFirmQuote): number { return ds.length ? Math.min(...ds) : 0; } -/** Known bStock token addresses on BNB Chain (18 decimals). */ -export const BSTOCK_TOKENS = { - TSLAB: "0x5b1910eAaD6450E50f816082Aa078C41F10C292f", - NVDAB: "0x02Fca66C1D1aFB4E2A7884261eB00F63598a7436", -} as const; - export const BSC_USDT = "0x55d398326f99059fF775485246999027B3197955"; diff --git a/scripts/bstock/native-quote.ts b/scripts/bstock/native-quote.ts index ccb8a1675..2bcdf1091 100644 --- a/scripts/bstock/native-quote.ts +++ b/scripts/bstock/native-quote.ts @@ -12,26 +12,30 @@ * * Env: * NATIVE_API_KEY (required) Native Swap API key - * TOKEN bStock symbol to quote: TSLAB | NVDAB (default TSLAB) + * TOKEN bStock symbol to quote, any listed in the orderbook (default TSLAB) * AMOUNT amount of bStock to sell, human units (default 1) * FROM taker address (default 0x..dEaD) */ -import { BSC_USDT, BSTOCK_TOKENS, getFirmQuote, getOrderbook, quoteDeadline } from "./lib/native"; +import { BSC_USDT, getFirmQuote, getOrderbook, quoteDeadline } from "./lib/native"; async function main() { - const symbol = (process.env.TOKEN || "TSLAB") as keyof typeof BSTOCK_TOKENS; - const tokenIn = BSTOCK_TOKENS[symbol]; - if (!tokenIn) throw new Error(`Unknown TOKEN ${symbol}; use TSLAB or NVDAB`); + const symbol = process.env.TOKEN || "TSLAB"; const amount = process.env.AMOUNT || "1"; const from = process.env.FROM || "0x000000000000000000000000000000000000dEaD"; + // Resolve the token address from the live orderbook (the source of truth) rather than a hardcoded + // map, so this stays correct as Native lists more bStock tokens. `tokenIn` is the sell-side base + // (base = symbol, quote = USDT). console.log(`\nOrderbook (bsc) entries for ${symbol}:`); const ob = await getOrderbook("bsc"); + let tokenIn: string | undefined; for (const r of ob) { if (`${r.base_symbol}${r.quote_symbol}`.includes(symbol)) { console.log(` ${r.base_symbol} <-> ${r.quote_symbol}`); } + if (r.base_symbol === symbol && r.quote_symbol === "USDT") tokenIn = r.base_address; } + if (!tokenIn) throw new Error(`No ${symbol}<->USDT pair in the Native bsc orderbook`); console.log(`\nFirm quote: ${amount} ${symbol} -> USDT`); const q = await getFirmQuote({ fromAddress: from, tokenIn, tokenOut: BSC_USDT, amount, slippage: 0.5 }); From 896dde9217cdac324bb20c83679a47ee41cf0afc Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 12:26:52 +0530 Subject: [PATCH 13/78] refactor(bstock): rename native-quote smoke test to native-smoke It is a read only diagnostic, not an operational script. Rename so it is visibly distinct from the two runnable scripts (atomic-liquidate, safe-fallback) and update its usage examples to the new path. --- scripts/bstock/{native-quote.ts => native-smoke.ts} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename scripts/bstock/{native-quote.ts => native-smoke.ts} (96%) diff --git a/scripts/bstock/native-quote.ts b/scripts/bstock/native-smoke.ts similarity index 96% rename from scripts/bstock/native-quote.ts rename to scripts/bstock/native-smoke.ts index 2bcdf1091..0523d1720 100644 --- a/scripts/bstock/native-quote.ts +++ b/scripts/bstock/native-smoke.ts @@ -5,10 +5,10 @@ * firm-quote so we can eyeball price, spread, TTL and the returned txRequest. * * Usage: - * NATIVE_API_KEY=... npx hardhat run scripts/bstock/native-quote.ts + * NATIVE_API_KEY=... npx hardhat run scripts/bstock/native-smoke.ts * * Options (env): - * NATIVE_API_KEY=... TOKEN=NVDAB AMOUNT=5 npx hardhat run scripts/bstock/native-quote.ts + * NATIVE_API_KEY=... TOKEN=NVDAB AMOUNT=5 npx hardhat run scripts/bstock/native-smoke.ts * * Env: * NATIVE_API_KEY (required) Native Swap API key From 1ae2c47d3cd998d8db7cd8d45546cdcf1d7105bd Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 13:36:05 +0530 Subject: [PATCH 14/78] feat(bstock): support non-USDT debt via two-hop swap - Native RFQ only quotes bStock->USDT on BSC, so only USDT-debt markets could be liquidated atomically; non-USDT ERC20 debt (BTCB, ETH, CAKE) was pushed to the manual Safe fallback. - Add an optional second hop (intermediate -> debt) through a second allowlisted router. A single final minOut in the debt asset covers the whole chain; when router2 is unset the path is the original single hop, unchanged. - Keep the contract DEX-agnostic: hop 2 is an opaque router call guarded by the allowlist + minOut, so V2/V3/aggregator is a purely off-chain choice. The script sources hop 2 from a pluggable provider (KyberSwap default, OpenOcean, or a local PancakeSwap V2 encode for CI). - The two-hop settle must be submitted via a private RPC: hop 2 is a public-mempool swap, so minOut bounds loss while the private RPC removes sandwich-induced reverts. - LiquidationParams gains router2/swapCalldata2/intermediateToken, changing the liquidate/flashLiquidate selectors. Pre-launch, and the only consumer (the off-chain script) is updated in lockstep. --- contracts/BStock/BStockLiquidator.sol | 78 +++++++++-- contracts/BStock/IBStockLiquidator.sol | 26 +++- scripts/bstock/atomic-liquidate.ts | 109 ++++++++++----- scripts/bstock/lib/amm.ts | 150 +++++++++++++++++++++ tests/hardhat/BStockAtomicLiquidate.ts | 44 ++++++ tests/hardhat/BStockLiquidator.ts | 114 ++++++++++++++++ tests/hardhat/Fork/BStockLiquidatorFork.ts | 4 + 7 files changed, 476 insertions(+), 49 deletions(-) create mode 100644 scripts/bstock/lib/amm.ts diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index cb62648e2..b97e67092 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -22,10 +22,13 @@ import { IBStockLiquidator } from "./IBStockLiquidator.sol"; * @notice Atomic backstop liquidator for bStock (ERC-8056 tokenized stock) collateral. * * In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken, - * redeems it to the raw bStock, and sells that bStock to the debt asset through the Native RFQ - * router using a pre-fetched, MM-signed firm-quote `txRequest`. Because seize and sell - * happen in the same tx there is no price-drift window, and the realized debt-asset amount must clear - * `minOut` or the whole call reverts — the protocol never ends up holding the RFQ-only asset. + * redeems it to the raw bStock, and sells that bStock to the debt asset in one or two hops. Hop 1 + * always sells bStock through the Native RFQ router using a pre-fetched, MM-signed firm-quote + * `txRequest`. For USDT debt that single hop lands the debt asset directly. For non-USDT debt an + * OPTIONAL hop 2 (Native quotes bStock->USDT only) converts the USDT to the debt asset through a + * second allowlisted router (an AMM/aggregator). Because seize and sell happen in the same tx there + * is no price-drift window, and the realized debt-asset amount must clear `minOut` or the whole call + * reverts — the protocol never ends up holding the RFQ-only asset or the intermediate. * * Two funding modes share the same core (`_liquidate`): * - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance. @@ -43,8 +46,11 @@ import { IBStockLiquidator } from "./IBStockLiquidator.sol"; * * Security model: * - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator). - * - the swap target must be allowlisted (`isRouter`) — defends the low-level `router.call(swapCalldata)`. - * - the bStock approval to the router is the exact seized amount, reset to 0 after the swap. + * - BOTH swap targets (`router` and, when set, `router2`) must be allowlisted (`isRouter`) — defends + * the low-level `router.call(swapCalldata)` on each hop. + * - the approval to each router is the exact amount being sold on that hop, reset to 0 afterwards + * (bStock on hop 1; the measured intermediate balance delta on hop 2, so pre-existing inventory + * is never exposed). * - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started). * - the realized debt-asset amount must clear `minOut` or the tx reverts. * @@ -136,6 +142,7 @@ contract BStockLiquidator is LiquidationParams calldata params ) external override onlyOperator nonReentrant returns (uint256 debtOut) { _validateRouter(params.router); + if (params.router2 != address(0)) _validateRouter(params.router2); uint256 seizedBStock; (debtOut, seizedBStock) = _liquidate(params); emit Liquidated(params.borrower, address(params.vBStock), params.repayAmount, seizedBStock, debtOut, false); @@ -148,6 +155,7 @@ contract BStockLiquidator is /// @inheritdoc IBStockLiquidator function flashLiquidate(LiquidationParams calldata params) external override onlyOperator nonReentrant { _validateRouter(params.router); + if (params.router2 != address(0)) _validateRouter(params.router2); IVToken[] memory vTokens = new IVToken[](1); vTokens[0] = params.vDebt; @@ -208,14 +216,24 @@ contract BStockLiquidator is if (!isRouter[router]) revert RouterNotAllowed(router); } + /// @dev One swap hop: approve the exact `amount` to the allowlisted `router`, forward the opaque + /// calldata via a low-level call, then reset the approval to 0. The approval caps what the + /// router can pull; if the calldata sells less, the remainder stays as inventory. + function _swap(IERC20Upgradeable token, address router, bytes memory data, uint256 amount) private { + token.forceApprove(router, amount); + (bool ok, ) = router.call(data); + if (!ok) revert SwapFailed(); + token.forceApprove(router, 0); // never leave a standing approval + } + /** * @dev The atomic sequence shared by both funding modes: * approve debt -> liquidateBorrow (seize vBStock) -> redeem (raw bStock) - * -> approve router -> swap to the debt asset via signed calldata -> assert minOut. + * -> swap bStock to the debt asset (one hop, or two via an intermediate) -> assert minOut. * A `calldata` struct from `liquidate` is copied to memory on entry; `executeOperation` * already holds it in memory (decoded from the flash callback). * @param params Liquidation parameters. - * @return debtOut Debt-asset proceeds realized by the swap (reverts if below `minOut`). + * @return debtOut Debt-asset proceeds realized by the swap chain (reverts if below `minOut`). * @return seizedBStock Raw bStock redeemed and sold (balance delta). */ function _liquidate(LiquidationParams memory params) private returns (uint256 debtOut, uint256 seizedBStock) { @@ -244,12 +262,46 @@ contract BStockLiquidator is if (redeemErr != 0) revert RedeemFailed(redeemErr); seizedBStock = bStock.balanceOf(address(this)) - rawBefore; - // 3. Sell the bStock to the debt asset via the allowlisted Native router (MM-signed calldata). + // 3. Sell the bStock to the debt asset (one hop, or two via an intermediate) and assert minOut. + // Extracted into `_sellToDebt` to keep this frame within the EVM stack limit. + debtOut = _sellToDebt(debt, bStock, seizedBStock, params); + } + + /** + * @dev Sell `seizedBStock` to the debt asset and enforce `minOut`. Single hop by default + * (bStock -> debt via the Native router). When `params.router2` is set, two hops + * (bStock -> intermediate -> debt): hop 1 sells bStock to the intermediate (USDT) via the + * Native router, hop 2 converts that intermediate to the debt asset via a second allowlisted + * router (AMM/aggregator). `minOut` is measured in the debt asset across the whole chain. + * @return debtOut Debt-asset proceeds (balance delta), reverting if below `minOut`. + */ + function _sellToDebt( + IERC20Upgradeable debt, + IERC20Upgradeable bStock, + uint256 seizedBStock, + LiquidationParams memory params + ) private returns (uint256 debtOut) { uint256 debtBefore = debt.balanceOf(address(this)); - bStock.forceApprove(params.router, seizedBStock); - (bool ok, ) = params.router.call(params.swapCalldata); - if (!ok) revert SwapFailed(); - bStock.forceApprove(params.router, 0); // never leave a standing approval + if (params.router2 == address(0)) { + _swap(bStock, params.router, params.swapCalldata, seizedBStock); + } else { + // The intermediate must be a real token distinct from both endpoints: if it equals `debt`, + // hop 1 would inflate the balance `debtBefore` snapshots against (breaking the proceeds + // delta); if it equals `bStock`, the hop-1 sell shrinks the balance and the midDelta + // subtraction underflows. + if ( + params.intermediateToken == address(0) || + params.intermediateToken == address(debt) || + params.intermediateToken == address(bStock) + ) revert InvalidIntermediate(); + + IERC20Upgradeable mid = IERC20Upgradeable(params.intermediateToken); + uint256 midBefore = mid.balanceOf(address(this)); + _swap(bStock, params.router, params.swapCalldata, seizedBStock); // hop 1: bStock -> intermediate + // Only the hop-1 proceeds are sold onward; any pre-existing intermediate inventory is excluded. + uint256 midDelta = mid.balanceOf(address(this)) - midBefore; + _swap(mid, params.router2, params.swapCalldata2, midDelta); // hop 2: intermediate -> debt + } debtOut = debt.balanceOf(address(this)) - debtBefore; if (debtOut < params.minOut) revert InsufficientOut(debtOut, params.minOut); diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol index 7b405fde4..19eb7e1b1 100644 --- a/contracts/BStock/IBStockLiquidator.sol +++ b/contracts/BStock/IBStockLiquidator.sol @@ -11,14 +11,23 @@ import { IVBep20 } from "../InterfacesV8.sol"; /// 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. 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) uint256 repayAmount; // debt underlying to repay (its own decimals) - address router; // Native router = firm-quote txRequest.target (must be allowlisted) - bytes swapCalldata; // firm-quote txRequest.calldata (MM-signed order) - uint256 minOut; // minimum debt-asset amount the swap must yield, else revert + address router; // hop-1 router = Native firm-quote txRequest.target (must be allowlisted) + bytes swapCalldata; // hop-1 calldata (MM-signed Native 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 } /// @notice Emitted when an operator is allowlisted or removed. @@ -61,6 +70,9 @@ interface IBStockLiquidator { /// @notice Thrown when swap proceeds are below `minOut`. error InsufficientOut(uint256 got, uint256 minOut); + /// @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(); @@ -89,13 +101,15 @@ interface IBStockLiquidator { /// @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, router, signed swap calldata, minOut). - /// @return debtOut Debt-asset proceeds realized by the swap. + /// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final + /// minOut, 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, router, signed swap calldata, minOut). + /// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final + /// minOut, and the optional hop-2 router/calldata/intermediate for non-USDT debt). function flashLiquidate(LiquidationParams calldata params) external; } diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 2cd71c16c..adfa89d49 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -1,16 +1,22 @@ /** * bStock ATOMIC liquidation — drives the on-chain `BStockLiquidator` contract. * - * One tx: the contract repays the borrow, seizes + redeems the bStock, and sells it to - * USDT via a pre-fetched Native firm-quote. This script does the OFF-CHAIN half (precompute - * the seize amount, fetch the MM-signed firm-quote with `from_address = the contract`) and - * then calls `liquidate` (inventory mode) or `flashLiquidate` (Venus flash-loan mode). + * One tx: the contract repays the borrow, seizes + redeems the bStock, and sells it to the debt + * asset in one or two hops. Hop 1 is always a pre-fetched Native firm-quote (bStock -> USDT). For a + * USDT debt market that single hop is the debt asset; for a non-USDT debt market a second hop + * (USDT -> debt via an allowlisted AMM/aggregator, see lib/amm.ts) is appended. This script does the + * OFF-CHAIN half (precompute the seize, fetch the quotes with `from_address = the contract`) and then + * calls `liquidate` (inventory mode) or `flashLiquidate` (Venus flash-loan mode). * * 1. Comptroller.liquidateCalculateSeizeTokens(vDebt, vBStock, repay) -> seize vTokens * 2. seizeTokens * vBStock.exchangeRateStored() / 1e18 -> raw bStock (floor) - * 3. Native firm-quote (from_address = LIQUIDATOR contract) -> txRequest + amountOut + * 3. Native firm-quote (bStock -> USDT) [+ AMM quote USDT -> debt] -> router(s) + calldata + out * 4. BStockLiquidator.liquidate / flashLiquidate(params) -> atomic settle * + * IMPORTANT: for a two-hop run the settle tx MUST be submitted through Venus's PRIVATE RPC, never a + * public one — hop 2 is a public-mempool AMM swap and `minOut` only bounds loss, not sandwich-induced + * reverts. Prefer MODE=flash for two-hop so a revert only burns gas, not locked inventory. + * * Usage: * NATIVE_API_KEY=... LIQUIDATOR=0x.. BORROWER=0x.. VBSTOCK=0x.. VDEBT=0x.. REPAY_AMOUNT=5000 \ * npx hardhat run scripts/bstock/atomic-liquidate.ts --network bscmainnet @@ -24,17 +30,22 @@ * MODE "inventory" (default) | "flash" * SLIPPAGE Native slippage %, default 0.5 * MIN_OUT_BUFFER extra haircut on minOut beyond slippage, default 0.5 (%) + * AMM_PROVIDER hop-2 route source for non-USDT debt: kyberswap (default) | openocean | pcsv2 * DRY_RUN "1" -> callStatic only, send nothing - * MOCK_NATIVE router addr + calldata source for fork tests (see below) + * MOCK_NATIVE hop-1 "router:calldata" for fork/local tests (see below) + * MOCK_AMM hop-2 "router:calldata" for fork/local tests (two-hop); MOCK_OUT = final debt out */ import { BigNumber, Contract, Signer } from "ethers"; import { ethers } from "hardhat"; +import { getAmmSwap } from "./lib/amm"; import { BSC_USDT, getFirmQuote, quoteDeadline } from "./lib/native"; +const PARAMS_TUPLE = + "(address borrower,address vDebt,address vBStock,uint256 repayAmount,address router,bytes swapCalldata,uint256 minOut,address router2,bytes swapCalldata2,address intermediateToken)"; const LIQUIDATOR_ABI = [ - "function liquidate((address borrower,address vDebt,address vBStock,uint256 repayAmount,address router,bytes swapCalldata,uint256 minOut)) returns (uint256)", - "function flashLiquidate((address borrower,address vDebt,address vBStock,uint256 repayAmount,address router,bytes swapCalldata,uint256 minOut))", + `function liquidate(${PARAMS_TUPLE}) returns (uint256)`, + `function flashLiquidate(${PARAMS_TUPLE})`, "function isRouter(address) view returns (bool)", ]; const VTOKEN_ABI = [ @@ -124,35 +135,42 @@ export async function atomicLiquidate(signer: Signer) { const seizedHuman = ethers.utils.formatUnits(seizedRaw, bStockDec); console.log(`seize ${ethers.utils.formatUnits(seizeTokens, 8)} v${bStockSym} -> ~${seizedHuman} ${bStockSym}`); - // 3. firm-quote, taker = the LIQUIDATOR CONTRACT (so it can submit + receive the debt asset). - // The contract measures swap proceeds in the DEBT asset and enforces `minOut` in it, so the quote - // output token MUST be the debt underlying, not a hardcoded USDT. Native only quotes bStock->USDT on - // BSC (every bStock pair in the live orderbook is *<->USDT, no other quote token), so the debt market - // has to be USDT for this atomic path. Assert that up front instead of eating a cryptic on-chain - // InsufficientOut(0, minOut) revert; non-USDT debt is settled off-chain via the Safe fallback. + // 3. Build the swap, taker = the LIQUIDATOR CONTRACT (so it can submit + receive the debt asset). + // The contract measures proceeds in the DEBT asset and enforces `minOut` in it. Native only quotes + // bStock->USDT on BSC (every bStock pair in the live orderbook is *<->USDT), so: + // - USDT debt -> single hop: Native bStock->USDT is already the debt asset. + // - other debt -> two hops: Native bStock->USDT (hop 1), then USDT->debt via an allowlisted + // AMM/aggregator (hop 2, see lib/amm.ts). `minOut` is in the debt asset + // across the whole chain; `amountOut` below is the FINAL debt out. const nativeOut = ethers.utils.getAddress(process.env.USDT_ADDR || BSC_USDT); + const twoHop = debt.address.toLowerCase() !== nativeOut.toLowerCase(); + let router: string; let swapCalldata: string; - let amountOut: BigNumber; + let amountOut: BigNumber; // final debt-asset out (drives minOut) + let router2 = ethers.constants.AddressZero; + let swapCalldata2 = "0x"; + let intermediateToken = ethers.constants.AddressZero; if (process.env.MOCK_NATIVE) { - // Fork-test path: MOCK_NATIVE = ":" pre-encoded against a MockNativeRouter. + // Fork/local-test path: MOCK_NATIVE = ":" (hop 1), optional MOCK_AMM = same for + // hop 2, both pre-encoded against a MockNativeRouter. MOCK_OUT is the FINAL debt out. const [r, data] = process.env.MOCK_NATIVE.split(":"); router = ethers.utils.getAddress(r); swapCalldata = data; amountOut = BigNumber.from(process.env.MOCK_OUT || "0"); - } else { - if (debt.address.toLowerCase() !== nativeOut.toLowerCase()) { - throw new Error( - `debt underlying ${debtSym} (${debt.address}) is not the Native RFQ output token (${nativeOut}). ` + - `Native only quotes bStock->USDT and the contract requires swap output == debt asset. ` + - `Use a USDT debt market, or the Safe fallback (safe-fallback.ts) for non-USDT debt.`, - ); + if (process.env.MOCK_AMM) { + const [r2, data2] = process.env.MOCK_AMM.split(":"); + router2 = ethers.utils.getAddress(r2); + swapCalldata2 = data2; + intermediateToken = nativeOut; } + } else { + // Hop 1: Native RFQ always outputs USDT (bStock pairs only with USDT on BSC). const q = await getFirmQuote({ fromAddress: liquidator.address, tokenIn: bStock.address, - tokenOut: debt.address, + tokenOut: nativeOut, amount: seizedHuman, slippage, }); @@ -160,14 +178,42 @@ export async function atomicLiquidate(signer: Signer) { if (ttl <= 0) throw new Error("quote already expired — refetch"); router = q.txRequest.target; swapCalldata = q.txRequest.calldata; - amountOut = BigNumber.from(q.amountOut); - console.log( - `Native quote: ${seizedHuman} ${bStockSym} -> ${ethers.utils.formatUnits(amountOut, debtDec)} ${debtSym} (TTL ${ttl}s, router ${router})`, - ); + const midOut = BigNumber.from(q.amountOut); // USDT out of hop 1 + + if (twoHop) { + // Hop 2: convert the hop-1 USDT to the (non-USDT) debt asset via an allowlisted AMM/aggregator. + const amm = await getAmmSwap( + { + tokenIn: nativeOut, + tokenOut: debt.address, + amountIn: midOut.toString(), + recipient: liquidator.address, + slippage, + }, + ethers.provider, + ); + router2 = ethers.utils.getAddress(amm.router); + swapCalldata2 = amm.calldata; + intermediateToken = nativeOut; + amountOut = BigNumber.from(amm.expectedOut); + console.log( + `Native: ${seizedHuman} ${bStockSym} -> ${ethers.utils.formatUnits(midOut, 18)} USDT (TTL ${ttl}s, ${router}); ` + + `AMM: -> ${ethers.utils.formatUnits(amountOut, debtDec)} ${debtSym} (${router2})`, + ); + } else { + amountOut = midOut; + console.log( + `Native quote: ${seizedHuman} ${bStockSym} -> ${ethers.utils.formatUnits(amountOut, debtDec)} ${debtSym} (TTL ${ttl}s, router ${router})`, + ); + } } - if (!(await liquidator.isRouter(router))) { - throw new Error(`router ${router} is not allowlisted on the liquidator — call setRouter first`); + // Both hop routers must be allowlisted on the liquidator (the low-level call is defended by isRouter). + const routers = router2 !== ethers.constants.AddressZero ? [router, router2] : [router]; + for (const r of routers) { + if (!(await liquidator.isRouter(r))) { + throw new Error(`router ${r} is not allowlisted on the liquidator — call setRouter first`); + } } // minOut = amountOut minus an extra safety buffer on top of the quote slippage. @@ -181,6 +227,9 @@ export async function atomicLiquidate(signer: Signer) { router, swapCalldata, minOut, + router2, + swapCalldata2, + intermediateToken, }; // Inventory mode spends the contract's own debt-asset balance; warn early if it can't cover the diff --git a/scripts/bstock/lib/amm.ts b/scripts/bstock/lib/amm.ts new file mode 100644 index 000000000..5d6fd4bbf --- /dev/null +++ b/scripts/bstock/lib/amm.ts @@ -0,0 +1,150 @@ +/** + * AMM / aggregator client for the OPTIONAL second swap hop (intermediate -> debt). + * + * Hop 1 of an atomic bStock liquidation always sells bStock -> USDT through the Native RFQ router + * (Native quotes bStock -> USDT only on BSC). For a non-USDT debt market the contract needs a second + * hop that converts that USDT to the debt asset (BTCB, ETH, CAKE, ...). The on-chain contract forwards + * an OPAQUE calldata blob to an allowlisted router and enforces its own `minOut` in the debt asset, so + * it is DEX-agnostic: this module just returns `{ router, calldata, expectedOut }` from whichever + * provider is configured. + * + * Provider is selected by env `AMM_PROVIDER`: + * - "kyberswap" (default) — keyless aggregator; routes/route-build return router + calldata + out. + * - "openocean" — keyless (free tier) aggregator; /swap returns to + data + outAmount. + * - "pcsv2" — offline/CI fallback: encode PancakeSwap V2 `swapExactTokensForTokens` + * locally; expected out from the router's `getAmountsOut` view. + * + * The returned `router` MUST be allowlisted on the liquidator (`setRouter`), and the swap recipient + * encoded inside `calldata` is the liquidator contract, so the debt asset lands where the on-chain + * `minOut` check reads it. The contract's `minOut` is the source of truth; a provider's internal + * min-out (baked into the calldata from `slippage`) is only a secondary bound. + */ +import { BigNumber, Contract, providers, utils } from "ethers"; + +// PancakeSwap V2 router on BSC mainnet (used only by the "pcsv2" offline provider / its default path). +export const PCS_V2_ROUTER = "0x10ED43C718714eb63d5aA57B78B54704E256024E"; +export const BSC_WBNB = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"; + +export interface AmmSwapParams { + chain?: string; // default "bsc" + tokenIn: string; // intermediate (USDT) + tokenOut: string; // debt asset + amountIn: string; // wei of tokenIn to sell (the exact hop-1 output) + recipient: string; // where the debt asset must land (the liquidator contract) + slippage: number; // percent, e.g. 0.5 +} + +export interface AmmSwap { + router: string; // the `to` to allowlist + forward calldata to + calldata: string; // opaque swap calldata blob + expectedOut: string; // wei of tokenOut expected (used to derive the contract minOut) +} + +function provider(): string { + return (process.env.AMM_PROVIDER || "kyberswap").toLowerCase(); +} + +/** Resolve the hop-2 swap for the configured provider. `rpc` is required only by the "pcsv2" path. */ +export async function getAmmSwap(p: AmmSwapParams, rpc?: providers.Provider): Promise { + switch (provider()) { + case "kyberswap": + return kyberswap(p); + case "openocean": + return openocean(p); + case "pcsv2": + return pcsv2(p, rpc); + default: + throw new Error(`unknown AMM_PROVIDER "${process.env.AMM_PROVIDER}" (use kyberswap|openocean|pcsv2)`); + } +} + +/** KyberSwap aggregator: GET /routes (best route + amountOut) then POST /route/build (executable tx). */ +async function kyberswap(p: AmmSwapParams): Promise { + const chain = p.chain || "bsc"; + const clientId = process.env.KYBER_CLIENT_ID || "venus-bstock-liquidator"; + const base = process.env.KYBER_API_BASE || `https://aggregator-api.kyberswap.com/${chain}/api/v1`; + + const qs = new URLSearchParams({ tokenIn: p.tokenIn, tokenOut: p.tokenOut, amountIn: p.amountIn }).toString(); + const rRes = await fetch(`${base}/routes?${qs}`, { headers: { "x-client-id": clientId } }); + const rBody: any = await rRes.json(); + if (rBody.code !== 0 || !rBody.data?.routeSummary) { + throw new Error(`KyberSwap /routes failed: ${rBody.message || JSON.stringify(rBody).slice(0, 200)}`); + } + + const bRes = await fetch(`${base}/route/build`, { + method: "POST", + headers: { "Content-Type": "application/json", "x-client-id": clientId }, + body: JSON.stringify({ + routeSummary: rBody.data.routeSummary, + sender: p.recipient, + recipient: p.recipient, + slippageTolerance: Math.round(p.slippage * 100), // percent -> bips + source: clientId, + }), + }); + const bBody: any = await bRes.json(); + if (bBody.code !== 0 || !bBody.data?.data) { + throw new Error(`KyberSwap /route/build failed: ${bBody.message || JSON.stringify(bBody).slice(0, 200)}`); + } + return { + router: utils.getAddress(bBody.data.routerAddress), + calldata: bBody.data.data, + expectedOut: String(bBody.data.amountOut ?? rBody.data.routeSummary.amountOut), + }; +} + +/** OpenOcean v4: GET /swap returns { to, data, outAmount } directly (account = recipient is required). */ +async function openocean(p: AmmSwapParams): Promise { + const chain = p.chain || "bsc"; + const base = process.env.OPENOCEAN_API_BASE || `https://open-api.openocean.finance/v4/${chain}`; + const gasPriceGwei = process.env.OPENOCEAN_GAS_GWEI || "3"; + const qs = new URLSearchParams({ + inTokenAddress: p.tokenIn, + outTokenAddress: p.tokenOut, + amountDecimals: p.amountIn, // raw wei + gasPrice: gasPriceGwei, // gwei, no decimals + slippage: String(p.slippage), + account: p.recipient, + }).toString(); + const res = await fetch(`${base}/swap?${qs}`); + const body: any = await res.json(); + if (String(body.code) !== "200" || !body.data?.data) { + throw new Error(`OpenOcean /swap failed: ${body.message || JSON.stringify(body).slice(0, 200)}`); + } + return { + router: utils.getAddress(body.data.to), + calldata: body.data.data, + expectedOut: String(body.data.outAmount), + }; +} + +/** + * Offline / CI fallback: encode PancakeSwap V2 `swapExactTokensForTokens` locally and read the + * expected out from the router's `getAmountsOut` view. Path from `AMM_PATH` (comma-separated), else + * a direct [tokenIn, tokenOut] pair; set AMM_PATH to route through WBNB when there is no direct pool. + */ +async function pcsv2(p: AmmSwapParams, rpc?: providers.Provider): Promise { + if (!rpc) throw new Error("pcsv2 AMM provider needs an RPC provider (pass one to getAmmSwap)"); + const router = utils.getAddress(process.env.AMM_ROUTER || PCS_V2_ROUTER); + const path = (process.env.AMM_PATH ? process.env.AMM_PATH.split(",") : [p.tokenIn, p.tokenOut]).map(a => + utils.getAddress(a.trim()), + ); + + const iface = new utils.Interface([ + "function swapExactTokensForTokens(uint256 amountIn,uint256 amountOutMin,address[] path,address to,uint256 deadline) returns (uint256[])", + "function getAmountsOut(uint256 amountIn,address[] path) view returns (uint256[])", + ]); + const amounts: BigNumber[] = await new Contract(router, iface, rpc).getAmountsOut(p.amountIn, path); + const expectedOut = amounts[amounts.length - 1]; + const amountOutMin = expectedOut.mul(Math.round((100 - p.slippage) * 100)).div(10000); + const deadline = Math.floor(Date.now() / 1000) + Number(process.env.AMM_DEADLINE_SECS || "1200"); + + const calldata = iface.encodeFunctionData("swapExactTokensForTokens", [ + p.amountIn, + amountOutMin, + path, + p.recipient, + deadline, + ]); + return { router, calldata, expectedOut: expectedOut.toString() }; +} diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index ca5c1bf14..f1b6ece79 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -26,6 +26,7 @@ const SCRIPT_ENV = [ "REPAY_AMOUNT", "USDT_ADDR", "MOCK_NATIVE", + "MOCK_AMM", "MOCK_OUT", "MODE", "DRY_RUN", @@ -145,4 +146,47 @@ describe("bStock atomic liquidation script", () => { await expect(atomicLiquidate(owner)).to.be.rejectedWith("not allowlisted"); expect(await usdt.balanceOf(liq.address)).to.equal(REPAY); // untouched }); + + // Two-hop (non-USDT debt): the debt is BTCB, so the script appends a hop-2 swap. Both hops are + // driven through MOCK_NATIVE (bStock->USDT) + MOCK_AMM (USDT->BTCB); MOCK_OUT is the final BTCB out. + async function deployTwoHop() { + const ERC20 = await ethers.getContractFactory("MockMintableERC20"); + const btcb = await ERC20.deploy("Bitcoin BEP20", "BTCB", 18); + const vBtcb = await (await ethers.getContractFactory("MockVTokenDebt")).deploy(btcb.address, comptroller.address); + const amm = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); + await btcb.mint(amm.address, OUT); // hop-2 router pays BTCB + const hop1 = router.interface.encodeFunctionData("swapAll", [bStock.address, usdt.address, liq.address]); + const hop2 = amm.interface.encodeFunctionData("swapAll", [usdt.address, btcb.address, liq.address]); + return { btcb, vBtcb, amm, hop1, hop2 }; + } + + it("two-hop mode: appends the AMM hop for non-USDT debt and settles via the contract", async () => { + const { btcb, vBtcb, amm, hop1, hop2 } = await deployTwoHop(); + await liq.connect(owner).setRouter(amm.address, true); + await btcb.mint(liq.address, REPAY); // debt inventory + setEnv({ + VDEBT: vBtcb.address, + MOCK_NATIVE: `${router.address}:${hop1}`, + MOCK_AMM: `${amm.address}:${hop2}`, + }); + + await atomicLiquidate(owner); + + expect(await btcb.balanceOf(liq.address)).to.equal(OUT); // final debt asset, profit kept + expect(await usdt.balanceOf(liq.address)).to.equal(0); // intermediate fully consumed + }); + + it("two-hop mode: refuses when the hop-2 router is not allowlisted", async () => { + const { btcb, vBtcb, amm, hop1, hop2 } = await deployTwoHop(); + // amm intentionally NOT allowlisted. + await btcb.mint(liq.address, REPAY); + setEnv({ + VDEBT: vBtcb.address, + MOCK_NATIVE: `${router.address}:${hop1}`, + MOCK_AMM: `${amm.address}:${hop2}`, + }); + + await expect(atomicLiquidate(owner)).to.be.rejectedWith("not allowlisted"); + expect(await btcb.balanceOf(liq.address)).to.equal(REPAY); // untouched + }); }); diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index 687ef96f0..4092e5a5a 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -72,6 +72,8 @@ describe("BStockLiquidator (atomic)", () => { return router.interface.encodeFunctionData("swapAll", [bStock.address, usdt.address, to]); } + // Default builder is SINGLE-HOP (router2 = 0): every test using params() is also the single-hop + // regression against the new optional fields. Two-hop tests fill router2/swapCalldata2/intermediate. function params(over: Partial = {}) { return { borrower: borrower.address, @@ -81,6 +83,9 @@ describe("BStockLiquidator (atomic)", () => { router: router.address, swapCalldata: swapCalldata(SEIZED, liq.address), minOut: MIN_OUT, + router2: ZERO, + swapCalldata2: "0x", + intermediateToken: ZERO, ...over, }; } @@ -185,6 +190,115 @@ describe("BStockLiquidator (atomic)", () => { }); }); + // Two-hop: non-USDT debt. Hop 1 (Native mock) sells bStock -> USDT; hop 2 (a second router mock, + // standing in for the AMM/aggregator) converts USDT -> the debt token. A single final minOut in the + // debt token covers the whole chain. Both hops at 1:1 -> SEIZED bStock => OUT debt. + describe("two-hop mode (non-USDT debt)", () => { + let btcb: Contract, vBtcb: Contract, amm: Contract; + + // hop-2 calldata against the AMM mock: swap(USDT, amountIn, BTCB, to). + function ammSwapCalldata(amountIn: BigNumber, to: string) { + return amm.interface.encodeFunctionData("swap", [usdt.address, amountIn, btcb.address, to]); + } + function ammSwapAllCalldata(to: string) { + return amm.interface.encodeFunctionData("swapAll", [usdt.address, btcb.address, to]); + } + function twoHopParams(over: Partial = {}) { + return params({ + vDebt: vBtcb.address, // debt is now BTCB, not USDT + router2: amm.address, + swapCalldata2: ammSwapCalldata(SEIZED, liq.address), + intermediateToken: usdt.address, + ...over, + }); + } + + beforeEach(async () => { + const ERC20 = await ethers.getContractFactory("MockMintableERC20"); + btcb = await ERC20.deploy("Bitcoin BEP20", "BTCB", 18); + vBtcb = await (await ethers.getContractFactory("MockVTokenDebt")).deploy(btcb.address, comptroller.address); + amm = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); + await liq.connect(owner).setRouter(amm.address, true); + // hop-1 router already holds USDT (from deploy()); hop-2 router pays out BTCB. + await btcb.mint(amm.address, OUT); + }); + + it("inventory: bStock -> USDT -> BTCB, keeps the incentive as profit", async () => { + await btcb.mint(liq.address, REPAY); // pre-funded debt inventory + + expect(await liq.connect(owner).callStatic.liquidate(twoHopParams())).to.equal(OUT); + + await expect(liq.connect(owner).liquidate(twoHopParams())) + .to.emit(liq, "Liquidated") + .withArgs(borrower.address, vBStock.address, REPAY, SEIZED, OUT, false); + + expect(await btcb.balanceOf(liq.address)).to.equal(OUT); // 5500 BTCB, profit = OUT - REPAY + expect(await usdt.balanceOf(liq.address)).to.equal(0); // intermediate fully consumed + expect(await bStock.balanceOf(liq.address)).to.equal(0); + // No standing approvals on either hop. + expect(await bStock.allowance(liq.address, router.address)).to.equal(0); + expect(await usdt.allowance(liq.address, amm.address)).to.equal(0); + }); + + it("flash: flash-borrows BTCB, two-hop settles, repays principal + premium", async () => { + await btcb.mint(vBtcb.address, REPAY); // the vToken lends the principal + await comptroller.setFlashPremium(U("0.001")); + const premium = REPAY.mul(U("0.001")).div(U("1")); + + await expect(liq.connect(owner).flashLiquidate(twoHopParams())) + .to.emit(liq, "Liquidated") + .withArgs(borrower.address, vBStock.address, REPAY, SEIZED, OUT, true); + + expect(await btcb.balanceOf(liq.address)).to.equal(OUT.sub(REPAY).sub(premium)); + expect(await btcb.balanceOf(vBtcb.address)).to.equal(REPAY.add(premium)); + expect(await usdt.balanceOf(liq.address)).to.equal(0); // intermediate fully consumed + }); + + it("enforces minOut in the debt asset when the hop-2 (AMM) rate underdelivers", async () => { + await btcb.mint(liq.address, REPAY); + await amm.setRate(U("0.9")); // 5500 USDT -> 4950 BTCB, below the 5400 minOut + await expect(liq.connect(owner).liquidate(twoHopParams())).to.be.revertedWithCustomError(liq, "InsufficientOut"); + expect(await btcb.balanceOf(liq.address)).to.equal(REPAY); // reverted, inventory intact + }); + + it("rejects a non-allowlisted hop-2 router (both entrypoints)", async () => { + await btcb.mint(liq.address, REPAY); + const p = twoHopParams({ router2: stranger.address }); + await expect(liq.connect(owner).liquidate(p)).to.be.revertedWithCustomError(liq, "RouterNotAllowed"); + await expect(liq.connect(owner).flashLiquidate(p)).to.be.revertedWithCustomError(liq, "RouterNotAllowed"); + }); + + it("reverts InvalidIntermediate when the intermediate equals debt, bStock, or zero", async () => { + await btcb.mint(liq.address, REPAY); + for (const bad of [btcb.address, bStock.address, ZERO]) { + await expect( + liq.connect(owner).liquidate(twoHopParams({ intermediateToken: bad })), + ).to.be.revertedWithCustomError(liq, "InvalidIntermediate"); + } + }); + + it("sells only the hop-1 proceeds — pre-existing intermediate inventory is excluded", async () => { + await btcb.mint(liq.address, REPAY); + await usdt.mint(liq.address, U("2000")); // stray USDT inventory + + await expect(liq.connect(owner).liquidate(twoHopParams())) + .to.emit(liq, "Liquidated") + .withArgs(borrower.address, vBStock.address, REPAY, SEIZED, OUT, false); + + expect(await usdt.balanceOf(liq.address)).to.equal(U("2000")); // stray USDT untouched + expect(await btcb.balanceOf(liq.address)).to.equal(OUT); + }); + + it("caps the hop-2 approval at the hop-1 delta (a swapAll cannot drain intermediate inventory)", async () => { + await btcb.mint(liq.address, REPAY); + await usdt.mint(liq.address, U("2000")); // inventory a greedy swapAll would try to take + // swapAll pulls the caller's ENTIRE USDT balance (7500), but the approval is capped at midDelta (5500). + const p = twoHopParams({ swapCalldata2: ammSwapAllCalldata(liq.address) }); + await expect(liq.connect(owner).liquidate(p)).to.be.revertedWithCustomError(liq, "SwapFailed"); + expect(await usdt.balanceOf(liq.address)).to.equal(U("2000")); // reverted, inventory intact + }); + }); + describe("flash callback guards (executeOperation)", () => { it("rejects a caller other than the Comptroller", async () => { await expect( diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts index 913ec6808..f3a04dac2 100644 --- a/tests/hardhat/Fork/BStockLiquidatorFork.ts +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -140,6 +140,10 @@ const test = () => { // Loose floor: the real Venus Liquidator keeps a treasury cut of the seized collateral, so // proceeds land a few % under the full redeemed value. Profit is asserted strictly below. minOut: redeemed.mul(90).div(100), + // Single hop (USDT debt): no second AMM leg. + router2: ethers.constants.AddressZero, + swapCalldata2: "0x", + intermediateToken: ethers.constants.AddressZero, }; const borrowBefore = await vDebt.connect(owner).callStatic.borrowBalanceCurrent(borrower.address); From 17717bcc08b915afabc920d1c0bea13ecb5bc029 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 14:26:59 +0530 Subject: [PATCH 15/78] feat(bstock): index debt market in Liquidated event The event named the collateral market but not the repaid debt market, so consumers couldn't filter liquidations by debt. Add vDebt as a third indexed field, emitted in both inventory and flash paths. --- contracts/BStock/BStockLiquidator.sol | 20 ++++++++++++++++++-- contracts/BStock/IBStockLiquidator.sol | 2 ++ tests/hardhat/BStockLiquidator.ts | 16 ++++++++-------- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index b97e67092..09019d422 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -145,7 +145,15 @@ contract BStockLiquidator is if (params.router2 != address(0)) _validateRouter(params.router2); uint256 seizedBStock; (debtOut, seizedBStock) = _liquidate(params); - emit Liquidated(params.borrower, address(params.vBStock), params.repayAmount, seizedBStock, debtOut, false); + emit Liquidated( + params.borrower, + address(params.vBStock), + address(params.vDebt), + params.repayAmount, + seizedBStock, + debtOut, + false + ); } // --------------------------------------------------------------------- // @@ -201,7 +209,15 @@ contract BStockLiquidator is // Approve the flashed vToken to pull back principal + premium. IERC20Upgradeable(params.vDebt.underlying()).forceApprove(address(vTokens[0]), repayAmounts[0]); - emit Liquidated(params.borrower, address(params.vBStock), params.repayAmount, seizedBStock, debtOut, true); + emit Liquidated( + params.borrower, + address(params.vBStock), + address(params.vDebt), + params.repayAmount, + seizedBStock, + debtOut, + true + ); return (true, repayAmounts); } diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol index 19eb7e1b1..2d37c28d1 100644 --- a/contracts/BStock/IBStockLiquidator.sol +++ b/contracts/BStock/IBStockLiquidator.sol @@ -39,6 +39,7 @@ interface IBStockLiquidator { /// @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. @@ -46,6 +47,7 @@ interface IBStockLiquidator { event Liquidated( address indexed borrower, address indexed vBStock, + address indexed vDebt, uint256 repayAmount, uint256 seizedBStock, uint256 debtOut, diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index 4092e5a5a..b54bd37ef 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -100,7 +100,7 @@ describe("BStockLiquidator (atomic)", () => { await expect(liq.connect(owner).liquidate(params())) .to.emit(liq, "Liquidated") - .withArgs(borrower.address, vBStock.address, REPAY, SEIZED, OUT, false); + .withArgs(borrower.address, vBStock.address, vDebt.address, REPAY, SEIZED, OUT, false); // Started with REPAY, repaid REPAY, received OUT -> ends at OUT (profit = OUT - REPAY = 500). expect(await usdt.balanceOf(liq.address)).to.equal(OUT); @@ -120,7 +120,7 @@ describe("BStockLiquidator (atomic)", () => { await expect(liq.connect(owner).liquidate(params())) .to.emit(liq, "Liquidated") - .withArgs(borrower.address, vBStock.address, REPAY, SEIZED, OUT, false); + .withArgs(borrower.address, vBStock.address, vDebt.address, REPAY, SEIZED, OUT, false); // The repay went to the gated Venus Liquidator, not to vDebt directly. expect(await usdt.balanceOf(venusLiq.address)).to.equal(REPAY); @@ -136,7 +136,7 @@ describe("BStockLiquidator (atomic)", () => { liq.connect(owner).liquidate(params({ swapCalldata: swapAllCalldata(liq.address), minOut: U("4900") })), ) .to.emit(liq, "Liquidated") - .withArgs(borrower.address, vBStock.address, REPAY, seizedAfterCut, seizedAfterCut, false); + .withArgs(borrower.address, vBStock.address, vDebt.address, REPAY, seizedAfterCut, seizedAfterCut, false); // 5000 inventory - 5000 repaid + 4950 proceeds = 4950, nothing stuck. expect(await usdt.balanceOf(liq.address)).to.equal(seizedAfterCut); @@ -149,7 +149,7 @@ describe("BStockLiquidator (atomic)", () => { // seizedBStock in the event is the DELTA (SEIZED), not SEIZED + 100. await expect(liq.connect(owner).liquidate(params())) .to.emit(liq, "Liquidated") - .withArgs(borrower.address, vBStock.address, REPAY, SEIZED, OUT, false); + .withArgs(borrower.address, vBStock.address, vDebt.address, REPAY, SEIZED, OUT, false); expect(await bStock.balanceOf(liq.address)).to.equal(U("100")); // stray untouched }); }); @@ -162,7 +162,7 @@ describe("BStockLiquidator (atomic)", () => { await expect(liq.connect(owner).flashLiquidate(params())) .to.emit(liq, "Liquidated") - .withArgs(borrower.address, vBStock.address, REPAY, SEIZED, OUT, true); + .withArgs(borrower.address, vBStock.address, vDebt.address, REPAY, SEIZED, OUT, true); // Contract keeps proceeds minus principal minus premium; no inventory was used. expect(await usdt.balanceOf(liq.address)).to.equal(OUT.sub(REPAY).sub(premium)); @@ -230,7 +230,7 @@ describe("BStockLiquidator (atomic)", () => { await expect(liq.connect(owner).liquidate(twoHopParams())) .to.emit(liq, "Liquidated") - .withArgs(borrower.address, vBStock.address, REPAY, SEIZED, OUT, false); + .withArgs(borrower.address, vBStock.address, vBtcb.address, REPAY, SEIZED, OUT, false); expect(await btcb.balanceOf(liq.address)).to.equal(OUT); // 5500 BTCB, profit = OUT - REPAY expect(await usdt.balanceOf(liq.address)).to.equal(0); // intermediate fully consumed @@ -247,7 +247,7 @@ describe("BStockLiquidator (atomic)", () => { await expect(liq.connect(owner).flashLiquidate(twoHopParams())) .to.emit(liq, "Liquidated") - .withArgs(borrower.address, vBStock.address, REPAY, SEIZED, OUT, true); + .withArgs(borrower.address, vBStock.address, vBtcb.address, REPAY, SEIZED, OUT, true); expect(await btcb.balanceOf(liq.address)).to.equal(OUT.sub(REPAY).sub(premium)); expect(await btcb.balanceOf(vBtcb.address)).to.equal(REPAY.add(premium)); @@ -283,7 +283,7 @@ describe("BStockLiquidator (atomic)", () => { await expect(liq.connect(owner).liquidate(twoHopParams())) .to.emit(liq, "Liquidated") - .withArgs(borrower.address, vBStock.address, REPAY, SEIZED, OUT, false); + .withArgs(borrower.address, vBStock.address, vBtcb.address, REPAY, SEIZED, OUT, false); expect(await usdt.balanceOf(liq.address)).to.equal(U("2000")); // stray USDT untouched expect(await btcb.balanceOf(liq.address)).to.equal(OUT); From c9afc4245537db042c039007a449d9b352def3af Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 15:03:11 +0530 Subject: [PATCH 16/78] feat(bstock): reject zero minOut in liquidator - A zero floor passes the InsufficientOut check for any swap output, so a misconfigured call could settle a liquidation at an arbitrary loss, including zero if a router drains the input without returning the debt asset. - Guard both entry points so the check fails before any flash borrow or repay, not mid-pipeline after funds have moved. --- contracts/BStock/BStockLiquidator.sol | 4 ++++ contracts/BStock/IBStockLiquidator.sol | 4 ++++ tests/hardhat/BStockLiquidator.ts | 11 +++++++++++ 3 files changed, 19 insertions(+) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index 09019d422..91cfb7691 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -143,6 +143,8 @@ contract BStockLiquidator is ) external override onlyOperator nonReentrant returns (uint256 debtOut) { _validateRouter(params.router); if (params.router2 != address(0)) _validateRouter(params.router2); + + if (params.minOut == 0) revert ZeroMinOut(); uint256 seizedBStock; (debtOut, seizedBStock) = _liquidate(params); emit Liquidated( @@ -165,6 +167,8 @@ contract BStockLiquidator is _validateRouter(params.router); if (params.router2 != address(0)) _validateRouter(params.router2); + if (params.minOut == 0) revert ZeroMinOut(); + IVToken[] memory vTokens = new IVToken[](1); vTokens[0] = params.vDebt; uint256[] memory amounts = new uint256[](1); diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol index 2d37c28d1..77f6bcae0 100644 --- a/contracts/BStock/IBStockLiquidator.sol +++ b/contracts/BStock/IBStockLiquidator.sol @@ -72,6 +72,10 @@ interface IBStockLiquidator { /// @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(); diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index b54bd37ef..e6ac8695f 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -355,6 +355,17 @@ describe("BStockLiquidator (atomic)", () => { await expect(liq.connect(owner).liquidate(params())).to.be.revertedWithCustomError(liq, "InsufficientOut"); }); + it("rejects a zero minOut on both entrypoints", async () => { + await expect(liq.connect(owner).liquidate(params({ minOut: 0 }))).to.be.revertedWithCustomError( + liq, + "ZeroMinOut", + ); + await expect(liq.connect(owner).flashLiquidate(params({ minOut: 0 }))).to.be.revertedWithCustomError( + liq, + "ZeroMinOut", + ); + }); + it("bubbles up a non-zero redeem error code", async () => { await vBStock.setRedeemError(7); await expect(liq.connect(owner).liquidate(params())) From 087ab6c95fb125daea5503dbfc00a3a504c75e8a Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 15:03:29 +0530 Subject: [PATCH 17/78] refactor(bstock): clarify treasury-cut var and hop-2 amountIn constraint - Rename `ours` to `treasuryCut`: it is the Venus Liquidator's treasury share subtracted from the seize, not the amount we receive. - Document that hop-2 calldata encodes a fixed amountIn while the contract approves only the actual hop-1 delta, so an under-fill reverts SwapFailed (acute for the offline pcsv2 provider). --- scripts/bstock/atomic-liquidate.ts | 4 ++-- scripts/bstock/lib/amm.ts | 12 +++++++++++- scripts/bstock/safe-fallback.ts | 4 ++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index adfa89d49..9786313bd 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -122,8 +122,8 @@ export async function atomicLiquidate(signer: Signer) { if (!liqTreasuryPct.eq(0)) { const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); const bonusAmount = seizeTokens.mul(totalIncentive.sub(ONE)).div(totalIncentive); - const ours = bonusAmount.mul(liqTreasuryPct).div(ONE); - vReceived = seizeTokens.sub(ours); + const treasuryCut = bonusAmount.mul(liqTreasuryPct).div(ONE); + vReceived = seizeTokens.sub(treasuryCut); } } diff --git a/scripts/bstock/lib/amm.ts b/scripts/bstock/lib/amm.ts index 5d6fd4bbf..62567a5e9 100644 --- a/scripts/bstock/lib/amm.ts +++ b/scripts/bstock/lib/amm.ts @@ -29,7 +29,11 @@ export interface AmmSwapParams { chain?: string; // default "bsc" tokenIn: string; // intermediate (USDT) tokenOut: string; // debt asset - amountIn: string; // wei of tokenIn to sell (the exact hop-1 output) + // Wei of tokenIn to sell. Providers bake this into the swap calldata as a FIXED input amount, while + // the contract approves router2 only the ACTUAL on-chain hop-1 output (`midDelta`). Pass the exact + // hop-1 output: if `midDelta` ends up below this value the router pulls more than approved and the + // hop reverts `SwapFailed()`. Native RFQ (hop 1) fills firm quotes exactly, so the two match. + amountIn: string; recipient: string; // where the debt asset must land (the liquidator contract) slippage: number; // percent, e.g. 0.5 } @@ -122,6 +126,12 @@ async function openocean(p: AmmSwapParams): Promise { * Offline / CI fallback: encode PancakeSwap V2 `swapExactTokensForTokens` locally and read the * expected out from the router's `getAmountsOut` view. Path from `AMM_PATH` (comma-separated), else * a direct [tokenIn, tokenOut] pair; set AMM_PATH to route through WBNB when there is no direct pool. + * + * Caveat: `amountIn` is encoded verbatim as the V2 `amountIn`, so the router pulls exactly that many + * tokens. The contract approves router2 only the actual hop-1 output (`midDelta`), so if `midDelta` + * is even 1 wei below the `amountIn` passed here the pull exceeds the approval and the hop reverts + * `SwapFailed()` with no further detail. Hop 1 is a Native RFQ firm quote (exact fill), so in the + * normal flow `midDelta == amountIn`; keep hop 1 on Native and do not hand this a stale/rounded value. */ async function pcsv2(p: AmmSwapParams, rpc?: providers.Provider): Promise { if (!rpc) throw new Error("pcsv2 AMM provider needs an RPC provider (pass one to getAmmSwap)"); diff --git a/scripts/bstock/safe-fallback.ts b/scripts/bstock/safe-fallback.ts index e24bf220a..06a181720 100644 --- a/scripts/bstock/safe-fallback.ts +++ b/scripts/bstock/safe-fallback.ts @@ -142,8 +142,8 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { if (!liqTreasuryPct.eq(0)) { const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); const bonusAmount = seizeTokens.mul(totalIncentive.sub(ONE)).div(totalIncentive); - const ours = bonusAmount.mul(liqTreasuryPct).div(ONE); - vReceived = seizeTokens.sub(ours); + const treasuryCut = bonusAmount.mul(liqTreasuryPct).div(ONE); + vReceived = seizeTokens.sub(treasuryCut); } } From 2968bf38fad862bf7c436067705033b5cc86193b Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 17:34:36 +0530 Subject: [PATCH 18/78] feat(bstock): add per-call swap deadline New deadline field on LiquidationParams; liquidate/flashLiquidate revert DeadlineExpired once block.timestamp passes it, so a stale mempool tx cannot settle against an expired quote. --- contracts/BStock/BStockLiquidator.sol | 2 ++ contracts/BStock/IBStockLiquidator.sol | 11 ++++++++--- scripts/bstock/atomic-liquidate.ts | 6 +++++- tests/hardhat/BStockLiquidator.ts | 13 +++++++++++++ tests/hardhat/Fork/BStockLiquidatorFork.ts | 1 + 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index 91cfb7691..981cea521 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -145,6 +145,7 @@ contract BStockLiquidator is if (params.router2 != address(0)) _validateRouter(params.router2); if (params.minOut == 0) revert ZeroMinOut(); + if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp); uint256 seizedBStock; (debtOut, seizedBStock) = _liquidate(params); emit Liquidated( @@ -168,6 +169,7 @@ contract BStockLiquidator is if (params.router2 != address(0)) _validateRouter(params.router2); if (params.minOut == 0) revert ZeroMinOut(); + if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp); IVToken[] memory vTokens = new IVToken[](1); vTokens[0] = params.vDebt; diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol index 77f6bcae0..fec919a93 100644 --- a/contracts/BStock/IBStockLiquidator.sol +++ b/contracts/BStock/IBStockLiquidator.sol @@ -16,7 +16,8 @@ interface IBStockLiquidator { /// 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. + /// 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) @@ -28,6 +29,7 @@ interface IBStockLiquidator { 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. @@ -88,6 +90,9 @@ interface IBStockLiquidator { /// @notice Thrown when the flashed asset does not match `params.vDebt`. error WrongFlashAsset(); + /// @notice Thrown when the call is submitted after `params.deadline`. + error DeadlineExpired(uint256 deadline, uint256 nowTs); + /// @notice Allow or disallow an address to trigger liquidations. /// @param operator Address to allowlist or remove. /// @param allowed True to allow, false to remove. @@ -108,7 +113,7 @@ interface IBStockLiquidator { /// @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, and the optional hop-2 router/calldata/intermediate for non-USDT debt). + /// 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); @@ -116,6 +121,6 @@ interface IBStockLiquidator { /// @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, and the optional hop-2 router/calldata/intermediate for non-USDT debt). + /// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt). function flashLiquidate(LiquidationParams calldata params) external; } diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 9786313bd..f60d5bc1d 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -42,7 +42,7 @@ import { getAmmSwap } from "./lib/amm"; import { BSC_USDT, getFirmQuote, quoteDeadline } from "./lib/native"; const PARAMS_TUPLE = - "(address borrower,address vDebt,address vBStock,uint256 repayAmount,address router,bytes swapCalldata,uint256 minOut,address router2,bytes swapCalldata2,address intermediateToken)"; + "(address borrower,address vDebt,address vBStock,uint256 repayAmount,address router,bytes swapCalldata,uint256 minOut,address router2,bytes swapCalldata2,address intermediateToken,uint256 deadline)"; const LIQUIDATOR_ABI = [ `function liquidate(${PARAMS_TUPLE}) returns (uint256)`, `function flashLiquidate(${PARAMS_TUPLE})`, @@ -151,6 +151,8 @@ export async function atomicLiquidate(signer: Signer) { let router2 = ethers.constants.AddressZero; let swapCalldata2 = "0x"; let intermediateToken = ethers.constants.AddressZero; + // On-chain deadline mirrors the RFQ quote's own expiry; the mock/fork path never expires. + let deadline: BigNumber = ethers.constants.MaxUint256; if (process.env.MOCK_NATIVE) { // Fork/local-test path: MOCK_NATIVE = ":" (hop 1), optional MOCK_AMM = same for @@ -176,6 +178,7 @@ export async function atomicLiquidate(signer: Signer) { }); const ttl = quoteDeadline(q) - Math.floor(Date.now() / 1000); if (ttl <= 0) throw new Error("quote already expired — refetch"); + deadline = BigNumber.from(quoteDeadline(q)); // settle tx reverts on-chain past the quote's expiry router = q.txRequest.target; swapCalldata = q.txRequest.calldata; const midOut = BigNumber.from(q.amountOut); // USDT out of hop 1 @@ -230,6 +233,7 @@ export async function atomicLiquidate(signer: Signer) { router2, swapCalldata2, intermediateToken, + deadline, }; // Inventory mode spends the contract's own debt-asset balance; warn early if it can't cover the diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index e6ac8695f..ae5b8a53e 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -86,6 +86,7 @@ describe("BStockLiquidator (atomic)", () => { router2: ZERO, swapCalldata2: "0x", intermediateToken: ZERO, + deadline: ethers.constants.MaxUint256, // never expires unless a test overrides it ...over, }; } @@ -366,6 +367,18 @@ describe("BStockLiquidator (atomic)", () => { ); }); + it("rejects an expired deadline on both entrypoints", async () => { + // deadline in the past: a stale tx sitting in the mempool must not execute against an old quote. + await expect(liq.connect(owner).liquidate(params({ deadline: 1 }))).to.be.revertedWithCustomError( + liq, + "DeadlineExpired", + ); + await expect(liq.connect(owner).flashLiquidate(params({ deadline: 1 }))).to.be.revertedWithCustomError( + liq, + "DeadlineExpired", + ); + }); + it("bubbles up a non-zero redeem error code", async () => { await vBStock.setRedeemError(7); await expect(liq.connect(owner).liquidate(params())) diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts index f3a04dac2..b6ea9645e 100644 --- a/tests/hardhat/Fork/BStockLiquidatorFork.ts +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -144,6 +144,7 @@ const test = () => { router2: ethers.constants.AddressZero, swapCalldata2: "0x", intermediateToken: ethers.constants.AddressZero, + deadline: ethers.constants.MaxUint256, }; const borrowBefore = await vDebt.connect(owner).callStatic.borrowBalanceCurrent(borrower.address); From cb3ee373e5bb28b17543b6eeaf265fb787a36ece Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 17:35:25 +0530 Subject: [PATCH 19/78] fix(bstock): reset liquidation-gate approval to zero forceApprove(gate, 0) after liquidateBorrow so a partial pull (close-factor cap) leaves no standing allowance, matching the _swap invariant. --- contracts/BStock/BStockLiquidator.sol | 3 +++ contracts/test/BStockLiquidationMocks.sol | 10 +++++++++- tests/hardhat/BStockLiquidator.ts | 9 +++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index 981cea521..0251336f1 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -275,6 +275,9 @@ contract BStockLiquidator is ensureNonzeroAddress(gate); debt.forceApprove(gate, params.repayAmount); ILiquidator(gate).liquidateBorrow(address(params.vDebt), params.borrower, params.repayAmount, params.vBStock); + // Reset the gate approval: if the Liquidator pulled less than `repayAmount` (e.g. a close-factor + // cap), the remainder would otherwise linger as a standing allowance. Same invariant as `_swap`. + debt.forceApprove(gate, 0); uint256 seizedV = params.vBStock.balanceOf(address(this)) - vBefore; // 2. Redeem the seized vBStock for raw bStock. Measure by DELTA so any pre-existing bStock diff --git a/contracts/test/BStockLiquidationMocks.sol b/contracts/test/BStockLiquidationMocks.sol index 8a41c99a3..b6bf89c2e 100644 --- a/contracts/test/BStockLiquidationMocks.sol +++ b/contracts/test/BStockLiquidationMocks.sol @@ -215,11 +215,18 @@ contract MockNativeRouter { contract MockVenusLiquidator { uint256 public incentiveMantissa = 1.1e18; uint256 public treasuryCutMantissa; // cut of the seized collateral kept by the liquidator (default 0) + uint256 public pullMantissa = 1e18; // fraction of repayAmount actually pulled (default 100%) function setTreasuryCut(uint256 m) external { treasuryCutMantissa = m; } + /// @dev Simulate a partial repay pull (e.g. a close-factor cap): the gate pulls less than the + /// caller approved, so a standing allowance would linger unless the caller resets it. + function setPullMantissa(uint256 m) external { + pullMantissa = m; + } + /// @dev Mirrors the real Venus Liquidator getter the off-chain script reads to precompute the cut. function treasuryPercentMantissa() external view returns (uint256) { return treasuryCutMantissa; @@ -232,7 +239,8 @@ contract MockVenusLiquidator { address vTokenCollateral ) external payable { address underlying = MockVTokenDebt(vToken).underlying(); - require(ERC20(underlying).transferFrom(msg.sender, address(this), repayAmount), "repay pull failed"); + uint256 pulled = (repayAmount * pullMantissa) / 1e18; + require(ERC20(underlying).transferFrom(msg.sender, address(this), pulled), "repay pull failed"); uint256 seizeV = (repayAmount * incentiveMantissa) / 1e18; uint256 toCaller = seizeV - (seizeV * treasuryCutMantissa) / 1e18; MockVTokenCollateral(vTokenCollateral).creditSeize(msg.sender, toCaller); diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index ae5b8a53e..979c4d76f 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -153,6 +153,15 @@ describe("BStockLiquidator (atomic)", () => { .withArgs(borrower.address, vBStock.address, vDebt.address, REPAY, SEIZED, OUT, false); expect(await bStock.balanceOf(liq.address)).to.equal(U("100")); // stray untouched }); + + it("resets the gate approval to zero even when the Venus Liquidator pulls less than approved", async () => { + await usdt.mint(liq.address, REPAY); + // Gate pulls only half the approved repay (mimics a close-factor cap); without the post-call + // reset the unpulled remainder would linger as a standing allowance to the gate. + await venusLiq.setPullMantissa(U("0.5")); + await liq.connect(owner).liquidate(params()); + expect(await usdt.allowance(liq.address, venusLiq.address)).to.equal(0); + }); }); describe("flash mode", () => { From 37bbaea1bbc0a9b3c60d6d66fb4da752ce212241 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 17:35:32 +0530 Subject: [PATCH 20/78] docs(bstock): clarify inventory floor and native-debt limits --- contracts/BStock/BStockLiquidator.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index 0251336f1..d032465c2 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -138,6 +138,12 @@ contract BStockLiquidator is // --------------------------------------------------------------------- // /// @inheritdoc IBStockLiquidator + /// @dev INVENTORY mode spends the contract's OWN debt-asset capital, so — unlike FLASH mode, where + /// `executeOperation` forces the swap proceeds to cover principal + premium — there is no + /// built-in floor tying `debtOut` to `repayAmount`. This asymmetry is intentional: a repay can + /// legitimately out-cost its proceeds (e.g. the Venus Liquidator keeps a treasury cut of the + /// seized collateral, so proceeds land a few % under the repay). `minOut` IS the operator's + /// chosen loss floor for inventory mode — set it to the lowest acceptable debt-asset return. function liquidate( LiquidationParams calldata params ) external override onlyOperator nonReentrant returns (uint256 debtOut) { @@ -259,6 +265,9 @@ contract BStockLiquidator is * @return seizedBStock Raw bStock redeemed and sold (balance delta). */ function _liquidate(LiquidationParams memory params) private returns (uint256 debtOut, uint256 seizedBStock) { + // Only ERC20 debt markets are supported: `underlying()` is read for both the debt and the + // collateral. A native-BNB debt market (vBNB) exposes no `underlying()` and repays via + // `msg.value`, so it reverts here loudly rather than silently mis-repaying. IERC20Upgradeable debt = IERC20Upgradeable(params.vDebt.underlying()); IERC20Upgradeable bStock = IERC20Upgradeable(params.vBStock.underlying()); From d843b27edc6614409774a648f173e9c3151cc19c Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 18:10:56 +0530 Subject: [PATCH 21/78] feat(bstock): support native BNB (vBNB) debt - vBNB has no underlying() and is repaid in native BNB, so the ERC20 repay path cannot handle it. Account the debt in WBNB for both the inventory and flash modes, unwrap only the repay amount, and forward it to the gate's payable vBNB branch; swap proceeds stay in WBNB. - Flash mode borrows vWBNB, not vBNB: vBNB's native doTransferIn can never satisfy the comptroller-driven flash repay. - Keep the vBNB/vWBNB/WBNB addresses immutable so WBNB.withdraw's 2300-gas transfer into the proxy receive() stays within budget. - Accept native via receive() and add owner sweepNative to rescue any stray or refunded BNB. - Fork tests target codeless on-chain EOAs: vBNB disburses a borrow with a native transfer (a CALL into the borrower), and hardhat's default accounts carry EIP-7702 delegation code that is an invalid opcode under the fork's London rules. --- contracts/BStock/BStockLiquidator.sol | 113 +++++++- contracts/BStock/IBStockLiquidator.sol | 11 + contracts/external/IWBNB.sol | 2 + contracts/test/BStockLiquidationMocks.sol | 17 +- deploy/019-deploy-bstock-liquidator.ts | 45 +++ scripts/bstock/atomic-liquidate.ts | 27 +- tests/hardhat/BStockAtomicLiquidate.ts | 37 ++- tests/hardhat/BStockLiquidator.ts | 126 ++++++++- tests/hardhat/Fork/BStockLiquidatorFork.ts | 312 ++++++++++++++------- 9 files changed, 551 insertions(+), 139 deletions(-) create mode 100644 deploy/019-deploy-bstock-liquidator.ts diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index d032465c2..ce25d9d97 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -9,6 +9,7 @@ import { ensureNonzeroAddress } from "@venusprotocol/solidity-utilities/contract import { VToken } from "../Tokens/VTokens/VToken.sol"; import { IFlashLoanReceiver } from "../FlashLoan/interfaces/IFlashLoanReceiver.sol"; +import { IWBNB } from "../external/IWBNB.sol"; // Shared Venus interfaces: IComptroller (Core diamond — liquidator gate + flash loan), IVToken // (flash-loan asset array element), and ILiquidator (the pool-wide Venus Liquidator gate that pulls @@ -35,6 +36,12 @@ import { IBStockLiquidator } from "./IBStockLiquidator.sol"; * - FLASH: the debt asset is flash-borrowed from Venus (`Comptroller.executeFlashLoan`) and repaid * (+ premium) within the same tx; no capital is locked in the contract. * + * Native BNB debt (vBNB): supported in both modes with WBNB as the debt-accounting token. The repay + * must be native BNB, so exactly the repay amount of WBNB is unwrapped and forwarded to the gate's + * payable path; the two-hop swap lands WBNB (bStock->USDT->WBNB) and `minOut` is measured in WBNB + * (1:1 with BNB). FLASH mode borrows from vWBNB, NOT vBNB: vBNB cannot be flash-repaid (its + * `doTransferIn` requires `msg.value`), whereas vWBNB's underlying is a plain ERC20. + * * Ownership / scope: this is Venus's OWN backstop tool, NOT a public utility — `liquidate` and * `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock * liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus @@ -75,6 +82,21 @@ contract BStockLiquidator is /// @custom:oz-upgrades-unsafe-allow state-variable-immutable IComptroller public immutable comptroller; + /// @notice The native BNB market (vBNB). A debt equal to this address is settled with native BNB: + /// WBNB is the debt-accounting token, and only the repay amount is unwrapped (see `_liquidate`). + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + address public immutable vBNB; + + /// @notice The WBNB market (vWBNB). BNB debt is flash-funded from here, NOT from vBNB: vBNB cannot be + /// flash-repaid (its `doTransferIn` needs `msg.value`), whereas vWBNB's underlying is a plain + /// ERC20 repaid via `transferFrom`. + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + IVToken public immutable vWBNB; + + /// @notice WBNB token: the debt-accounting asset for BNB debt, unwrapped to native BNB for the repay. + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + IWBNB public immutable wbnb; + /// @notice Addresses allowed to trigger a liquidation. mapping(address => bool) public isOperator; @@ -89,12 +111,21 @@ contract BStockLiquidator is _; } - /// @notice Constructor for the implementation contract. Sets the immutable Comptroller and locks initializers. + /// @notice Constructor for the implementation contract. Sets the immutables and locks initializers. /// @param comptroller_ Venus Core Comptroller (diamond) — gates liquidation and provides flash loans. + /// @param vBNB_ Native BNB market; a debt equal to this address is settled in native BNB. + /// @param vWBNB_ WBNB market; the flash-borrow source for BNB debt (vBNB itself cannot be flash-repaid). + /// @param wbnb_ WBNB token; the debt-accounting asset for BNB debt, unwrapped for the native repay. /// @custom:oz-upgrades-unsafe-allow constructor - constructor(IComptroller comptroller_) { + constructor(IComptroller comptroller_, address vBNB_, IVToken vWBNB_, IWBNB wbnb_) { ensureNonzeroAddress(address(comptroller_)); + ensureNonzeroAddress(vBNB_); + ensureNonzeroAddress(address(vWBNB_)); + ensureNonzeroAddress(address(wbnb_)); comptroller = comptroller_; + vBNB = vBNB_; + vWBNB = vWBNB_; + wbnb = wbnb_; _disableInitializers(); } @@ -107,6 +138,13 @@ contract BStockLiquidator is _transferOwnership(initialOwner); } + /// @notice Accept native BNB. The expected inflow is `wbnb.withdraw` during a BNB liquidation (the + /// unwrapped repay is forwarded to the gate in the same call, so no BNB is retained on the + /// happy path). Left permissive (not restricted to `wbnb`) both to keep the receive body + /// minimal for WBNB's 2300-gas `.transfer` stipend and to tolerate a stray transfer or a + /// future gate refund — any such balance is recoverable via `sweepNative`. + receive() external payable {} + // --------------------------------------------------------------------- // // Admin // // --------------------------------------------------------------------- // @@ -133,6 +171,14 @@ contract BStockLiquidator is emit Swept(token, to, amount); } + /// @inheritdoc IBStockLiquidator + function sweepNative(address to, uint256 amount) external override onlyOwner { + ensureNonzeroAddress(to); + (bool ok, ) = to.call{ value: amount }(""); + if (!ok) revert NativeTransferFailed(); + emit SweptNative(to, amount); + } + // --------------------------------------------------------------------- // // INVENTORY mode // // --------------------------------------------------------------------- // @@ -177,8 +223,10 @@ contract BStockLiquidator is if (params.minOut == 0) revert ZeroMinOut(); if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp); + // BNB debt is flash-funded from vWBNB (an ERC20 market), not vBNB: vBNB cannot be flash-repaid. + // The flashed WBNB is unwrapped to native BNB for the repay inside `_liquidate` (see `executeOperation`). IVToken[] memory vTokens = new IVToken[](1); - vTokens[0] = params.vDebt; + vTokens[0] = (address(params.vDebt) == vBNB) ? vWBNB : IVToken(address(params.vDebt)); uint256[] memory amounts = new uint256[](1); amounts[0] = params.repayAmount; @@ -206,7 +254,12 @@ contract BStockLiquidator is if (initiator != address(this)) revert BadInitiator(initiator); LiquidationParams memory params = abi.decode(param, (LiquidationParams)); - if (address(vTokens[0]) != address(params.vDebt)) revert WrongFlashAsset(); + // For BNB debt the flash is drawn from vWBNB, not vBNB (see `flashLiquidate`). Scoped so the + // temporary doesn't count against the stack depth of the rest of the function. + { + address expectedFlash = address(params.vDebt) == vBNB ? address(vWBNB) : address(params.vDebt); + if (address(vTokens[0]) != expectedFlash) revert WrongFlashAsset(); + } // Repay was just funded by the flash loan; run the liquidation + swap. (uint256 debtOut, uint256 seizedBStock) = _liquidate(params); @@ -218,8 +271,12 @@ contract BStockLiquidator is repayAmounts[0] = amounts[0] + premiums[0]; if (debtOut < repayAmounts[0]) revert InsufficientOut(debtOut, repayAmounts[0]); - // Approve the flashed vToken to pull back principal + premium. - IERC20Upgradeable(params.vDebt.underlying()).forceApprove(address(vTokens[0]), repayAmounts[0]); + // Approve the flashed vToken to pull back principal + premium. For BNB debt the flashed asset is + // WBNB (from vWBNB); the ternary short-circuits so `underlying()` is never called on vBNB (it has none). + IERC20Upgradeable(address(params.vDebt) == vBNB ? address(wbnb) : params.vDebt.underlying()).forceApprove( + address(vTokens[0]), + repayAmounts[0] + ); emit Liquidated( params.borrower, @@ -265,10 +322,17 @@ contract BStockLiquidator is * @return seizedBStock Raw bStock redeemed and sold (balance delta). */ function _liquidate(LiquidationParams memory params) private returns (uint256 debtOut, uint256 seizedBStock) { - // Only ERC20 debt markets are supported: `underlying()` is read for both the debt and the - // collateral. A native-BNB debt market (vBNB) exposes no `underlying()` and repays via - // `msg.value`, so it reverts here loudly rather than silently mis-repaying. - IERC20Upgradeable debt = IERC20Upgradeable(params.vDebt.underlying()); + // A debt equal to `vBNB` is native BNB. vBNB has no `underlying()`, so the `isBnb` check MUST come + // first: the ternary short-circuits and `underlying()` is never evaluated for vBNB. WBNB is the + // debt-accounting token throughout (1:1 with BNB), so the whole swap/minOut path below is reused. + // KEEP THIS ORDER — hoisting `underlying()` above the check reverts every BNB liquidation. + bool isBnb = address(params.vDebt) == vBNB; + // Native RFQ only quotes bStock->USDT, so a BNB debt is inherently two-hop (...->WBNB). Reject a + // single-hop BNB config up front instead of failing opaquely later on a zero WBNB delta. + if (isBnb && params.router2 == address(0)) revert InvalidIntermediate(); + IERC20Upgradeable debt = isBnb + ? IERC20Upgradeable(address(wbnb)) + : IERC20Upgradeable(params.vDebt.underlying()); IERC20Upgradeable bStock = IERC20Upgradeable(params.vBStock.underlying()); // 1. Repay the borrow, seizing the bStock vToken to this contract. @@ -282,11 +346,30 @@ contract BStockLiquidator is uint256 vBefore = params.vBStock.balanceOf(address(this)); address gate = comptroller.liquidatorContract(); ensureNonzeroAddress(gate); - debt.forceApprove(gate, params.repayAmount); - ILiquidator(gate).liquidateBorrow(address(params.vDebt), params.borrower, params.repayAmount, params.vBStock); - // Reset the gate approval: if the Liquidator pulled less than `repayAmount` (e.g. a close-factor - // cap), the remainder would otherwise linger as a standing allowance. Same invariant as `_swap`. - debt.forceApprove(gate, 0); + if (isBnb) { + // Unwrap EXACTLY the repay (WBNB held as inventory or drawn from the vWBNB flash) and forward + // native BNB to the gate's vBNB branch (`{value:}`). Only the repay portion is unwrapped, so + // pre-existing WBNB inventory is untouched; the swap proceeds below stay as WBNB. No approval + // is granted (value is forwarded), so there is no standing allowance to reset. + wbnb.withdraw(params.repayAmount); + ILiquidator(gate).liquidateBorrow{ value: params.repayAmount }( + address(params.vDebt), + params.borrower, + params.repayAmount, + params.vBStock + ); + } else { + debt.forceApprove(gate, params.repayAmount); + ILiquidator(gate).liquidateBorrow( + address(params.vDebt), + params.borrower, + params.repayAmount, + params.vBStock + ); + // Reset the gate approval: if the Liquidator pulled less than `repayAmount` (e.g. a close-factor + // cap), the remainder would otherwise linger as a standing allowance. Same invariant as `_swap`. + debt.forceApprove(gate, 0); + } uint256 seizedV = params.vBStock.balanceOf(address(this)) - vBefore; // 2. Redeem the seized vBStock for raw bStock. Measure by DELTA so any pre-existing bStock diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol index fec919a93..8397e34a8 100644 --- a/contracts/BStock/IBStockLiquidator.sol +++ b/contracts/BStock/IBStockLiquidator.sol @@ -59,6 +59,9 @@ interface IBStockLiquidator { /// @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 Thrown when the caller is neither the owner nor an allowlisted operator. error NotOperator(); @@ -93,6 +96,9 @@ interface IBStockLiquidator { /// @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. @@ -109,6 +115,11 @@ interface IBStockLiquidator { /// @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`. diff --git a/contracts/external/IWBNB.sol b/contracts/external/IWBNB.sol index 01cf333b5..f1ecda7c2 100644 --- a/contracts/external/IWBNB.sol +++ b/contracts/external/IWBNB.sol @@ -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; } diff --git a/contracts/test/BStockLiquidationMocks.sol b/contracts/test/BStockLiquidationMocks.sol index b6bf89c2e..c65d9f9a2 100644 --- a/contracts/test/BStockLiquidationMocks.sol +++ b/contracts/test/BStockLiquidationMocks.sol @@ -216,6 +216,7 @@ contract MockVenusLiquidator { uint256 public incentiveMantissa = 1.1e18; uint256 public treasuryCutMantissa; // cut of the seized collateral kept by the liquidator (default 0) uint256 public pullMantissa = 1e18; // fraction of repayAmount actually pulled (default 100%) + address public vBnb; // native BNB market; a repay for this vToken must arrive as msg.value function setTreasuryCut(uint256 m) external { treasuryCutMantissa = m; @@ -227,6 +228,11 @@ contract MockVenusLiquidator { pullMantissa = m; } + /// @dev Mirrors the real gate: a vBNB repay is native BNB (msg.value), not an ERC20 transferFrom. + function setVBnb(address v) external { + vBnb = v; + } + /// @dev Mirrors the real Venus Liquidator getter the off-chain script reads to precompute the cut. function treasuryPercentMantissa() external view returns (uint256) { return treasuryCutMantissa; @@ -238,9 +244,14 @@ contract MockVenusLiquidator { uint256 repayAmount, address vTokenCollateral ) external payable { - address underlying = MockVTokenDebt(vToken).underlying(); - uint256 pulled = (repayAmount * pullMantissa) / 1e18; - require(ERC20(underlying).transferFrom(msg.sender, address(this), pulled), "repay pull failed"); + if (vToken == vBnb) { + // Native BNB repay: require exact msg.value (mirrors Liquidator.sol) and keep the BNB. + require(msg.value == repayAmount, "bad value"); + } else { + address underlying = MockVTokenDebt(vToken).underlying(); + uint256 pulled = (repayAmount * pullMantissa) / 1e18; + require(ERC20(underlying).transferFrom(msg.sender, address(this), pulled), "repay pull failed"); + } uint256 seizeV = (repayAmount * incentiveMantissa) / 1e18; uint256 toCaller = seizeV - (seizeV * treasuryCutMantissa) / 1e18; MockVTokenCollateral(vTokenCollateral).creditSeize(msg.sender, toCaller); diff --git a/deploy/019-deploy-bstock-liquidator.ts b/deploy/019-deploy-bstock-liquidator.ts new file mode 100644 index 000000000..115b46400 --- /dev/null +++ b/deploy/019-deploy-bstock-liquidator.ts @@ -0,0 +1,45 @@ +import { DeployFunction } from "hardhat-deploy/types"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const { deployments, network, getNamedAccounts } = hre; + const { deploy, catchUnknownSigner } = deployments; + const { deployer } = await getNamedAccounts(); + + // Core Comptroller (diamond) + the three BNB-debt immutables. vBNB is the native market (detected as + // BNB debt), vWBNB is the ERC20 flash-borrow source (vBNB itself cannot be flash-repaid), and WBNB is + // the debt-accounting token unwrapped for the native repay. The immutable vBNB MUST match the address + // the pool-wide Liquidator gate is configured with, else the gate takes its BEP20 branch and reverts. + const comptrollerAddress = (await deployments.get("Unitroller")).address; + const vBnbAddress = (await deployments.get("vBNB")).address; + const vWbnbAddress = (await deployments.get("vWBNB")).address; + const wBnbAddress = (await deployments.get("WBNB")).address; + const timelockAddress = (await deployments.get("NormalTimelock")).address; + + const owner = network.name === "hardhat" ? deployer : timelockAddress; + + await catchUnknownSigner( + deploy("BStockLiquidator", { + contract: "BStockLiquidator", + from: deployer, + args: [comptrollerAddress, vBnbAddress, vWbnbAddress, wBnbAddress], + log: true, + autoMine: true, + proxy: { + owner, + proxyContract: "OpenZeppelinTransparentProxy", + execute: { + methodName: "initialize", + args: [owner], + }, + }, + }), + ); +}; + +func.tags = ["bstock-liquidator"]; +// bStock (RFQ-only via Native) and the vWBNB flash source are bscmainnet-only; wire the vBNB/vWBNB/WBNB +// deployments before enabling this on any other network. +func.skip = async (hre: HardhatRuntimeEnvironment) => hre.network.name !== "bscmainnet"; + +export default func; diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index f60d5bc1d..59b1f0690 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -31,6 +31,9 @@ * SLIPPAGE Native slippage %, default 0.5 * MIN_OUT_BUFFER extra haircut on minOut beyond slippage, default 0.5 (%) * AMM_PROVIDER hop-2 route source for non-USDT debt: kyberswap (default) | openocean | pcsv2 + * WBNB_ADDR WBNB token, used only for a vBNB (native BNB) debt market (default: BSC WBNB). + * Native BNB debt is auto-detected (vBNB has no underlying()) and accounted in WBNB; + * the contract unwraps the repay, so pre-fund inventory in WBNB (MODE=inventory). * DRY_RUN "1" -> callStatic only, send nothing * MOCK_NATIVE hop-1 "router:calldata" for fork/local tests (see below) * MOCK_AMM hop-2 "router:calldata" for fork/local tests (two-hop); MOCK_OUT = final debt out @@ -38,7 +41,7 @@ import { BigNumber, Contract, Signer } from "ethers"; import { ethers } from "hardhat"; -import { getAmmSwap } from "./lib/amm"; +import { BSC_WBNB, getAmmSwap } from "./lib/amm"; import { BSC_USDT, getFirmQuote, quoteDeadline } from "./lib/native"; const PARAMS_TUPLE = @@ -85,15 +88,23 @@ export async function atomicLiquidate(signer: Signer) { const vDebt = new Contract(env("VDEBT"), VTOKEN_ABI, signer); const comptroller = new Contract(await vBStock.comptroller(), COMPTROLLER_ABI, signer); - const debt = new Contract(await vDebt.underlying(), ERC20_ABI, signer); + + // vBNB has no underlying(): a native-BNB debt is accounted in WBNB (1:1 with BNB). The contract + // unwraps the repay internally, so off-chain the debt asset for the swap chain + minOut is WBNB. + let debtAddr: string; + let isBnb = false; + try { + debtAddr = await vDebt.underlying(); + } catch { + isBnb = true; + debtAddr = ethers.utils.getAddress(process.env.WBNB_ADDR || BSC_WBNB); + } + const debt = new Contract(debtAddr, ERC20_ABI, signer); const bStock = new Contract(await vBStock.underlying(), ERC20_ABI, signer); - const [debtDec, debtSym, bStockDec, bStockSym] = await Promise.all([ - debt.decimals(), - debt.symbol(), - bStock.decimals(), - bStock.symbol(), - ]); + const [bStockDec, bStockSym] = await Promise.all([bStock.decimals(), bStock.symbol()]); + const [debtDec, debtSym] = isBnb ? [18, "WBNB"] : await Promise.all([debt.decimals(), debt.symbol()]); const repay = ethers.utils.parseUnits(env("REPAY_AMOUNT"), debtDec); + if (isBnb) console.log(`native BNB debt: accounting in WBNB ${debt.address} (contract unwraps the repay)`); // 0. liquidatable? const [, , shortfall]: BigNumber[] = await comptroller.getAccountLiquidity(borrower); diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index f1b6ece79..547784d88 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -1,3 +1,4 @@ +import { setBalance } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; import { Contract } from "ethers"; import { ethers, upgrades } from "hardhat"; @@ -12,6 +13,8 @@ import { atomicLiquidate } from "../../scripts/bstock/atomic-liquidate"; const U = (n: string) => ethers.utils.parseUnits(n, 18); const INCENTIVE = U("1.1"); // mock seizes repay * 1.1 +// Sentinel native BNB market (vBNB); no code — the ERC20 script paths never touch it. +const VBNB = ethers.utils.getAddress("0x0000000000000000000000000000000000000b0b"); const REPAY = U("5000"); const SEIZED = REPAY.mul(INCENTIVE).div(U("1")); // 5500 bStock at 1:1 redeem @@ -25,6 +28,7 @@ const SCRIPT_ENV = [ "VDEBT", "REPAY_AMOUNT", "USDT_ADDR", + "WBNB_ADDR", "MOCK_NATIVE", "MOCK_AMM", "MOCK_OUT", @@ -38,11 +42,12 @@ describe("bStock atomic liquidation script", () => { let owner: any, borrower: any; let usdt: Contract, bStock: Contract; let comptroller: Contract, vBStock: Contract, vDebt: Contract, router: Contract, liq: Contract; + let wbnb: Contract, vWBNB: Contract; async function deployLiquidator(comptrollerAddr: string) { const Factory = await ethers.getContractFactory("BStockLiquidator"); return upgrades.deployProxy(Factory, [owner.address], { - constructorArgs: [comptrollerAddr], + constructorArgs: [comptrollerAddr, VBNB, vWBNB.address, wbnb.address], unsafeAllow: ["constructor", "state-variable-immutable"], }); } @@ -85,9 +90,14 @@ describe("bStock atomic liquidation script", () => { vDebt = await (await ethers.getContractFactory("MockVTokenDebt")).deploy(usdt.address, comptroller.address); router = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); + // WBNB + its market, for the BNB-debt constructor immutables (vWBNB is the flash source). + wbnb = await (await ethers.getContractFactory("WBNB")).deploy(); + vWBNB = await (await ethers.getContractFactory("MockVTokenDebt")).deploy(wbnb.address, comptroller.address); + // Core's pool-wide liquidator gate is always configured; every liquidation routes through it. const venusLiq = await (await ethers.getContractFactory("MockVenusLiquidator")).deploy(); await comptroller.setLiquidatorContract(venusLiq.address); + await venusLiq.setVBnb(VBNB); liq = await deployLiquidator(comptroller.address); await liq.connect(owner).setRouter(router.address, true); @@ -189,4 +199,29 @@ describe("bStock atomic liquidation script", () => { await expect(atomicLiquidate(owner)).to.be.rejectedWith("not allowlisted"); expect(await btcb.balanceOf(liq.address)).to.equal(REPAY); // untouched }); + + // Native BNB debt: VDEBT is vBNB (no underlying()), so the script auto-detects it, accounts the debt + // in WBNB, and appends the USDT->WBNB hop. The contract unwraps the repay and settles in native BNB. + it("bnb debt: auto-detects vBNB, accounts in WBNB, settles via the contract (inventory)", async () => { + const ammBnb = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); + await liq.connect(owner).setRouter(ammBnb.address, true); + await wbnb.setBalanceOf(ammBnb.address, OUT); // hop-2 pays WBNB + await wbnb.setBalanceOf(liq.address, REPAY); // pre-funded WBNB float (inventory) + await setBalance(wbnb.address, U("1000000")); // back the WBNB mock so withdraw() pays out + + const hop1 = router.interface.encodeFunctionData("swapAll", [bStock.address, usdt.address, liq.address]); + const hop2 = ammBnb.interface.encodeFunctionData("swapAll", [usdt.address, wbnb.address, liq.address]); + setEnv({ + VDEBT: VBNB, // sentinel vBNB: underlying() reverts -> script treats debt as native BNB + WBNB_ADDR: wbnb.address, + MOCK_NATIVE: `${router.address}:${hop1}`, + MOCK_AMM: `${ammBnb.address}:${hop2}`, + }); + + await atomicLiquidate(owner); + + expect(await wbnb.balanceOf(liq.address)).to.equal(OUT); // float consumed, proceeds retained as WBNB + expect(await ethers.provider.getBalance(liq.address)).to.equal(0); // no native BNB retained + expect(await usdt.balanceOf(liq.address)).to.equal(0); // intermediate consumed + }); }); diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index 979c4d76f..55980de35 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -1,3 +1,4 @@ +import { setBalance } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; import { BigNumber, Contract } from "ethers"; import { ethers, upgrades } from "hardhat"; @@ -11,11 +12,16 @@ import { ethers, upgrades } from "hardhat"; const U = (n: string) => ethers.utils.parseUnits(n, 18); const ZERO = ethers.constants.AddressZero; const INCENTIVE = U("1.1"); // 10% — mock seizes repay * 1.1 +// Sentinel address for the native BNB market (vBNB). It has no code on purpose: the BNB happy-path +// tests thus double as the guard that the contract never calls `underlying()` (or anything) on vBNB — +// any such call would revert against a codeless address. +const VBNB = ethers.utils.getAddress("0x0000000000000000000000000000000000000b0b"); describe("BStockLiquidator (atomic)", () => { let owner: any, operator: any, borrower: any, stranger: any; let usdt: Contract, bStock: Contract; let comptroller: Contract, vBStock: Contract, vDebt: Contract, router: Contract, venusLiq: Contract; + let wbnb: Contract, vWBNB: Contract; let liq: Contract; const REPAY = U("5000"); @@ -37,10 +43,16 @@ describe("BStockLiquidator (atomic)", () => { vDebt = await (await ethers.getContractFactory("MockVTokenDebt")).deploy(usdt.address, comptroller.address); router = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); + // WBNB + its market: WBNB is the debt-accounting token for BNB debt, and vWBNB is the flash source + // (vBNB itself cannot be flash-repaid). The gate learns the native market so its repay branch matches. + wbnb = await (await ethers.getContractFactory("WBNB")).deploy(); + vWBNB = await (await ethers.getContractFactory("MockVTokenDebt")).deploy(wbnb.address, comptroller.address); + // Core's pool-wide liquidator gate is always configured on the networks this contract targets, // so every liquidation routes through the Venus Liquidator. Default treasury cut = 0. venusLiq = await (await ethers.getContractFactory("MockVenusLiquidator")).deploy(); await comptroller.setLiquidatorContract(venusLiq.address); + await venusLiq.setVBnb(VBNB); liq = await deployLiquidator(comptroller.address); await liq.connect(owner).setRouter(router.address, true); @@ -56,7 +68,7 @@ describe("BStockLiquidator (atomic)", () => { async function deployLiquidator(comptrollerAddr: string) { const Factory = await ethers.getContractFactory("BStockLiquidator"); return upgrades.deployProxy(Factory, [owner.address], { - constructorArgs: [comptrollerAddr], + constructorArgs: [comptrollerAddr, VBNB, vWBNB.address, wbnb.address], unsafeAllow: ["constructor", "state-variable-immutable"], }); } @@ -309,6 +321,111 @@ describe("BStockLiquidator (atomic)", () => { }); }); + // Native BNB debt. vBNB has no underlying() and its borrow is repaid in native BNB, so WBNB is the + // debt-accounting token: only the repay amount is unwrapped (WBNB -> BNB) for the gate's payable path, + // and the two-hop swap lands WBNB (bStock -> USDT -> WBNB). FLASH mode borrows from vWBNB (an ERC20 + // market), not vBNB. The WBNB.withdraw -> proxy -> receive() path here exercises the 2300-gas stipend. + describe("BNB debt (native, WBNB-accounted)", () => { + let ammBnb: Contract; + + // hop-2 calldata against the AMM mock: swap(USDT, amountIn, WBNB, to). + function bnbSwapCalldata(amountIn: BigNumber, to: string) { + return ammBnb.interface.encodeFunctionData("swap", [usdt.address, amountIn, wbnb.address, to]); + } + function bnbParams(over: Partial = {}) { + return params({ + vDebt: VBNB, // native BNB market + router2: ammBnb.address, + swapCalldata2: bnbSwapCalldata(SEIZED, liq.address), + intermediateToken: usdt.address, + ...over, + }); + } + + beforeEach(async () => { + ammBnb = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); + await liq.connect(owner).setRouter(ammBnb.address, true); + // hop-1 router (Native mock) already holds USDT (from deploy()); hop-2 AMM pays out WBNB. + await wbnb.setBalanceOf(ammBnb.address, OUT); + // Back the WBNB mock with native BNB so `withdraw()`'s `.transfer` can pay out the unwrapped repay. + await setBalance(wbnb.address, U("1000000")); + }); + + it("inventory: unwraps only the repay, sells bStock -> USDT -> WBNB, keeps the incentive", async () => { + await wbnb.setBalanceOf(liq.address, REPAY); // pre-funded WBNB float + + expect(await liq.connect(owner).callStatic.liquidate(bnbParams())).to.equal(OUT); + + await expect(liq.connect(owner).liquidate(bnbParams())) + .to.emit(liq, "Liquidated") + .withArgs(borrower.address, vBStock.address, VBNB, REPAY, SEIZED, OUT, false); + + expect(await wbnb.balanceOf(liq.address)).to.equal(OUT); // float consumed, proceeds retained as WBNB + expect(await ethers.provider.getBalance(liq.address)).to.equal(0); // no native BNB retained + expect(await usdt.balanceOf(liq.address)).to.equal(0); // intermediate fully consumed + expect(await bStock.balanceOf(liq.address)).to.equal(0); + // The native repay reached the gate. + expect(await ethers.provider.getBalance(venusLiq.address)).to.equal(REPAY); + }); + + it("flash: borrows WBNB from vWBNB, unwraps, settles, repays principal + premium", async () => { + await wbnb.setBalanceOf(vWBNB.address, REPAY); // vWBNB lends the WBNB principal + await comptroller.setFlashPremium(U("0.001")); + const premium = REPAY.mul(U("0.001")).div(U("1")); + + await expect(liq.connect(owner).flashLiquidate(bnbParams())) + .to.emit(liq, "Liquidated") + .withArgs(borrower.address, vBStock.address, VBNB, REPAY, SEIZED, OUT, true); + + expect(await wbnb.balanceOf(liq.address)).to.equal(OUT.sub(REPAY).sub(premium)); + expect(await wbnb.balanceOf(vWBNB.address)).to.equal(REPAY.add(premium)); // principal + premium back + expect(await ethers.provider.getBalance(liq.address)).to.equal(0); // no native BNB retained + expect(await usdt.balanceOf(liq.address)).to.equal(0); // intermediate consumed + }); + + it("enforces minOut in WBNB when the hop-2 rate underdelivers", async () => { + await wbnb.setBalanceOf(liq.address, REPAY); + await ammBnb.setRate(U("0.9")); // 5500 USDT -> 4950 WBNB, below the 5400 minOut + await expect(liq.connect(owner).liquidate(bnbParams())).to.be.revertedWithCustomError(liq, "InsufficientOut"); + expect(await wbnb.balanceOf(liq.address)).to.equal(REPAY); // reverted, float intact + }); + + it("flash: will not let WBNB inventory mask a swap below principal + premium", async () => { + await wbnb.setBalanceOf(vWBNB.address, REPAY); // flash principal + await wbnb.setBalanceOf(liq.address, U("2000")); // inventory that could silently backfill + await comptroller.setFlashPremium(U("0.001")); // obligation 5005 + await ammBnb.setRate(U("0.8")); // 5500 -> 4400 WBNB: clears a loose minOut but < 5005 + const p = bnbParams({ minOut: U("4000") }); + await expect(liq.connect(owner).flashLiquidate(p)).to.be.revertedWithCustomError(liq, "InsufficientOut"); + expect(await wbnb.balanceOf(liq.address)).to.equal(U("2000")); // inventory untouched + }); + + it("rejects a single-hop BNB config (router2 == 0)", async () => { + await wbnb.setBalanceOf(liq.address, REPAY); + await expect( + liq.connect(owner).liquidate(bnbParams({ router2: ZERO, swapCalldata2: "0x", intermediateToken: ZERO })), + ).to.be.revertedWithCustomError(liq, "InvalidIntermediate"); + }); + + it("sweepNative: owner recovers stray native BNB, non-owner and zero recipient revert", async () => { + await setBalance(stranger.address, U("10")); + await stranger.sendTransaction({ to: liq.address, value: U("3") }); + expect(await ethers.provider.getBalance(liq.address)).to.equal(U("3")); + + await expect(liq.connect(stranger).sweepNative(stranger.address, U("1"))).to.be.revertedWith( + "Ownable: caller is not the owner", + ); + await expect(liq.connect(owner).sweepNative(ZERO, U("1"))).to.be.revertedWithCustomError( + liq, + "ZeroAddressNotAllowed", + ); + await expect(liq.connect(owner).sweepNative(owner.address, U("3"))) + .to.emit(liq, "SweptNative") + .withArgs(owner.address, U("3")); + expect(await ethers.provider.getBalance(liq.address)).to.equal(0); + }); + }); + describe("flash callback guards (executeOperation)", () => { it("rejects a caller other than the Comptroller", async () => { await expect( @@ -408,11 +525,14 @@ describe("BStockLiquidator (atomic)", () => { await expect(liq.initialize(owner.address)).to.be.revertedWith("Initializable: contract is already initialized"); const Factory = await ethers.getContractFactory("BStockLiquidator"); // zero comptroller -> implementation constructor reverts - await expect(Factory.deploy(ZERO)).to.be.revertedWithCustomError(Factory, "ZeroAddressNotAllowed"); + await expect(Factory.deploy(ZERO, VBNB, vWBNB.address, wbnb.address)).to.be.revertedWithCustomError( + Factory, + "ZeroAddressNotAllowed", + ); // zero owner -> initializer reverts during proxy deploy await expect( upgrades.deployProxy(Factory, [ZERO], { - constructorArgs: [comptroller.address], + constructorArgs: [comptroller.address, VBNB, vWBNB.address, wbnb.address], unsafeAllow: ["constructor", "state-variable-immutable"], }), ).to.be.revertedWithCustomError(Factory, "ZeroAddressNotAllowed"); diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts index b6ea9645e..2048364da 100644 --- a/tests/hardhat/Fork/BStockLiquidatorFork.ts +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -1,4 +1,4 @@ -import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; +import { setBalance } from "@nomicfoundation/hardhat-network-helpers"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { expect } from "chai"; import { BigNumber, Contract } from "ethers"; @@ -9,168 +9,262 @@ import { IAccessControlManagerV8__factory } from "../../../typechain"; import { FORK_MAINNET, forking, initMainnetUser } from "./utils"; /** - * Real bscmainnet fork test for the atomic BStockLiquidator. + * Real bscmainnet fork tests for the atomic BStockLiquidator — 100% on-chain, NO mock routers, NO mock gate. * - * The bStock markets (vTSLAB/vNVDAB) are DEPLOYED (PR #679) but NOT YET LISTED in Core on mainnet - * (VIP #718 hasn't executed), so this exercises the contract's atomic pipeline against REAL Venus - * Core mechanics (liquidateBorrow -> seize -> redeem) using liquid Core markets as stand-ins for the - * bStock collateral: vUSDC (collateral) / vUSDT (debt). The contract is collateral-agnostic; retarget - * to vTSLAB/vUSDT via FORK_VCOLLATERAL / FORK_VDEBT / FORK_COLLATERAL_WHALE once the market is listed. + * These are not happy-path demos. A bStock backstop liquidation fires during stress (volatile prices, thin + * books, native-BNB debt), so the point is to prove the contract behaves correctly THEN: it settles when the + * real liquidity can cover it, and it reverts CLEANLY (no loss, no half-liquidation, inventory intact) when it + * cannot. Every swap hop is a real PancakeSwap V2 swap and every repay routes through the real Venus Liquidator + * gate, so on-chain liquidity depth, the native-BNB repay, and the WBNB unwrap are all exercised for real. BTCB + * (vBTC) stands in for the (not-yet-listed) bStock collateral; the contract is collateral-agnostic. * - * Setup, end to end: - * 1. Build a genuine borrow position (supply collateral -> enterMarkets -> borrow). - * 2. MANIPULATE it into a real shortfall by dropping the collateral's liquidation threshold via the - * timelock (a governance parameter change) — no balance is faked. - * 3. Deploy the liquidator and run `liquidate`. Core's `liquidatorContract` is already set to the real - * Venus Liquidator on mainnet, so the repay is routed through it automatically (no gate config here). - * The Native swap is mocked (a signed firm-quote can't be obtained for synthetic fork amounts), exactly - * as it would be on any fork; everything else is real Core. + * Borrowers are impersonated REAL on-chain EOAs with no code. This is load-bearing for the native path: vBNB + * disburses a borrow with a native `borrower.transfer`, i.e. a CALL into the borrower. hardhat's default + * accounts carry an EIP-7702 delegation (`0xEF01…`) on mainnet, and under the fork's London rules the leading + * `0xEF` is an invalid opcode, so a native disbursement into them reverts. Codeless EOAs sidestep that. + * + * Covered behavior: + * 1. Native BNB debt (vBNB): WBNB is unwrapped for exactly the repay, the real gate accepts the payable + * native repay, seized BTCB is sold BTCB->USDT->WBNB on real PancakeSwap, proceeds are kept as WBNB, and + * no native BNB is stranded (the real WBNB.withdraw 2300-gas transfer survives the proxy receive()). + * 2. Native BNB adverse move: when realized WBNB proceeds would breach minOut, the whole tx reverts + * (InsufficientOut) and rolls back — the borrow is untouched and the WBNB repay inventory is fully intact. + * 3. Thin liquidity (CAKE): a genuinely thin USDT->CAKE route still settles when minOut tracks the live quote. */ const A = { COMPTROLLER: "0xfD36E2c2a6789Db23113685031d7F16329158384", - VCOLLATERAL: process.env.FORK_VCOLLATERAL || "0xecA88125a5ADbe82614ffC12D0DB554E2e2867C8", // vUSDC - VDEBT: process.env.FORK_VDEBT || "0xfD5840Cd36d94D7229439859C0112a4185BC0255", // vUSDT - COLLATERAL_WHALE: process.env.FORK_COLLATERAL_WHALE || "0xF977814e90dA44bFA03b6295A0616a897441aceC", - DEBT_WHALE: process.env.FORK_DEBT_WHALE || "0xF977814e90dA44bFA03b6295A0616a897441aceC", TIMELOCK: "0x939bD8d64c0A9583A7Dcea9933f7b21697ab6396", ACM: "0x4788629ABc6cFCA10F9f969efdEAa1cF70c23555", + BTCB_WHALE: process.env.FORK_BTCB_WHALE || "0xF977814e90dA44bFA03b6295A0616a897441aceC", // Binance hot wallet + VBNB: "0xA07c5b74C9B40447a954e1466938b865b6BBea36", + VWBNB: "0x6bCa74586218dB34cdB402295796b79663d816e9", +}; + +const TOK = { + USDT: "0x55d398326f99059fF775485246999027B3197955", + WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", + CAKE: "0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82", + BTCB: "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c", +}; + +const MKT = { + vBTC: "0x882C173bC7Ff3b7786CA16dfeD3DFFfb9Ee7847B", + vCAKE: "0x86aC3974e2BD0d60825230fa6F355fF11409df5c", }; +// Real on-chain EOAs (no 7702 delegation) used as liquidation targets. Impersonated, not signed for. +const BNB_BORROWER = "0x33C6476F88eeA28D7E7900F759B4597704Ef95B7"; +const CAKE_BORROWER = ethers.utils.getAddress("0x000000000000000000000000000000000ca6e001"); + +const PCS_ROUTER = "0x10ED43C718714eb63d5aA57B78B54704E256024E"; +const ONE = parseUnits("1", 18); + const VTOKEN_ABI = [ "function underlying() view returns (address)", "function mint(uint256) returns (uint256)", "function borrow(uint256) returns (uint256)", - "function balanceOf(address) view returns (uint256)", - "function borrowBalanceCurrent(address) returns (uint256)", + "function borrowBalanceStored(address) view returns (uint256)", "function exchangeRateStored() view returns (uint256)", ]; const COMPTROLLER_ABI = [ "function enterMarkets(address[]) returns (uint256[])", - "function getAccountLiquidity(address) view returns (uint256,uint256,uint256)", "function liquidateCalculateSeizeTokens(address,address,uint256) view returns (uint256,uint256)", "function setCollateralFactor(address,uint256,uint256) returns (uint256)", ]; const ERC20_ABI = [ - "function approve(address,uint256) returns (bool)", "function transfer(address,uint256) returns (bool)", + "function approve(address,uint256) returns (bool)", "function balanceOf(address) view returns (uint256)", - "function decimals() view returns (uint8)", +]; +const WBNB_ABI = ["function deposit() payable", "function transfer(address,uint256) returns (bool)"]; +const PCS_ABI = [ + "function swapExactTokensForTokens(uint256,uint256,address[],address,uint256) returns (uint256[])", + "function getAmountsOut(uint256,address[]) view returns (uint256[])", ]; const test = () => { - describe("BStockLiquidator — bscmainnet fork (real Venus Core)", () => { + describe("BStockLiquidator — bscmainnet fork, real PancakeSwap + real gate under stress", () => { let owner: SignerWithAddress; - let borrower: SignerWithAddress; - let timelock: SignerWithAddress; - let comptroller: Contract, vCol: Contract, vDebt: Contract, col: Contract, debt: Contract; - let router: Contract, liq: Contract; - let colDec: number, debtDec: number; + let comptroller: Contract, vBtc: Contract, btcb: Contract, cake: Contract, wbnb: Contract, pcs: Contract; - const REPAY = parseUnits("1000", 18); + const BTCB_COLLATERAL = parseEther("0.2"); // per borrower, ample for these borrows + const BNB_BORROW = parseEther("2"); + const BNB_REPAY = parseEther("0.7"); // < closeFactor * borrow + const CAKE_BORROW = parseEther("1000"); + const CAKE_REPAY = parseEther("300"); before(async () => { - [owner, borrower] = await ethers.getSigners(); - timelock = await initMainnetUser(A.TIMELOCK, parseEther("10")); + [owner] = await ethers.getSigners(); + await setBalance(owner.address, parseEther("100")); // BNB to wrap for the native-debt WBNB inventory comptroller = new ethers.Contract(A.COMPTROLLER, COMPTROLLER_ABI, owner); - vCol = new ethers.Contract(A.VCOLLATERAL, VTOKEN_ABI, owner); - vDebt = new ethers.Contract(A.VDEBT, VTOKEN_ABI, owner); - col = new ethers.Contract(await vCol.underlying(), ERC20_ABI, owner); - debt = new ethers.Contract(await vDebt.underlying(), ERC20_ABI, owner); - colDec = await col.decimals(); - debtDec = await debt.decimals(); - - // --- access: grant the timelock the ACM perm to drop the collateral factor (for shortfall) --- + vBtc = new ethers.Contract(MKT.vBTC, VTOKEN_ABI, owner); + btcb = new ethers.Contract(TOK.BTCB, ERC20_ABI, owner); + cake = new ethers.Contract(TOK.CAKE, ERC20_ABI, owner); + wbnb = new ethers.Contract(TOK.WBNB, WBNB_ABI, owner); + pcs = new ethers.Contract(PCS_ROUTER, PCS_ABI, owner); + + // Build both borrows at the still-normal BTCB collateral factor, BEFORE dropping it. + const btcbWhale = await initMainnetUser(A.BTCB_WHALE, parseEther("100")); + for (const [account, vDebt, amount] of [ + [BNB_BORROWER, A.VBNB, BNB_BORROW], + [CAKE_BORROWER, MKT.vCAKE, CAKE_BORROW], + ] as [string, string, BigNumber][]) { + expect(await ethers.provider.getCode(account)).to.equal("0x"); // codeless: native disbursement is safe + const borrower = await initMainnetUser(account, parseEther("10")); + await btcb.connect(btcbWhale).transfer(account, BTCB_COLLATERAL); + await btcb.connect(borrower).approve(vBtc.address, BTCB_COLLATERAL); + await vBtc.connect(borrower).mint(BTCB_COLLATERAL); + await comptroller.connect(borrower).enterMarkets([vBtc.address]); + await new ethers.Contract(vDebt, VTOKEN_ABI, borrower).borrow(amount); + } + + // Drop the BTCB threshold hard -> both borrows are underwater regardless of price. + const timelock = await initMainnetUser(A.TIMELOCK, parseEther("10")); const acm = IAccessControlManagerV8__factory.connect(A.ACM, timelock); await acm.giveCallPermission(A.COMPTROLLER, "setCollateralFactor(address,uint256,uint256)", A.TIMELOCK); + await comptroller.connect(timelock).setCollateralFactor(MKT.vBTC, parseUnits("0.02", 18), parseUnits("0.02", 18)); + }); - // --- build a real borrow position --- - const colWhale = await initMainnetUser(A.COLLATERAL_WHALE, parseEther("10")); - const supply = parseUnits("10000", colDec); - await col.connect(colWhale).transfer(borrower.address, supply); - await col.connect(borrower).approve(vCol.address, supply); - expect(await vCol.connect(borrower).callStatic.mint(supply)).to.equal(0); - await vCol.connect(borrower).mint(supply); - await comptroller.connect(borrower).enterMarkets([vCol.address]); - await vDebt.connect(borrower).borrow(parseUnits("8000", debtDec)); - - // --- MANIPULATE: drop the liquidation threshold so the account is underwater --- - // setCollateralFactor(vToken, CF, LT): LT 0.50 -> max borrow 5000 < 8000 borrowed => shortfall. - await comptroller - .connect(timelock) - .setCollateralFactor(vCol.address, parseUnits("0.5", 18), parseUnits("0.5", 18)); - const [, , shortfall] = await comptroller.getAccountLiquidity(borrower.address); - expect(shortfall).to.be.gt(0); // position manipulated into shortfall - - // --- deploy the liquidator (routes through Core's existing Venus Liquidator automatically) --- + async function deployLiq() { const Factory = await ethers.getContractFactory("BStockLiquidator"); - liq = await upgrades.deployProxy(Factory, [owner.address], { - constructorArgs: [A.COMPTROLLER], + const liq = await upgrades.deployProxy(Factory, [owner.address], { + constructorArgs: [A.COMPTROLLER, A.VBNB, A.VWBNB, TOK.WBNB], unsafeAllow: ["constructor", "state-variable-immutable"], }); + await liq.connect(owner).setRouter(PCS_ROUTER, true); + return liq; + } + + // Real two-hop PancakeSwap calldata (BTCB -> USDT -> path2 tail) + the live expected final out. + // hop-1 amountIn is under-shot 20% so the fixed-amount calldata always fits under the on-chain approval + // (the gate hands the liquidator ~98% of the gross seize; 80% leaves margin). + async function buildTwoHop(vDebt: string, repay: BigNumber, liq: string, path2: string[]) { + const [, seizeTokens]: BigNumber[] = await comptroller.liquidateCalculateSeizeTokens(vDebt, MKT.vBTC, repay); + const rate: BigNumber = await vBtc.exchangeRateStored(); + const x1 = seizeTokens.mul(rate).div(ONE).mul(80).div(100); // BTCB to sell on hop 1 + const mid: BigNumber = (await pcs.getAmountsOut(x1, [TOK.BTCB, TOK.USDT]))[1]; + const x2 = mid.mul(999).div(1000); + const deadline = Math.floor(Date.now() / 1000) + 3600; + return { + swapCalldata: pcs.interface.encodeFunctionData("swapExactTokensForTokens", [ + x1, + 0, + [TOK.BTCB, TOK.USDT], + liq, + deadline, + ]), + swapCalldata2: pcs.interface.encodeFunctionData("swapExactTokensForTokens", [x2, 0, path2, liq, deadline]), + expectedOut: (await pcs.getAmountsOut(x2, path2))[path2.length - 1] as BigNumber, + }; + } - // --- deploy + fund the mock Native router and the liquidator inventory --- - router = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); - await liq.connect(owner).setRouter(router.address, true); - const debtWhale = await initMainnetUser(A.DEBT_WHALE, parseEther("10")); - await debt.connect(debtWhale).transfer(router.address, parseUnits("2000", debtDec)); // USDT to pay the swap - await debt.connect(debtWhale).transfer(liq.address, REPAY); // USDT inventory to fund the repay + function bnbParams(liq: string, swapCalldata: string, swapCalldata2: string, minOut: BigNumber) { + return { + borrower: BNB_BORROWER, + vDebt: A.VBNB, + vBStock: MKT.vBTC, + repayAmount: BNB_REPAY, + router: PCS_ROUTER, + swapCalldata, + minOut, + router2: PCS_ROUTER, + swapCalldata2, + intermediateToken: TOK.USDT, + deadline: ethers.constants.MaxUint256, + }; + } + + it("native BNB adverse: reverts InsufficientOut and rolls back, leaving the borrow and inventory intact", async () => { + const liq = await deployLiq(); + await wbnb.deposit({ value: BNB_REPAY }); + await wbnb.transfer(liq.address, BNB_REPAY); // WBNB repay inventory + + const { swapCalldata, swapCalldata2, expectedOut } = await buildTwoHop(A.VBNB, BNB_REPAY, liq.address, [ + TOK.USDT, + TOK.WBNB, + ]); + // Demand 5% MORE WBNB than the pool can deliver (as if quoted before the price moved against us). + const params = bnbParams(liq.address, swapCalldata, swapCalldata2, expectedOut.mul(105).div(100)); + + const vBnb = new ethers.Contract(A.VBNB, VTOKEN_ABI, owner); + const borrowBefore: BigNumber = await vBnb.borrowBalanceStored(BNB_BORROWER); + await expect(liq.connect(owner).liquidate(params)).to.be.revertedWithCustomError(liq, "InsufficientOut"); + + // Full rollback: borrow not reduced, WBNB inventory untouched, no collateral seized, no BNB stranded. + expect(await vBnb.borrowBalanceStored(BNB_BORROWER)).to.be.gte(borrowBefore); + expect(await new ethers.Contract(TOK.WBNB, ERC20_ABI, owner).balanceOf(liq.address)).to.equal(BNB_REPAY); + expect(await btcb.balanceOf(liq.address)).to.equal(0); + expect(await ethers.provider.getBalance(liq.address)).to.equal(0); }); - it("atomically repays, seizes, redeems, sells, and books a profit", async () => { - // Precompute the seized collateral -> raw underlying the contract will hold after redeem (for minOut). - const [, seizeTokens]: BigNumber[] = await comptroller.liquidateCalculateSeizeTokens( - vDebt.address, - vCol.address, - REPAY, + it("native BNB: unwraps the repay, repays vBNB natively through the gate, sells to WBNB, strands nothing", async () => { + const liq = await deployLiq(); + await wbnb.deposit({ value: BNB_REPAY }); + await wbnb.transfer(liq.address, BNB_REPAY); + + const { swapCalldata, swapCalldata2, expectedOut } = await buildTwoHop(A.VBNB, BNB_REPAY, liq.address, [ + TOK.USDT, + TOK.WBNB, + ]); + const params = bnbParams(liq.address, swapCalldata, swapCalldata2, expectedOut.mul(97).div(100)); + + const vBnb = new ethers.Contract(A.VBNB, VTOKEN_ABI, owner); + const borrowBefore: BigNumber = await vBnb.borrowBalanceStored(BNB_BORROWER); + const out: BigNumber = await liq.connect(owner).callStatic.liquidate(params); + expect(out).to.be.gte(params.minOut); + + const tx = await liq.connect(owner).liquidate(params); + const rcpt = await tx.wait(); + console.log(` native BNB liquidation gas: ${rcpt.gasUsed.toString()}`); + + expect(borrowBefore.sub(await vBnb.borrowBalanceStored(BNB_BORROWER))).to.be.closeTo( + BNB_REPAY, + BNB_REPAY.div(50), ); - const rate: BigNumber = await vCol.exchangeRateStored(); - const redeemed = seizeTokens.mul(rate).div(parseUnits("1", 18)); + // Proceeds retained as WBNB (>= minOut), no native BNB stranded on the proxy. + expect(await new ethers.Contract(TOK.WBNB, ERC20_ABI, owner).balanceOf(liq.address)).to.be.gte(params.minOut); + expect(await ethers.provider.getBalance(liq.address)).to.equal(0); + }); + + it("thin liquidity: settles a real USDT->CAKE route at live slippage when minOut tracks the quote", async () => { + const liq = await deployLiq(); + const borrower = await initMainnetUser(CAKE_BORROWER, parseEther("10")); + await cake.connect(borrower).transfer(liq.address, CAKE_REPAY); // CAKE repay inventory - // swapAll sells whatever the contract actually holds post-redeem (no fixed-amount mismatch). - const swapCalldata = router.interface.encodeFunctionData("swapAll", [col.address, debt.address, liq.address]); + const { swapCalldata, swapCalldata2, expectedOut } = await buildTwoHop(MKT.vCAKE, CAKE_REPAY, liq.address, [ + TOK.USDT, + TOK.CAKE, + ]); const params = { - borrower: borrower.address, - vDebt: vDebt.address, - vBStock: vCol.address, - repayAmount: REPAY, - router: router.address, + borrower: CAKE_BORROWER, + vDebt: MKT.vCAKE, + vBStock: MKT.vBTC, + repayAmount: CAKE_REPAY, + router: PCS_ROUTER, swapCalldata, - // Loose floor: the real Venus Liquidator keeps a treasury cut of the seized collateral, so - // proceeds land a few % under the full redeemed value. Profit is asserted strictly below. - minOut: redeemed.mul(90).div(100), - // Single hop (USDT debt): no second AMM leg. - router2: ethers.constants.AddressZero, - swapCalldata2: "0x", - intermediateToken: ethers.constants.AddressZero, + minOut: expectedOut.mul(97).div(100), + router2: PCS_ROUTER, + swapCalldata2, + intermediateToken: TOK.USDT, deadline: ethers.constants.MaxUint256, }; - const borrowBefore = await vDebt.connect(owner).callStatic.borrowBalanceCurrent(borrower.address); - const debtBefore = await debt.balanceOf(liq.address); // = REPAY inventory - const out = await liq.connect(owner).callStatic.liquidate(params); + const vCake = new ethers.Contract(MKT.vCAKE, VTOKEN_ABI, owner); + const borrowBefore: BigNumber = await vCake.borrowBalanceStored(CAKE_BORROWER); + const out: BigNumber = await liq.connect(owner).callStatic.liquidate(params); expect(out).to.be.gte(params.minOut); + await liq.connect(owner).liquidate(params); - await expect(liq.connect(owner).liquidate(params)) - .to.emit(liq, "Liquidated") - .withArgs(borrower.address, vCol.address, REPAY, anyValue, anyValue, false); - - // Proceeds match the simulated `out` (dust differs by one block of accrual). - const debtAfter = await debt.balanceOf(liq.address); - const proceeds = debtAfter.sub(debtBefore).add(REPAY); - expect(proceeds).to.be.closeTo(out, parseUnits("1", 15)); - - // Strict: a profit was booked, the borrow shrank by the repay, nothing is left stuck. - expect(proceeds).to.be.gt(REPAY); // incentive captured (proceeds exceed the repay funded) - expect(debtAfter).to.be.gt(debtBefore); - const borrowAfter = await vDebt.connect(owner).callStatic.borrowBalanceCurrent(borrower.address); - expect(borrowBefore.sub(borrowAfter)).to.be.closeTo(REPAY, parseUnits("1", 15)); - expect(await col.balanceOf(liq.address)).to.equal(0); // no collateral left stuck + expect(borrowBefore.sub(await vCake.borrowBalanceStored(CAKE_BORROWER))).to.be.closeTo( + CAKE_REPAY, + CAKE_REPAY.div(50), + ); }); }); }; if (FORK_MAINNET) { - forking(104930000, test); + forking(107565173, test); } From b7df9596999cfedd52607c07f596fc42913cd667 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 19:10:42 +0530 Subject: [PATCH 22/78] docs(bstock): add operator runbook for liquidation scripts Document the three manual-trigger scripts (atomic, safe-fallback, native-smoke) with commands, env tables, and operator gotchas so the team can drive liquidations without reading the source. --- scripts/bstock/README.md | 103 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 scripts/bstock/README.md diff --git a/scripts/bstock/README.md b/scripts/bstock/README.md new file mode 100644 index 000000000..93c15f6ac --- /dev/null +++ b/scripts/bstock/README.md @@ -0,0 +1,103 @@ +# bStock Liquidation Scripts + +Operator runbook for liquidating bStock (tokenized-stock) collateral in Venus Core. Three entrypoints, +one shared goal: repay a borrower's debt, seize their bStock, and offload it. + +| Script | Path | What it does | Chain writes? | +|---|---|---|---| +| **Atomic** | `atomic-liquidate.ts` | Primary path. Drives the on-chain `BStockLiquidator` — repay, seize, redeem, and sell bStock in ONE tx via a Native RFQ quote (+ optional AMM hop). | Yes | +| **Safe fallback** | `safe-fallback.ts` | Backstop when Native is unavailable (API down / halt / weekend / thin depth). Emits a Safe{Wallet} batch JSON; signers repay from Safe funds and ship raw bStock to Binance. | No — writes JSON | +| **Native smoke** | `native-smoke.ts` | Pre-flight check. Prints the Native orderbook + a live firm-quote (price, spread, TTL). No chain interaction. | No | + +Pick **Atomic** first. Fall back to **Safe** only if the Native quote path fails. + +--- + +## Prerequisites + +- **Native API key** (`NATIVE_API_KEY`) for anything that fetches a quote. Never commit it. +- **Deployed `BStockLiquidator`** (see `deploy/019-deploy-bstock-liquidator.ts`). Its owner must have run: + - `setRouter(router, true)` for every router the swap will touch (Native RFQ router, and the AMM + router for non-USDT debt). The scripts **abort** if a router isn't allowlisted. + - `setOperator(caller, true)` for the account that submits `liquidate` / `flashLiquidate`. +- **Funding** for `MODE=inventory`: the liquidator must hold ≥ `REPAY_AMOUNT` of the debt asset. + `MODE=flash` borrows instead (no pre-funding). + +--- + +## 1. Native smoke — verify the quote path + +```bash +NATIVE_API_KEY=... TOKEN=TSLAB AMOUNT=5 npx hardhat run scripts/bstock/native-smoke.ts +``` + +Run this first during an incident to confirm Native is live and see the current price/spread/TTL. +`TOKEN` = any bStock in the orderbook (default `TSLAB`), `AMOUNT` = bStock to sell (default `1`). + +--- + +## 2. Atomic liquidation — primary path + +```bash +NATIVE_API_KEY=... LIQUIDATOR=0x.. BORROWER=0x.. VBSTOCK=0x.. VDEBT=0x.. REPAY_AMOUNT=5000 \ + npx hardhat run scripts/bstock/atomic-liquidate.ts --network bscmainnet +``` + +Flow: precompute the exact seize → fetch a Native firm-quote (bStock→USDT) with `from_address = the +contract` → for non-USDT debt, append an AMM hop (USDT→debt) → call `liquidate`/`flashLiquidate`. + +**Always dry-run first** (`DRY_RUN=1`) — it `callStatic`s the settle with no send. + +**Two-hop (non-USDT debt): submit through Venus's PRIVATE RPC.** Hop 2 is a public-mempool AMM swap; +`minOut` bounds loss but not sandwich-induced reverts. Prefer `MODE=flash` so a revert only burns gas. + +### Env + +| Var | Req | Default | Notes | +|---|---|---|---| +| `LIQUIDATOR` | ✓ | | Deployed `BStockLiquidator` address | +| `BORROWER` | ✓ | | Account to liquidate (must have shortfall) | +| `VBSTOCK` | ✓ | | bStock collateral market (e.g. vTSLAB) | +| `VDEBT` | ✓ | | Borrowed market to repay (e.g. vUSDT) | +| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | +| `MODE` | | `inventory` | `inventory` (own funds) or `flash` (Venus flash-loan) | +| `DRY_RUN` | | | `1` → callStatic only, sends nothing | +| `SLIPPAGE` | | `0.5` | Native slippage % | +| `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) | +| `AMM_PROVIDER` | | `kyberswap` | Hop-2 route for non-USDT debt: `kyberswap` / `openocean` / `pcsv2` | +| `WBNB_ADDR` | | BSC WBNB | Only for a vBNB debt market (native BNB auto-detected; contract unwraps) | + +_Fork/local testing:_ `MOCK_NATIVE="router:calldata"` (hop 1), `MOCK_AMM` (hop 2), `MOCK_OUT` (final +debt out), `IMPERSONATE=0x..` to run as an operator. + +--- + +## 3. Safe fallback — manual multisig path + +Use when Native is unavailable. Reads chain state and writes a Safe Transaction Builder batch JSON — +**it sends nothing.** + +```bash +BORROWER=0x.. VBSTOCK=0x.. VDEBT=0x.. REPAY_AMOUNT=5000 TARGET=0x.. \ + npx ts-node scripts/bstock/safe-fallback.ts +``` + +Then: **Safe → Apps → Transaction Builder → Load** the JSON, review, sign, execute. + +The batch is 4 txs (approve → liquidateBorrow → redeem → transfer): the Safe repays from its own +funds, seizes the bStock, and ships raw bStock to `TARGET` (Binance top-up / custody) for finance to +offload on the CEX. + +| Var | Req | Default | Notes | +|---|---|---|---| +| `BORROWER` | ✓ | | Account to liquidate | +| `VBSTOCK` | ✓ | | bStock collateral market | +| `VDEBT` | ✓ | | Borrowed market to repay | +| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | +| `TARGET` | ✓ | | Binance top-up / custody address for the bStock (or `ALLOW_PLACEHOLDER=1` for a draft) | +| `SAFE` | | `0xdc6E…2029` | Executing Safe | +| `RPC_URL` | | public dataseed | BSC RPC | +| `OUT` | | `out/bstock-safe-fallback.json` | Output path | + +> The batch is a **snapshot** at the current block. If the borrower's position changes before the Safe +> executes, **regenerate** — a stale redeem/transfer that exceeds the seized balance reverts the batch. From 6319593e71338b6fe70ddeb5ce79ee083d49e4d8 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 18:58:07 +0530 Subject: [PATCH 23/78] test(bstock): add fork helper to list a real bStock collateral market Deploys a bStock ERC20 + real Venus vToken, lists it on the live Core diamond via timelock/ACM, wires a ResilientOracle direct price, sets collateral factor, supply cap, action-unpause, and the per-market liquidation incentive (diamond default 0 zeroes the seize). Plus seeded PRNG + balance-slot helpers. --- tests/hardhat/Fork/helpers/bstock.ts | 268 +++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 tests/hardhat/Fork/helpers/bstock.ts diff --git a/tests/hardhat/Fork/helpers/bstock.ts b/tests/hardhat/Fork/helpers/bstock.ts new file mode 100644 index 000000000..96e3d4afb --- /dev/null +++ b/tests/hardhat/Fork/helpers/bstock.ts @@ -0,0 +1,268 @@ +// Helpers for the bStock backstop-liquidator fork suite. +// +// The centerpiece is `listBStockMarket`: on a bscmainnet fork it deploys a fresh bStock ERC20 + a REAL +// Venus vToken, lists that vToken on the LIVE Core Pool diamond (via timelock/ACM impersonation), wires +// a price into the real ResilientOracle, and configures collateral factor / supply cap / action pausing — +// i.e. it stands up a genuine bStock collateral market the same way governance would. Everything else in +// the suite (borrower positions, liquidations) then runs against that real market and the real gate. +// +// bStock (ERC-8056 tokenized stock) has NO on-chain AMM liquidity, so a freshly listed market cannot be +// offloaded on PancakeSwap. In production the bStock->USDT leg is an off-chain Native RFQ (MM-signed firm +// quote); on a fork we cannot reproduce that, so hop-1 is a MockNativeRouter pre-funded with USDT that +// models the firm quote, while hop-2 (USDT->debt) uses REAL PancakeSwap where liquidity actually exists. +import { setStorageAt } from "@nomicfoundation/hardhat-network-helpers"; +import { BigNumber, Contract } from "ethers"; +import { parseUnits } from "ethers/lib/utils"; +import { ethers } from "hardhat"; + +import { initMainnetUser } from "../utils"; + +/* ------------------------------------------------------------------ */ +/* bscmainnet addresses */ +/* ------------------------------------------------------------------ */ + +export const A = { + COMPTROLLER: "0xfD36E2c2a6789Db23113685031d7F16329158384", + TIMELOCK: "0x939bD8d64c0A9583A7Dcea9933f7b21697ab6396", + ACM: "0x4788629ABc6cFCA10F9f969efdEAa1cF70c23555", + PCS_ROUTER: "0x10ED43C718714eb63d5aA57B78B54704E256024E", + VBNB: "0xA07c5b74C9B40447a954e1466938b865b6BBea36", + VWBNB: "0x6bCa74586218dB34cdB402295796b79663d816e9", + // A real listed market used only to read a live interest-rate model. + VBTC: "0x882C173bC7Ff3b7786CA16dfeD3DFFfb9Ee7847B", + // Oracles are addressed directly (the diamond's oracle() getter is not reliably routed at the fork). + RESILIENT_ORACLE: "0x6592b5DE802159F3E74B2486b091D11a8256ab8A", + CHAINLINK_ORACLE: "0x1B2103441A0A108daD8848D8F5d790e4D402921F", +}; + +export const TOK = { + USDT: "0x55d398326f99059fF775485246999027B3197955", + WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", + CAKE: "0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82", + BTCB: "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c", +}; + +export const ONE = parseUnits("1", 18); +export const ZERO = ethers.constants.AddressZero; + +// Core Pool Comptroller "Action" enum (see IComptroller.Action). +export const Action = { MINT: 0, REDEEM: 1, BORROW: 2, REPAY: 3, SEIZE: 4, LIQUIDATE: 5, ENTER_MARKET: 7 }; + +/* ------------------------------------------------------------------ */ +/* minimal ABIs */ +/* ------------------------------------------------------------------ */ + +const ACM_ABI = ["function giveCallPermission(address contractAddress, string functionSig, address account)"]; +const COMPTROLLER_ABI = [ + "function oracle() view returns (address)", + "function getAllMarkets() view returns (address[])", + "function markets(address) view returns (bool isListed, uint256 collateralFactorMantissa, bool isVenus)", + "function actionPaused(address,uint8) view returns (bool)", + "function getAccountLiquidity(address) view returns (uint256,uint256,uint256)", + "function liquidatorContract() view returns (address)", + "function closeFactorMantissa() view returns (uint256)", + "function liquidateCalculateSeizeTokens(address,address,uint256) view returns (uint256,uint256)", + "function _supportMarket(address) returns (uint256)", + "function setCollateralFactor(address,uint256,uint256) returns (uint256)", + "function _setMarketSupplyCaps(address[],uint256[])", + "function _setActionsPaused(address[],uint8[],bool)", + "function _setForcedLiquidation(address,bool)", + "function setLiquidationIncentive(address,uint256) returns (uint256)", + "function enterMarkets(address[]) returns (uint256[])", +]; +const RESILIENT_ORACLE_ABI = [ + "function getUnderlyingPrice(address) view returns (uint256)", + "function getTokenConfig(address) view returns (tuple(address asset, address[3] oracles, bool[3] enableFlagsForOracles, bool cachingEnabled))", + "function setTokenConfig(tuple(address asset, address[3] oracles, bool[3] enableFlagsForOracles, bool cachingEnabled))", +]; +const CHAINLINK_ORACLE_ABI = ["function setDirectPrice(address asset, uint256 price)"]; +export const VTOKEN_ABI = [ + "function underlying() view returns (address)", + "function mint(uint256) returns (uint256)", + "function borrow(uint256) returns (uint256)", + "function borrowBalanceStored(address) view returns (uint256)", + "function exchangeRateStored() view returns (uint256)", + "function interestRateModel() view returns (address)", + "function isFlashLoanEnabled() view returns (bool)", +]; +export const ERC20_ABI = [ + "function decimals() view returns (uint8)", + "function symbol() view returns (string)", + "function balanceOf(address) view returns (uint256)", + "function approve(address,uint256) returns (bool)", + "function transfer(address,uint256) returns (bool)", +]; + +/* ------------------------------------------------------------------ */ +/* deterministic PRNG (mulberry32) */ +/* ------------------------------------------------------------------ */ + +// Tiny seeded PRNG so fuzz runs replay exactly from a seed (no runtime dependency). Returns a +// function yielding a float in [0, 1). Log the seed in the test so a failing run is reproducible. +export function mulberry32(seed: number): () => number { + let a = seed >>> 0; + return () => { + a |= 0; + a = (a + 0x6d2b79f5) | 0; + let t = Math.imul(a ^ (a >>> 15), 1 | a); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +// Pick a random BigNumber in [min, max] using the PRNG (bounded-range, uniform enough for fuzzing). +export function randBn(rnd: () => number, min: BigNumber, max: BigNumber): BigNumber { + const span = max.sub(min); + if (span.lte(0)) return min; + const bps = Math.floor(rnd() * 10001); // 0..10000 + return min.add(span.mul(bps).div(10000)); +} + +/* ------------------------------------------------------------------ */ +/* storage-balance seeding */ +/* ------------------------------------------------------------------ */ + +// Brute-force the ERC20 balances mapping slot, then write a balance directly — used to fund the mock +// Native router with USDT and to top up thin debt markets without needing a whale. +export async function findBalanceSlot(token: string): Promise { + const probe = "0x" + "ba1".padStart(40, "0"); + const probeAmount = BigNumber.from("1234567890"); + const erc20 = new ethers.Contract(token, ERC20_ABI, ethers.provider); + for (let slot = 0; slot <= 12; slot++) { + const storageSlot = ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode(["address", "uint256"], [probe, slot]), + ); + const prev = await ethers.provider.getStorageAt(token, storageSlot); + await setStorageAt(token, storageSlot, ethers.utils.hexZeroPad(probeAmount.toHexString(), 32)); + try { + const bal = await erc20.balanceOf(probe); + await setStorageAt(token, storageSlot, prev); + if (bal.eq(probeAmount)) return slot; + } catch { + await setStorageAt(token, storageSlot, prev); + } + } + return null; +} + +export async function setTokenBalance(token: string, account: string, amount: BigNumber, slot: number) { + const storageSlot = ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode(["address", "uint256"], [account, slot]), + ); + await setStorageAt(token, storageSlot, ethers.utils.hexZeroPad(amount.toHexString(), 32)); +} + +/* ------------------------------------------------------------------ */ +/* ACM-gated call helper */ +/* ------------------------------------------------------------------ */ + +// Grant `sig` on `target` to the timelock, returning a timelock signer that can then make the call. +export async function asTimelockWith(sigs: [string, string][]): Promise { + const timelock = await initMainnetUser(A.TIMELOCK, parseUnits("100", 18)); + const acm = new ethers.Contract(A.ACM, ACM_ABI, timelock); + for (const [target, sig] of sigs) { + await acm.giveCallPermission(target, sig, A.TIMELOCK); + } + return timelock; +} + +/* ------------------------------------------------------------------ */ +/* deploy the BStockLiquidator */ +/* ------------------------------------------------------------------ */ + +export async function deployLiq(owner: any): Promise { + const { upgrades } = await import("hardhat"); + const Factory = await ethers.getContractFactory("BStockLiquidator"); + const liq = await upgrades.deployProxy(Factory, [owner.address], { + constructorArgs: [A.COMPTROLLER, A.VBNB, A.VWBNB, TOK.WBNB], + unsafeAllow: ["constructor", "state-variable-immutable"], + }); + return liq as unknown as Contract; +} + +/* ------------------------------------------------------------------ */ +/* list a real bStock market */ +/* ------------------------------------------------------------------ */ + +export interface BStockMarket { + bStock: Contract; // raw ERC20 (18-dec) + vBStock: Contract; // the listed Venus vToken (8-dec) + price: BigNumber; // USD price mantissa (1e18) currently set on the oracle + setPrice: (p: BigNumber) => Promise; // crash/raise the bStock oracle price + setCollateralFactor: (cf: BigNumber) => Promise; // drop CF to force shortfall +} + +// Deploy + list a genuine bStock collateral market on the live Core Pool diamond and price it in the +// real ResilientOracle. Order matters: oracle price must exist BEFORE setCollateralFactor (Compound +// blocks a CF on an unpriced asset); _supportMarket itself needs no price. +export async function listBStockMarket(owner: any, initialPriceUsd = parseUnits("250", 18)): Promise { + const resilientAddr = A.RESILIENT_ORACLE; + const chainlinkAddr = A.CHAINLINK_ORACLE; + + // 1. Raw bStock token (mintable 18-dec ERC20 stands in for the ERC-8056 stock token on the fork). + const bStock = await (await ethers.getContractFactory("MockMintableERC20")).deploy("Tesla bStock", "TSLAB", 18); + + // 2. A real Venus vToken for it, reusing the live interest-rate model from an existing market. + const irm: string = await new ethers.Contract(A.VBTC, VTOKEN_ABI, owner).interestRateModel(); + const delegate = await (await ethers.getContractFactory("VBep20Delegate")).deploy(); + const initialExchangeRate = parseUnits("2", 26); // 0.02 * 10^(18+18-8), Compound standard for 8-dec vToken + const vBStock = await ( + await ethers.getContractFactory("VBep20Delegator") + ).deploy( + bStock.address, + A.COMPTROLLER, + irm, + initialExchangeRate, + "Venus bStock", + "vBSTOCK", + 8, + owner.address, // admin + delegate.address, + "0x", + ); + const vBStockAsVToken = new ethers.Contract(vBStock.address, VTOKEN_ABI, owner); + + // 3. Oracle: MAIN -> live ChainlinkOracle, pivot/fallback disabled; then a direct price (no feed needed). + const tl = await asTimelockWith([ + [resilientAddr, "setTokenConfig(TokenConfig)"], + [chainlinkAddr, "setDirectPrice(address,uint256)"], + [A.COMPTROLLER, "_supportMarket(address)"], + [A.COMPTROLLER, "setCollateralFactor(address,uint256,uint256)"], + [A.COMPTROLLER, "_setMarketSupplyCaps(address[],uint256[])"], + [A.COMPTROLLER, "_setActionsPaused(address[],uint8[],bool)"], + [A.COMPTROLLER, "setLiquidationIncentive(address,uint256)"], + ]); + await new ethers.Contract(resilientAddr, RESILIENT_ORACLE_ABI, tl).setTokenConfig({ + asset: bStock.address, + oracles: [chainlinkAddr, ZERO, ZERO], + enableFlagsForOracles: [true, false, false], + cachingEnabled: false, + }); + const chainlink = new ethers.Contract(chainlinkAddr, CHAINLINK_ORACLE_ABI, tl); + await chainlink.setDirectPrice(bStock.address, initialPriceUsd); + + // 4. List the market, set a healthy collateral factor, lift the supply cap, unpause the actions. + const cAsTl = new ethers.Contract(A.COMPTROLLER, COMPTROLLER_ABI, tl); + await cAsTl._supportMarket(vBStock.address); + await cAsTl._setMarketSupplyCaps([vBStock.address], [ethers.constants.MaxUint256.div(2)]); + await cAsTl._setActionsPaused( + [vBStock.address], + [Action.MINT, Action.BORROW, Action.SEIZE, Action.LIQUIDATE, Action.ENTER_MARKET], + false, + ); + await cAsTl.setCollateralFactor(vBStock.address, parseUnits("0.6", 18), parseUnits("0.6", 18)); + // Per-market liquidation incentive (diamond default is 0 for a fresh market, which zeroes the seize). + await cAsTl.setLiquidationIncentive(vBStock.address, parseUnits("1.1", 18)); + + return { + bStock, + vBStock: vBStockAsVToken, + price: initialPriceUsd, + setPrice: async (p: BigNumber) => { + await chainlink.setDirectPrice(bStock.address, p); + }, + setCollateralFactor: async (cf: BigNumber) => { + await cAsTl.setCollateralFactor(vBStock.address, cf, cf); + }, + }; +} From 0a3f35502c59dbcc4e9a4ba4527e607e36c1a0f6 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 20:03:41 +0530 Subject: [PATCH 24/78] test(bstock): harden liquidator suite with reentrancy, deadline, and fork fuzz coverage - add MockReentrantRouter and assert nonReentrant blocks re-entry into liquidate/flashLiquidate while the outer settle succeeds - add deadline-boundary and gate-approval-reset assertions - add fork helpers: underwater-borrower factory, swap-hop builders, settle/rollback invariants - add curated fork scenarios (native BNB debt, two-hop PCS, forced liquidation, minOut-breach rollback) + dynamic debt-market sweep - add seeded and fast-check property fuzz over repay/minOut --- contracts/test/BStockLiquidationMocks.sol | 42 + package.json | 1 + tests/hardhat/BStockLiquidator.ts | 80 +- tests/hardhat/Fork/BStockLiquidatorFork.ts | 1011 +++++++++++++++----- tests/hardhat/Fork/helpers/bstock.ts | 195 +++- yarn.lock | 17 + 6 files changed, 1090 insertions(+), 256 deletions(-) diff --git a/contracts/test/BStockLiquidationMocks.sol b/contracts/test/BStockLiquidationMocks.sol index c65d9f9a2..b227f1e47 100644 --- a/contracts/test/BStockLiquidationMocks.sol +++ b/contracts/test/BStockLiquidationMocks.sol @@ -210,6 +210,48 @@ contract MockNativeRouter { } } +/// @dev Malicious router: during the swap hop it re-enters the liquidator (via a preconfigured call) +/// before completing a normal swap. Used to prove the `nonReentrant` guard blocks re-entry while +/// the outer liquidation still settles. Must be allowlisted AND set as an operator so the re-entry +/// clears `onlyOperator` and actually reaches the reentrancy guard. +contract MockReentrantRouter { + uint256 public rate = 1e18; + address public target; // the liquidator to re-enter + bytes public reentryCalldata; // encoded liquidate/flashLiquidate call + bool public reentrySucceeded; // true iff the re-entry call returned success (guard FAILED) + bool public reentryAttempted; + + function configure(address target_, bytes calldata reentryCalldata_) external { + target = target_; + reentryCalldata = reentryCalldata_; + } + + function swap(address tokenIn, uint256 amountIn, address tokenOut, address to) external { + reentryAttempted = true; + // Attempt to re-enter; swallow the result so the OUTER swap/liquidation can still complete. + (bool ok, ) = target.call(reentryCalldata); + reentrySucceeded = ok; + require(ERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), "in pull failed"); + require(ERC20(tokenOut).transfer(to, (amountIn * rate) / 1e18), "out transfer failed"); + } + + /// @dev Same re-entry attempt, but pulls the caller's ENTIRE tokenIn balance and pays out — so the + /// OUTER liquidation actually settles (clearing minOut), letting the test observe that the + /// re-entry was blocked while the surrounding call succeeded. + function swapAll(address tokenIn, address tokenOut, address to) external { + reentryAttempted = true; + (bool ok, ) = target.call(reentryCalldata); + reentrySucceeded = ok; + uint256 amountIn = ERC20(tokenIn).balanceOf(msg.sender); + require(ERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), "in pull failed"); + require(ERC20(tokenOut).transfer(to, (amountIn * rate) / 1e18), "out transfer failed"); + } + + function setRate(uint256 r) external { + rate = r; + } +} + /// @dev Stand-in for the pool-wide Venus Liquidator (`liquidatorContract`). Pulls the repay from the /// caller and credits the seized collateral (repay * incentive) to the caller, minus a treasury cut. contract MockVenusLiquidator { diff --git a/package.json b/package.json index 0c48e58ca..a114b4a73 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "eslint": "^8.25.0", "eslint-config-prettier": "^8.5.0", "ethers": "^5.7.0", + "fast-check": "^4.8.0", "hardhat": "2.22.18", "hardhat-contract-sizer": "^2.8.0", "hardhat-dependency-compiler": "^1.2.1", diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index 55980de35..0bdea8966 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -192,6 +192,15 @@ describe("BStockLiquidator (atomic)", () => { expect(await usdt.balanceOf(venusLiq.address)).to.equal(REPAY); // vToken received only the flash repayment (principal + premium). expect(await usdt.balanceOf(vDebt.address)).to.equal(REPAY.add(premium)); + // Gate approval reset in flash mode too. + expect(await usdt.allowance(liq.address, venusLiq.address)).to.equal(0); + }); + + it("resets the gate approval on a partial pull in flash mode", async () => { + await usdt.mint(vDebt.address, REPAY); + await venusLiq.setPullMantissa(U("0.5")); // gate pulls only half the approved repay + await liq.connect(owner).flashLiquidate(params()); + expect(await usdt.allowance(liq.address, venusLiq.address)).to.equal(0); }); it("reverts the whole tx (flash unwinds) when the sale underdelivers", async () => { @@ -257,9 +266,10 @@ describe("BStockLiquidator (atomic)", () => { expect(await btcb.balanceOf(liq.address)).to.equal(OUT); // 5500 BTCB, profit = OUT - REPAY expect(await usdt.balanceOf(liq.address)).to.equal(0); // intermediate fully consumed expect(await bStock.balanceOf(liq.address)).to.equal(0); - // No standing approvals on either hop. + // No standing approvals on either hop, nor to the gate (approved in the debt asset, BTCB). expect(await bStock.allowance(liq.address, router.address)).to.equal(0); expect(await usdt.allowance(liq.address, amm.address)).to.equal(0); + expect(await btcb.allowance(liq.address, venusLiq.address)).to.equal(0); }); it("flash: flash-borrows BTCB, two-hop settles, repays principal + premium", async () => { @@ -591,4 +601,72 @@ describe("BStockLiquidator (atomic)", () => { expect(await usdt.balanceOf(liq.address)).to.equal(0); }); }); + + describe("deadline boundary", () => { + beforeEach(async () => { + await usdt.mint(liq.address, REPAY); + }); + + async function nextTs(offset: number) { + const t = (await ethers.provider.getBlock("latest")).timestamp + offset; + await ethers.provider.send("evm_setNextBlockTimestamp", [t]); + return t; + } + + it("passes when deadline equals the block timestamp (guard is strict >)", async () => { + const t = await nextTs(100); + await expect(liq.connect(owner).liquidate(params({ deadline: t }))).to.emit(liq, "Liquidated"); + }); + + it("reverts with (deadline, now) when the deadline is one second stale", async () => { + const t = await nextTs(100); + await expect(liq.connect(owner).liquidate(params({ deadline: t - 1 }))) + .to.be.revertedWithCustomError(liq, "DeadlineExpired") + .withArgs(t - 1, t); + }); + + it("moves no funds on an expired deadline (reverts before any external call)", async () => { + const t = await nextTs(100); + await expect(liq.connect(owner).liquidate(params({ deadline: t - 1 }))).to.be.revertedWithCustomError( + liq, + "DeadlineExpired", + ); + expect(await usdt.balanceOf(liq.address)).to.equal(REPAY); // inventory untouched + expect(await usdt.balanceOf(venusLiq.address)).to.equal(0); // no repay pulled through the gate + expect(await usdt.allowance(liq.address, venusLiq.address)).to.equal(0); // no gate approval granted + }); + }); + + describe("reentrancy", () => { + let evil: Contract; + + beforeEach(async () => { + evil = await (await ethers.getContractFactory("MockReentrantRouter")).deploy(); + await usdt.mint(evil.address, OUT); // the evil router pays out the swap like the normal one + await usdt.mint(liq.address, REPAY); // inventory to fund the outer liquidation + // Allowlist AND operator-grant the router so the re-entry clears onlyOperator and actually + // reaches the nonReentrant guard (otherwise it would revert on NotOperator, proving nothing). + await liq.connect(owner).setRouter(evil.address, true); + await liq.connect(owner).setOperator(evil.address, true); + }); + + it("blocks re-entry into liquidate while the outer liquidation still settles", async () => { + const reentry = liq.interface.encodeFunctionData("liquidate", [params()]); + await evil.configure(liq.address, reentry); + const p = params({ router: evil.address, swapCalldata: swapCalldata(SEIZED, liq.address) }); + + await expect(liq.connect(owner).liquidate(p)).to.emit(liq, "Liquidated"); + expect(await evil.reentryAttempted()).to.equal(true); + expect(await evil.reentrySucceeded()).to.equal(false); // guard blocked the nested call + }); + + it("blocks cross-function re-entry into flashLiquidate", async () => { + const reentry = liq.interface.encodeFunctionData("flashLiquidate", [params()]); + await evil.configure(liq.address, reentry); + const p = params({ router: evil.address, swapCalldata: swapCalldata(SEIZED, liq.address) }); + + await expect(liq.connect(owner).liquidate(p)).to.emit(liq, "Liquidated"); + expect(await evil.reentrySucceeded()).to.equal(false); + }); + }); }); diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts index 2048364da..0dfd7f4aa 100644 --- a/tests/hardhat/Fork/BStockLiquidatorFork.ts +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -1,270 +1,807 @@ +// ============================================================================================ +// BStockLiquidator — bscmainnet FORK suite (real bStock collateral market, real gate, RFQ-mocked hop-1) +// ============================================================================================ +// +// WHAT THIS PROVES +// ---------------- +// The bStock backstop liquidator settles a real, underwater bStock position end-to-end against the LIVE +// Core Pool: it repays the borrow through the real pool-wide Venus Liquidator gate, seizes + redeems the +// bStock collateral, sells it to the debt asset, and enforces `minOut` — across every debt-market shape +// (single-hop USDT, two-hop non-USDT, native BNB), in both funding modes (inventory + flash), and it +// rolls back cleanly when the sale under-delivers. +// +// WHY bStock IS LISTED FRESH, AND WHY HOP-1 IS A MOCK +// --------------------------------------------------- +// bStock (an ERC-8056 tokenized stock) is not yet on Core Pool, so `helpers/bstock.ts::listBStockMarket` +// stands up a genuine bStock collateral market on the live diamond exactly as governance would (deploy a +// vToken, wire a ResilientOracle price, set collateral factor / supply cap / per-market liquidation +// incentive, unpause). A freshly listed stock token has NO on-chain AMM liquidity — which is precisely +// why the production design sells the bStock->USDT leg through an off-chain Native RFQ (an MM-signed firm +// quote), not a DEX. A fork cannot reproduce an off-chain MM, so hop-1 is a `MockNativeRouter` pre-funded +// with USDT whose `rate` models the firm quote; hop-2 (USDT->debt) uses REAL PancakeSwap, where liquidity +// genuinely exists. The contract is collateral- and route-agnostic, so this faithfully exercises it. +// +// The realistic trigger modeled throughout is a PRICE GAP: the stock drops (bStock oracle price crashed +// via setDirectPrice), the account falls into shortfall, and the backstop fires. Borrowers are codeless +// EOAs because vBNB disburses native BNB with a CALL into the borrower and hardhat's default accounts +// carry an EIP-7702 delegation that reverts under the fork's rules. import { setBalance } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { expect } from "chai"; import { BigNumber, Contract } from "ethers"; import { parseEther, parseUnits } from "ethers/lib/utils"; -import { ethers, upgrades } from "hardhat"; +import fc from "fast-check"; +import { ethers } from "hardhat"; -import { IAccessControlManagerV8__factory } from "../../../typechain"; +import { + A, + Action, + BStockMarket, + BalanceSlot, + ERC20_ABI, + ONE, + TOK, + VTOKEN_ABI, + ZERO, + assertRolledBack, + assertSettledFor, + buildSingleHopMock, + buildTwoHopMockThenPcs, + deployFundedMockNative, + deployLiq, + findBalanceSlot, + listBStockMarket, + makeUnderwaterBorrower, + mulberry32, + randBn, + setTokenBalance, +} from "./helpers/bstock"; import { FORK_MAINNET, forking, initMainnetUser } from "./utils"; -/** - * Real bscmainnet fork tests for the atomic BStockLiquidator — 100% on-chain, NO mock routers, NO mock gate. - * - * These are not happy-path demos. A bStock backstop liquidation fires during stress (volatile prices, thin - * books, native-BNB debt), so the point is to prove the contract behaves correctly THEN: it settles when the - * real liquidity can cover it, and it reverts CLEANLY (no loss, no half-liquidation, inventory intact) when it - * cannot. Every swap hop is a real PancakeSwap V2 swap and every repay routes through the real Venus Liquidator - * gate, so on-chain liquidity depth, the native-BNB repay, and the WBNB unwrap are all exercised for real. BTCB - * (vBTC) stands in for the (not-yet-listed) bStock collateral; the contract is collateral-agnostic. - * - * Borrowers are impersonated REAL on-chain EOAs with no code. This is load-bearing for the native path: vBNB - * disburses a borrow with a native `borrower.transfer`, i.e. a CALL into the borrower. hardhat's default - * accounts carry an EIP-7702 delegation (`0xEF01…`) on mainnet, and under the fork's London rules the leading - * `0xEF` is an invalid opcode, so a native disbursement into them reverts. Codeless EOAs sidestep that. - * - * Covered behavior: - * 1. Native BNB debt (vBNB): WBNB is unwrapped for exactly the repay, the real gate accepts the payable - * native repay, seized BTCB is sold BTCB->USDT->WBNB on real PancakeSwap, proceeds are kept as WBNB, and - * no native BNB is stranded (the real WBNB.withdraw 2300-gas transfer survives the proxy receive()). - * 2. Native BNB adverse move: when realized WBNB proceeds would breach minOut, the whole tx reverts - * (InsufficientOut) and rolls back — the borrow is untouched and the WBNB repay inventory is fully intact. - * 3. Thin liquidity (CAKE): a genuinely thin USDT->CAKE route still settles when minOut tracks the live quote. - */ - -const A = { - COMPTROLLER: "0xfD36E2c2a6789Db23113685031d7F16329158384", - TIMELOCK: "0x939bD8d64c0A9583A7Dcea9933f7b21697ab6396", - ACM: "0x4788629ABc6cFCA10F9f969efdEAa1cF70c23555", - BTCB_WHALE: process.env.FORK_BTCB_WHALE || "0xF977814e90dA44bFA03b6295A0616a897441aceC", // Binance hot wallet - VBNB: "0xA07c5b74C9B40447a954e1466938b865b6BBea36", - VWBNB: "0x6bCa74586218dB34cdB402295796b79663d816e9", -}; +const FORK_BLOCK = Number(process.env.FORK_BSTOCK_BLOCK || "107565173"); -const TOK = { - USDT: "0x55d398326f99059fF775485246999027B3197955", - WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", - CAKE: "0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82", - BTCB: "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c", -}; +const VUSDT = "0xfD5840Cd36d94D7229439859C0112a4185BC0255"; +const VCAKE = "0x86aC3974e2BD0d60825230fa6F355fF11409df5c"; -const MKT = { - vBTC: "0x882C173bC7Ff3b7786CA16dfeD3DFFfb9Ee7847B", - vCAKE: "0x86aC3974e2BD0d60825230fa6F355fF11409df5c", -}; +// bStock USD prices: healthy vs post-crash (the stock gapped down ~80%). +const P_HEALTHY = parseUnits("250", 18); +const P_CRASH = parseUnits("50", 18); -// Real on-chain EOAs (no 7702 delegation) used as liquidation targets. Impersonated, not signed for. -const BNB_BORROWER = "0x33C6476F88eeA28D7E7900F759B4597704Ef95B7"; -const CAKE_BORROWER = ethers.utils.getAddress("0x000000000000000000000000000000000ca6e001"); - -const PCS_ROUTER = "0x10ED43C718714eb63d5aA57B78B54704E256024E"; -const ONE = parseUnits("1", 18); - -const VTOKEN_ABI = [ - "function underlying() view returns (address)", - "function mint(uint256) returns (uint256)", - "function borrow(uint256) returns (uint256)", - "function borrowBalanceStored(address) view returns (uint256)", - "function exchangeRateStored() view returns (uint256)", -]; -const COMPTROLLER_ABI = [ - "function enterMarkets(address[]) returns (uint256[])", - "function liquidateCalculateSeizeTokens(address,address,uint256) view returns (uint256,uint256)", - "function setCollateralFactor(address,uint256,uint256) returns (uint256)", -]; -const ERC20_ABI = [ - "function transfer(address,uint256) returns (bool)", - "function approve(address,uint256) returns (bool)", - "function balanceOf(address) view returns (uint256)", -]; -const WBNB_ABI = ["function deposit() payable", "function transfer(address,uint256) returns (bool)"]; -const PCS_ABI = [ - "function swapExactTokensForTokens(uint256,uint256,address[],address,uint256) returns (uint256[])", - "function getAmountsOut(uint256,address[]) view returns (uint256[])", -]; +// Distinct codeless EOA borrowers (no bytecode on the fork -> native disbursement is safe). +const B = { + USDT: ethers.utils.getAddress("0x00000000000000000000000000000000ca110001"), + CAKE: ethers.utils.getAddress("0x00000000000000000000000000000000ca110002"), + BNB: ethers.utils.getAddress("0x00000000000000000000000000000000ca110003"), + FLASH: ethers.utils.getAddress("0x00000000000000000000000000000000ca110004"), + FORCED: ethers.utils.getAddress("0x00000000000000000000000000000000ca110005"), + DEADLINE: ethers.utils.getAddress("0x00000000000000000000000000000000ca110006"), + REENTRANCY: ethers.utils.getAddress("0x00000000000000000000000000000000ca110007"), + ROLLBACK: ethers.utils.getAddress("0x00000000000000000000000000000000ca110008"), + SWEEP: (i: number) => ethers.utils.getAddress("0x" + (0xca120000 + i).toString(16).padStart(40, "0")), + FUZZ: (i: number) => ethers.utils.getAddress("0x" + (0xca130000 + i).toString(16).padStart(40, "0")), +}; const test = () => { - describe("BStockLiquidator — bscmainnet fork, real PancakeSwap + real gate under stress", () => { - let owner: SignerWithAddress; - let comptroller: Contract, vBtc: Contract, btcb: Contract, cake: Contract, wbnb: Contract, pcs: Contract; + describe("BStockLiquidator — bscmainnet fork: real bStock market, real gate, RFQ-mocked hop-1", () => { + let owner: any; + let mkt: BStockMarket; + let liq: Contract; + let mock: Contract; // Native RFQ stand-in for hop-1 (bStock -> USDT) + let usdtSlot: number; + let baseSnap: string; - const BTCB_COLLATERAL = parseEther("0.2"); // per borrower, ample for these borrows - const BNB_BORROW = parseEther("2"); - const BNB_REPAY = parseEther("0.7"); // < closeFactor * borrow - const CAKE_BORROW = parseEther("1000"); - const CAKE_REPAY = parseEther("300"); + const slotCache: Record = {}; + + // Seed `amount` of `token` directly into `to` by writing the ERC20 balances slot (no whale needed). + async function fund(token: string, to: string, amount: BigNumber) { + if (slotCache[token] === undefined) { + const s = await findBalanceSlot(token); + if (s === null) throw new Error(`no balance slot for ${token}`); + slotCache[token] = s; + } + await setTokenBalance(token, to, amount, slotCache[token]); + } before(async () => { [owner] = await ethers.getSigners(); - await setBalance(owner.address, parseEther("100")); // BNB to wrap for the native-debt WBNB inventory - - comptroller = new ethers.Contract(A.COMPTROLLER, COMPTROLLER_ABI, owner); - vBtc = new ethers.Contract(MKT.vBTC, VTOKEN_ABI, owner); - btcb = new ethers.Contract(TOK.BTCB, ERC20_ABI, owner); - cake = new ethers.Contract(TOK.CAKE, ERC20_ABI, owner); - wbnb = new ethers.Contract(TOK.WBNB, WBNB_ABI, owner); - pcs = new ethers.Contract(PCS_ROUTER, PCS_ABI, owner); - - // Build both borrows at the still-normal BTCB collateral factor, BEFORE dropping it. - const btcbWhale = await initMainnetUser(A.BTCB_WHALE, parseEther("100")); - for (const [account, vDebt, amount] of [ - [BNB_BORROWER, A.VBNB, BNB_BORROW], - [CAKE_BORROWER, MKT.vCAKE, CAKE_BORROW], - ] as [string, string, BigNumber][]) { - expect(await ethers.provider.getCode(account)).to.equal("0x"); // codeless: native disbursement is safe - const borrower = await initMainnetUser(account, parseEther("10")); - await btcb.connect(btcbWhale).transfer(account, BTCB_COLLATERAL); - await btcb.connect(borrower).approve(vBtc.address, BTCB_COLLATERAL); - await vBtc.connect(borrower).mint(BTCB_COLLATERAL); - await comptroller.connect(borrower).enterMarkets([vBtc.address]); - await new ethers.Contract(vDebt, VTOKEN_ABI, borrower).borrow(amount); - } + await setBalance(owner.address, parseEther("1000")); + + // Stand up the real bStock market once; every test reverts back to this state. + mkt = await listBStockMarket(owner, P_HEALTHY); + ({ mock, usdtSlot } = await deployFundedMockNative(owner, P_HEALTHY)); + slotCache[TOK.USDT] = usdtSlot; - // Drop the BTCB threshold hard -> both borrows are underwater regardless of price. - const timelock = await initMainnetUser(A.TIMELOCK, parseEther("10")); - const acm = IAccessControlManagerV8__factory.connect(A.ACM, timelock); - await acm.giveCallPermission(A.COMPTROLLER, "setCollateralFactor(address,uint256,uint256)", A.TIMELOCK); - await comptroller.connect(timelock).setCollateralFactor(MKT.vBTC, parseUnits("0.02", 18), parseUnits("0.02", 18)); + liq = await deployLiq(owner); + await liq.connect(owner).setRouter(mock.address, true); // hop-1 (Native RFQ mock) + await liq.connect(owner).setRouter(A.PCS_ROUTER, true); // hop-2 (real PancakeSwap) + + baseSnap = await ethers.provider.send("evm_snapshot", []); + }); + + afterEach(async () => { + // evm_revert consumes the snapshot id, so re-take it for the next test. + await ethers.provider.send("evm_revert", [baseSnap]); + baseSnap = await ethers.provider.send("evm_snapshot", []); + await mock.setRate(P_HEALTHY); // reset the quote the mock models }); - async function deployLiq() { - const Factory = await ethers.getContractFactory("BStockLiquidator"); - const liq = await upgrades.deployProxy(Factory, [owner.address], { - constructorArgs: [A.COMPTROLLER, A.VBNB, A.VWBNB, TOK.WBNB], - unsafeAllow: ["constructor", "state-variable-immutable"], + /* ---------------------------------------------------------------- */ + /* curated scenarios */ + /* ---------------------------------------------------------------- */ + + describe("curated scenarios", () => { + it("single-hop USDT debt, inventory mode: price-gap shortfall settles, borrow drops", async () => { + // Borrower supplies 100 bStock ($25k), borrows 5k USDT; the stock then gaps to $50 -> shortfall. + await makeUnderwaterBorrower({ + mkt, + vDebt: VUSDT, + borrower: B.USDT, + collateralBStock: parseUnits("100", 18), + borrowAmount: parseUnits("5000", 18), + crashPriceTo: P_CRASH, + }); + await mock.setRate(P_CRASH); // MM quotes the gapped-down price + + const REPAY = parseUnits("2000", 18); + await fund(TOK.USDT, liq.address, REPAY); // inventory + const params = { + borrower: B.USDT, + vDebt: VUSDT, + vBStock: mkt.vBStock.address, + repayAmount: REPAY, + router: mock.address, + swapCalldata: buildSingleHopMock(mkt, mock, liq.address), + minOut: parseUnits("1900", 18), + router2: ZERO, + swapCalldata2: "0x", + intermediateToken: ZERO, + deadline: ethers.constants.MaxUint256, + }; + + const borrowBefore = await new ethers.Contract(VUSDT, VTOKEN_ABI, owner).borrowBalanceStored(B.USDT); + const out = await liq.connect(owner).callStatic.liquidate(params); + expect(out).to.be.gte(params.minOut); + await liq.connect(owner).liquidate(params); + await assertSettledFor(liq, mkt, VUSDT, B.USDT, borrowBefore); + expect(await new ethers.Contract(TOK.USDT, ERC20_ABI, owner).balanceOf(liq.address)).to.be.gte(params.minOut); }); - await liq.connect(owner).setRouter(PCS_ROUTER, true); - return liq; - } - // Real two-hop PancakeSwap calldata (BTCB -> USDT -> path2 tail) + the live expected final out. - // hop-1 amountIn is under-shot 20% so the fixed-amount calldata always fits under the on-chain approval - // (the gate hands the liquidator ~98% of the gross seize; 80% leaves margin). - async function buildTwoHop(vDebt: string, repay: BigNumber, liq: string, path2: string[]) { - const [, seizeTokens]: BigNumber[] = await comptroller.liquidateCalculateSeizeTokens(vDebt, MKT.vBTC, repay); - const rate: BigNumber = await vBtc.exchangeRateStored(); - const x1 = seizeTokens.mul(rate).div(ONE).mul(80).div(100); // BTCB to sell on hop 1 - const mid: BigNumber = (await pcs.getAmountsOut(x1, [TOK.BTCB, TOK.USDT]))[1]; - const x2 = mid.mul(999).div(1000); - const deadline = Math.floor(Date.now() / 1000) + 3600; - return { - swapCalldata: pcs.interface.encodeFunctionData("swapExactTokensForTokens", [ - x1, - 0, - [TOK.BTCB, TOK.USDT], - liq, - deadline, - ]), - swapCalldata2: pcs.interface.encodeFunctionData("swapExactTokensForTokens", [x2, 0, path2, liq, deadline]), - expectedOut: (await pcs.getAmountsOut(x2, path2))[path2.length - 1] as BigNumber, - }; - } + it("two-hop CAKE debt, inventory mode: bStock->USDT (mock) -> CAKE (real PCS) at live slippage", async () => { + await makeUnderwaterBorrower({ + mkt, + vDebt: VCAKE, + borrower: B.CAKE, + collateralBStock: parseUnits("100", 18), + borrowAmount: parseUnits("4000", 18), // ~4000 CAKE; must exceed post-crash collateral power ($3k) + crashPriceTo: P_CRASH, + }); + await mock.setRate(P_CRASH); - function bnbParams(liq: string, swapCalldata: string, swapCalldata2: string, minOut: BigNumber) { - return { - borrower: BNB_BORROWER, - vDebt: A.VBNB, - vBStock: MKT.vBTC, - repayAmount: BNB_REPAY, - router: PCS_ROUTER, - swapCalldata, - minOut, - router2: PCS_ROUTER, - swapCalldata2, - intermediateToken: TOK.USDT, - deadline: ethers.constants.MaxUint256, - }; - } + const REPAY = parseUnits("1500", 18); // CAKE (< closeFactor * borrow = 2000) + await fund(TOK.CAKE, liq.address, REPAY); + const { swapCalldata, swapCalldata2, expectedOut } = await buildTwoHopMockThenPcs( + owner, + mkt, + mock, + VCAKE, + REPAY, + [TOK.USDT, TOK.CAKE], + liq.address, + P_CRASH, + ); + const params = { + borrower: B.CAKE, + vDebt: VCAKE, + vBStock: mkt.vBStock.address, + repayAmount: REPAY, + router: mock.address, + swapCalldata, + minOut: expectedOut.mul(90).div(100), + router2: A.PCS_ROUTER, + swapCalldata2, + intermediateToken: TOK.USDT, + deadline: ethers.constants.MaxUint256, + }; + + const borrowBefore = await new ethers.Contract(VCAKE, VTOKEN_ABI, owner).borrowBalanceStored(B.CAKE); + const out = await liq.connect(owner).callStatic.liquidate(params); + expect(out).to.be.gte(params.minOut); + await liq.connect(owner).liquidate(params); + await assertSettledFor(liq, mkt, VCAKE, B.CAKE, borrowBefore); + }); + + it("native BNB debt (vBNB): WBNB unwrap of the repay, two-hop to WBNB, nothing stranded", async () => { + await makeUnderwaterBorrower({ + mkt, + vDebt: A.VBNB, + borrower: B.BNB, + collateralBStock: parseUnits("100", 18), + borrowAmount: parseEther("8"), // ~8 BNB; post-crash collateral power ($3k) is well under this + crashPriceTo: P_CRASH, + }); + await mock.setRate(P_CRASH); + + const REPAY = parseEther("2"); // WBNB repay inventory (contract unwraps to native for the gate) + await fund(TOK.WBNB, liq.address, REPAY); + const { swapCalldata, swapCalldata2, expectedOut } = await buildTwoHopMockThenPcs( + owner, + mkt, + mock, + A.VBNB, + REPAY, + [TOK.USDT, TOK.WBNB], + liq.address, + P_CRASH, + ); + const params = { + borrower: B.BNB, + vDebt: A.VBNB, + vBStock: mkt.vBStock.address, + repayAmount: REPAY, + router: mock.address, + swapCalldata, + minOut: expectedOut.mul(90).div(100), + router2: A.PCS_ROUTER, + swapCalldata2, + intermediateToken: TOK.USDT, + deadline: ethers.constants.MaxUint256, + }; + + const vBnb = new ethers.Contract(A.VBNB, VTOKEN_ABI, owner); + const borrowBefore = await vBnb.borrowBalanceStored(B.BNB); + const out = await liq.connect(owner).callStatic.liquidate(params); + expect(out).to.be.gte(params.minOut); + await liq.connect(owner).liquidate(params); + expect(await vBnb.borrowBalanceStored(B.BNB)).to.be.lt(borrowBefore); + // Proceeds retained as WBNB, no native BNB stranded on the proxy. + expect(await new ethers.Contract(TOK.WBNB, ERC20_ABI, owner).balanceOf(liq.address)).to.be.gte(params.minOut); + expect(await ethers.provider.getBalance(liq.address)).to.equal(0); + }); + + it("minOut breach -> InsufficientOut, FULL rollback: borrow + inventory intact, nothing stranded", async () => { + await makeUnderwaterBorrower({ + mkt, + vDebt: VUSDT, + borrower: B.ROLLBACK, + collateralBStock: parseUnits("100", 18), + borrowAmount: parseUnits("5000", 18), + crashPriceTo: P_CRASH, + }); + await mock.setRate(P_CRASH); + + const REPAY = parseUnits("2000", 18); + await fund(TOK.USDT, liq.address, REPAY); + const params = { + borrower: B.ROLLBACK, + vDebt: VUSDT, + vBStock: mkt.vBStock.address, + repayAmount: REPAY, + router: mock.address, + swapCalldata: buildSingleHopMock(mkt, mock, liq.address), + minOut: parseUnits("100000", 18), // impossibly high -> must revert + router2: ZERO, + swapCalldata2: "0x", + intermediateToken: ZERO, + deadline: ethers.constants.MaxUint256, + }; + + const borrowBefore = await new ethers.Contract(VUSDT, VTOKEN_ABI, owner).borrowBalanceStored(B.ROLLBACK); + await expect(liq.connect(owner).liquidate(params)).to.be.revertedWithCustomError(liq, "InsufficientOut"); + await assertRolledBack(liq, mkt, VUSDT, B.ROLLBACK, borrowBefore, TOK.USDT, REPAY); + }); + + it("forced liquidation: a healthy account with forced-liquidation enabled is liquidatable", async () => { + // Supply + borrow but DO NOT crash the price -> the account is healthy (no shortfall). + await makeUnderwaterBorrower({ + mkt, + vDebt: VUSDT, + borrower: B.FORCED, + collateralBStock: parseUnits("100", 18), + borrowAmount: parseUnits("5000", 18), + }); + // The MM still quotes the (uncrashed) price. + await mock.setRate(P_HEALTHY); + + const cAsTl = new ethers.Contract( + A.COMPTROLLER, + ["function _setForcedLiquidation(address,bool)"], + await initMainnetUser(A.TIMELOCK, parseEther("10")), + ); + const acm = new ethers.Contract( + A.ACM, + ["function giveCallPermission(address,string,address)"], + await initMainnetUser(A.TIMELOCK, parseEther("10")), + ); + await acm.giveCallPermission(A.COMPTROLLER, "_setForcedLiquidation(address,bool)", A.TIMELOCK); + await cAsTl._setForcedLiquidation(VUSDT, true); + + const REPAY = parseUnits("2000", 18); + await fund(TOK.USDT, liq.address, REPAY); + const params = { + borrower: B.FORCED, + vDebt: VUSDT, + vBStock: mkt.vBStock.address, + repayAmount: REPAY, + router: mock.address, + swapCalldata: buildSingleHopMock(mkt, mock, liq.address), + minOut: parseUnits("9000", 18), // healthy price -> ~2200*(250/50)... generous, see below + router2: ZERO, + swapCalldata2: "0x", + intermediateToken: ZERO, + deadline: ethers.constants.MaxUint256, + }; + // At the healthy $250 quote the seized bStock is worth far more; set minOut against the live math. + const out = await liq.connect(owner).callStatic.liquidate({ ...params, minOut: 1 }); + const borrowBefore = await new ethers.Contract(VUSDT, VTOKEN_ABI, owner).borrowBalanceStored(B.FORCED); + await liq.connect(owner).liquidate({ ...params, minOut: out.mul(95).div(100) }); + await assertSettledFor(liq, mkt, VUSDT, B.FORCED, borrowBefore); + }); + + it("expired deadline -> DeadlineExpired, no state change", async () => { + await makeUnderwaterBorrower({ + mkt, + vDebt: VUSDT, + borrower: B.DEADLINE, + collateralBStock: parseUnits("100", 18), + borrowAmount: parseUnits("5000", 18), + crashPriceTo: P_CRASH, + }); + await mock.setRate(P_CRASH); + const REPAY = parseUnits("2000", 18); + await fund(TOK.USDT, liq.address, REPAY); + const params = { + borrower: B.DEADLINE, + vDebt: VUSDT, + vBStock: mkt.vBStock.address, + repayAmount: REPAY, + router: mock.address, + swapCalldata: buildSingleHopMock(mkt, mock, liq.address), + minOut: parseUnits("1900", 18), + router2: ZERO, + swapCalldata2: "0x", + intermediateToken: ZERO, + deadline: 1, // in the past + }; + const borrowBefore = await new ethers.Contract(VUSDT, VTOKEN_ABI, owner).borrowBalanceStored(B.DEADLINE); + await expect(liq.connect(owner).liquidate(params)).to.be.revertedWithCustomError(liq, "DeadlineExpired"); + await assertRolledBack(liq, mkt, VUSDT, B.DEADLINE, borrowBefore, TOK.USDT, REPAY); + }); + + it("reentrancy: an allowlisted+operator malicious router cannot re-enter liquidate", async () => { + await makeUnderwaterBorrower({ + mkt, + vDebt: VUSDT, + borrower: B.REENTRANCY, + collateralBStock: parseUnits("100", 18), + borrowAmount: parseUnits("5000", 18), + crashPriceTo: P_CRASH, + }); - it("native BNB adverse: reverts InsufficientOut and rolls back, leaving the borrow and inventory intact", async () => { - const liq = await deployLiq(); - await wbnb.deposit({ value: BNB_REPAY }); - await wbnb.transfer(liq.address, BNB_REPAY); // WBNB repay inventory - - const { swapCalldata, swapCalldata2, expectedOut } = await buildTwoHop(A.VBNB, BNB_REPAY, liq.address, [ - TOK.USDT, - TOK.WBNB, - ]); - // Demand 5% MORE WBNB than the pool can deliver (as if quoted before the price moved against us). - const params = bnbParams(liq.address, swapCalldata, swapCalldata2, expectedOut.mul(105).div(100)); - - const vBnb = new ethers.Contract(A.VBNB, VTOKEN_ABI, owner); - const borrowBefore: BigNumber = await vBnb.borrowBalanceStored(BNB_BORROWER); - await expect(liq.connect(owner).liquidate(params)).to.be.revertedWithCustomError(liq, "InsufficientOut"); - - // Full rollback: borrow not reduced, WBNB inventory untouched, no collateral seized, no BNB stranded. - expect(await vBnb.borrowBalanceStored(BNB_BORROWER)).to.be.gte(borrowBefore); - expect(await new ethers.Contract(TOK.WBNB, ERC20_ABI, owner).balanceOf(liq.address)).to.equal(BNB_REPAY); - expect(await btcb.balanceOf(liq.address)).to.equal(0); - expect(await ethers.provider.getBalance(liq.address)).to.equal(0); + // Deploy the malicious router, fund it with USDT so its own swap can pay out, and configure the + // re-entry to call liquidate again. Allowlist + operator-grant it so the re-entry reaches the guard. + const evil = await (await ethers.getContractFactory("MockReentrantRouter")).deploy(); + await evil.setRate(P_CRASH); // pays USDT for the seized bStock at the crashed quote + await fund(TOK.USDT, evil.address, parseUnits("1000000", 18)); + await liq.connect(owner).setRouter(evil.address, true); + await liq.connect(owner).setOperator(evil.address, true); + + const REPAY = parseUnits("2000", 18); + await fund(TOK.USDT, liq.address, REPAY); + // Use swapAll so the hop actually delivers USDT and the OUTER liquidation SETTLES — that is what + // lets us observe the re-entry was blocked (a reverted outer tx would roll back evil's flags too). + const params = { + borrower: B.REENTRANCY, + vDebt: VUSDT, + vBStock: mkt.vBStock.address, + repayAmount: REPAY, + router: evil.address, + swapCalldata: evil.interface.encodeFunctionData("swapAll", [mkt.bStock.address, TOK.USDT, liq.address]), + minOut: parseUnits("1", 18), + router2: ZERO, + swapCalldata2: "0x", + intermediateToken: ZERO, + deadline: ethers.constants.MaxUint256, + }; + // The re-entry payload attempts a nested liquidate; it must be rejected by nonReentrant. + await evil.configure(liq.address, liq.interface.encodeFunctionData("liquidate", [params])); + + const borrowBefore = await new ethers.Contract(VUSDT, VTOKEN_ABI, owner).borrowBalanceStored(B.REENTRANCY); + await liq.connect(owner).liquidate(params); // outer settles + await assertSettledFor(liq, mkt, VUSDT, B.REENTRANCY, borrowBefore); + expect(await evil.reentryAttempted()).to.equal(true); // the router did run + expect(await evil.reentrySucceeded()).to.equal(false); // but the nested liquidate was blocked + }); + + it("flash mode: flash-borrows the debt from Venus, settles, repays principal + premium", async () => { + // Flash mode borrows the DEBT asset (USDT) from its vToken; the contract must be flash-authorized. + const flashOk = await enableFlashIfNeeded(owner, VUSDT, liq.address); + if (!flashOk) { + console.log(" flash: vUSDT flash-loan not enabled at this block — skipping (documented)"); + return; + } + await makeUnderwaterBorrower({ + mkt, + vDebt: VUSDT, + borrower: B.FLASH, + collateralBStock: parseUnits("100", 18), + borrowAmount: parseUnits("5000", 18), + crashPriceTo: P_CRASH, + }); + await mock.setRate(P_CRASH); + const REPAY = parseUnits("2000", 18); + const params = { + borrower: B.FLASH, + vDebt: VUSDT, + vBStock: mkt.vBStock.address, + repayAmount: REPAY, + router: mock.address, + swapCalldata: buildSingleHopMock(mkt, mock, liq.address), + minOut: parseUnits("1900", 18), + router2: ZERO, + swapCalldata2: "0x", + intermediateToken: ZERO, + deadline: ethers.constants.MaxUint256, + }; + const borrowBefore = await new ethers.Contract(VUSDT, VTOKEN_ABI, owner).borrowBalanceStored(B.FLASH); + await liq.connect(owner).flashLiquidate(params); + await assertSettledFor(liq, mkt, VUSDT, B.FLASH, borrowBefore); + // Profit retained as USDT (proceeds - principal - premium). + expect(await new ethers.Contract(TOK.USDT, ERC20_ABI, owner).balanceOf(liq.address)).to.be.gt(0); + }); }); - it("native BNB: unwraps the repay, repays vBNB natively through the gate, sells to WBNB, strands nothing", async () => { - const liq = await deployLiq(); - await wbnb.deposit({ value: BNB_REPAY }); - await wbnb.transfer(liq.address, BNB_REPAY); - - const { swapCalldata, swapCalldata2, expectedOut } = await buildTwoHop(A.VBNB, BNB_REPAY, liq.address, [ - TOK.USDT, - TOK.WBNB, - ]); - const params = bnbParams(liq.address, swapCalldata, swapCalldata2, expectedOut.mul(97).div(100)); - - const vBnb = new ethers.Contract(A.VBNB, VTOKEN_ABI, owner); - const borrowBefore: BigNumber = await vBnb.borrowBalanceStored(BNB_BORROWER); - const out: BigNumber = await liq.connect(owner).callStatic.liquidate(params); - expect(out).to.be.gte(params.minOut); - - const tx = await liq.connect(owner).liquidate(params); - const rcpt = await tx.wait(); - console.log(` native BNB liquidation gas: ${rcpt.gasUsed.toString()}`); - - expect(borrowBefore.sub(await vBnb.borrowBalanceStored(BNB_BORROWER))).to.be.closeTo( - BNB_REPAY, - BNB_REPAY.div(50), - ); - // Proceeds retained as WBNB (>= minOut), no native BNB stranded on the proxy. - expect(await new ethers.Contract(TOK.WBNB, ERC20_ABI, owner).balanceOf(liq.address)).to.be.gte(params.minOut); - expect(await ethers.provider.getBalance(liq.address)).to.equal(0); + /* ---------------------------------------------------------------- */ + /* dynamic all-market sweep */ + /* ---------------------------------------------------------------- */ + + describe("dynamic debt-market sweep", () => { + it("liquidates every eligible Core market against seized bStock; classifies skips", async () => { + const c = new ethers.Contract( + A.COMPTROLLER, + [ + "function getAllMarkets() view returns (address[])", + "function actionPaused(address,uint8) view returns (bool)", + ], + owner, + ); + const pcs = new ethers.Contract( + A.PCS_ROUTER, + ["function getAmountsOut(uint256,address[]) view returns (uint256[])"], + owner, + ); + const markets: string[] = await c.getAllMarkets(); + let liquidated = 0; + const report: string[] = []; + + for (const vDebt of markets) { + if (ethers.utils.getAddress(vDebt) === ethers.utils.getAddress(mkt.vBStock.address)) continue; + const sym = await marketSymbol(vDebt); + const label = `${sym} (${vDebt})`; + const snap = await ethers.provider.send("evm_snapshot", []); + try { + const reason = await sweepOne(owner, c, pcs, mkt, mock, liq, vDebt, fund); + if (reason === "OK") { + liquidated++; + report.push(` ${label}: liquidated`); + } else { + report.push(` ${label}: skipped (${reason})`); + } + } catch (e: any) { + // A skip reason is acceptable; any OTHER revert is a genuine failure. + report.push(` ${label}: ERROR ${(e.message || "").slice(0, 80)}`); + await ethers.provider.send("evm_revert", [snap]); + throw e; + } + await ethers.provider.send("evm_revert", [snap]); + } + console.log(" sweep:\n" + report.join("\n")); + expect(liquidated).to.be.gt(0); // at least the USDT/CAKE/BNB markets must liquidate + }); }); - it("thin liquidity: settles a real USDT->CAKE route at live slippage when minOut tracks the quote", async () => { - const liq = await deployLiq(); - const borrower = await initMainnetUser(CAKE_BORROWER, parseEther("10")); - await cake.connect(borrower).transfer(liq.address, CAKE_REPAY); // CAKE repay inventory - - const { swapCalldata, swapCalldata2, expectedOut } = await buildTwoHop(MKT.vCAKE, CAKE_REPAY, liq.address, [ - TOK.USDT, - TOK.CAKE, - ]); - const params = { - borrower: CAKE_BORROWER, - vDebt: MKT.vCAKE, - vBStock: MKT.vBTC, - repayAmount: CAKE_REPAY, - router: PCS_ROUTER, - swapCalldata, - minOut: expectedOut.mul(97).div(100), - router2: PCS_ROUTER, - swapCalldata2, - intermediateToken: TOK.USDT, - deadline: ethers.constants.MaxUint256, - }; - - const vCake = new ethers.Contract(MKT.vCAKE, VTOKEN_ABI, owner); - const borrowBefore: BigNumber = await vCake.borrowBalanceStored(CAKE_BORROWER); - const out: BigNumber = await liq.connect(owner).callStatic.liquidate(params); - expect(out).to.be.gte(params.minOut); - await liq.connect(owner).liquidate(params); - - expect(borrowBefore.sub(await vCake.borrowBalanceStored(CAKE_BORROWER))).to.be.closeTo( - CAKE_REPAY, - CAKE_REPAY.div(50), - ); + /* ---------------------------------------------------------------- */ + /* fuzz — seeded Hardhat loop */ + /* ---------------------------------------------------------------- */ + + describe("fuzz — seeded property loops", () => { + it("randomized repay/minOut over USDT debt: invariants hold or clean InsufficientOut", async () => { + const seed = Number(process.env.FUZZ_SEED || "1337"); + const rnd = mulberry32(seed); + console.log(` seed=${seed}`); + const ITER = Number(process.env.FUZZ_ITERS || "8"); + + for (let i = 0; i < ITER; i++) { + const snap = await ethers.provider.send("evm_snapshot", []); + await makeUnderwaterBorrower({ + mkt, + vDebt: VUSDT, + borrower: B.FUZZ(i), + collateralBStock: parseUnits("100", 18), + borrowAmount: parseUnits("5000", 18), + crashPriceTo: P_CRASH, + }); + await mock.setRate(P_CRASH); + + // repay in [500, 2500] USDT (<= closeFactor*borrow), minOut margin in [-8%, +8%] of the live out. + const repay = randBn(rnd, parseUnits("500", 18), parseUnits("2500", 18)); + await fund(TOK.USDT, liq.address, repay); + const base = { + borrower: B.FUZZ(i), + vDebt: VUSDT, + vBStock: mkt.vBStock.address, + repayAmount: repay, + router: mock.address, + swapCalldata: buildSingleHopMock(mkt, mock, liq.address), + minOut: BigNumber.from(1), + router2: ZERO, + swapCalldata2: "0x", + intermediateToken: ZERO, + deadline: ethers.constants.MaxUint256, + }; + const liveOut = await liq.connect(owner).callStatic.liquidate(base); + const marginBps = 9200 + Math.floor(rnd() * 1600); // 92%..108% + const minOut = liveOut.mul(marginBps).div(10000); + const borrowBefore = await new ethers.Contract(VUSDT, VTOKEN_ABI, owner).borrowBalanceStored(B.FUZZ(i)); + + if (minOut.lte(liveOut)) { + await liq.connect(owner).liquidate({ ...base, minOut }); + await assertSettledFor(liq, mkt, VUSDT, B.FUZZ(i), borrowBefore); + } else { + await expect(liq.connect(owner).liquidate({ ...base, minOut })).to.be.revertedWithCustomError( + liq, + "InsufficientOut", + ); + await assertRolledBack(liq, mkt, VUSDT, B.FUZZ(i), borrowBefore, TOK.USDT, repay); + } + await ethers.provider.send("evm_revert", [snap]); + } + }); + }); + + /* ---------------------------------------------------------------- */ + /* fuzz — fast-check property */ + /* ---------------------------------------------------------------- */ + + describe("fuzz — fast-check property", () => { + it("generated (repayFraction, minOutBps): invariants hold or clean revert", async () => { + await fc.assert( + fc.asyncProperty( + fc.record({ + repayPct: fc.integer({ min: 10, max: 50 }), // % of borrow (<= closeFactor) + minOutBps: fc.integer({ min: 9000, max: 11000 }), // 90%..110% of live out + }), + async ({ repayPct, minOutBps }) => { + const snap = await ethers.provider.send("evm_snapshot", []); + try { + await makeUnderwaterBorrower({ + mkt, + vDebt: VUSDT, + borrower: B.FUZZ(9000), + collateralBStock: parseUnits("100", 18), + borrowAmount: parseUnits("5000", 18), + crashPriceTo: P_CRASH, + }); + await mock.setRate(P_CRASH); + const repay = parseUnits("5000", 18).mul(repayPct).div(100); + await fund(TOK.USDT, liq.address, repay); + const base = { + borrower: B.FUZZ(9000), + vDebt: VUSDT, + vBStock: mkt.vBStock.address, + repayAmount: repay, + router: mock.address, + swapCalldata: buildSingleHopMock(mkt, mock, liq.address), + minOut: BigNumber.from(1), + router2: ZERO, + swapCalldata2: "0x", + intermediateToken: ZERO, + deadline: ethers.constants.MaxUint256, + }; + const liveOut = await liq.connect(owner).callStatic.liquidate(base); + const minOut = liveOut.mul(minOutBps).div(10000); + const borrowBefore = await new ethers.Contract(VUSDT, VTOKEN_ABI, owner).borrowBalanceStored( + B.FUZZ(9000), + ); + if (minOut.lte(liveOut)) { + await liq.connect(owner).liquidate({ ...base, minOut }); + await assertSettledFor(liq, mkt, VUSDT, B.FUZZ(9000), borrowBefore); + } else { + await expect(liq.connect(owner).liquidate({ ...base, minOut })).to.be.revertedWithCustomError( + liq, + "InsufficientOut", + ); + await assertRolledBack(liq, mkt, VUSDT, B.FUZZ(9000), borrowBefore, TOK.USDT, repay); + } + } finally { + await ethers.provider.send("evm_revert", [snap]); + } + }, + ), + { numRuns: Number(process.env.FC_RUNS || "10"), seed: 42, endOnFailure: true }, + ); + }); }); }); }; +/* ------------------------------------------------------------------ */ +/* sweep + flash helpers */ +/* ------------------------------------------------------------------ */ + +// Enable flash loans for `vDebt` + authorize `liqAddr` as timelock, returning false if not feasible. +async function enableFlashIfNeeded(owner: any, vDebt: string, liqAddr: string): Promise { + try { + const v = new ethers.Contract(vDebt, ["function isFlashLoanEnabled() view returns (bool)"], owner); + const enabled: boolean = await v.isFlashLoanEnabled(); + if (!enabled) return false; + // Whitelist the receiver on the diamond (best-effort; if the setter is absent, skip flash). + const tl = await initMainnetUser(A.TIMELOCK, parseEther("10")); + const acm = new ethers.Contract(A.ACM, ["function giveCallPermission(address,string,address)"], tl); + try { + await acm.giveCallPermission(A.COMPTROLLER, "setWhiteListFlashLoanAccount(address,bool)", A.TIMELOCK); + await new ethers.Contract( + A.COMPTROLLER, + ["function setWhiteListFlashLoanAccount(address,bool)"], + tl, + ).setWhiteListFlashLoanAccount(liqAddr, true); + } catch { + /* setter may not exist / not required at this block */ + } + return true; + } catch { + return false; + } +} + +// Best-effort vToken symbol (e.g. "vUSDT", "vBNB") for readable sweep logs. +async function marketSymbol(vToken: string): Promise { + try { + return await new ethers.Contract(vToken, ["function symbol() view returns (string)"], ethers.provider).symbol(); + } catch { + return "v???"; + } +} + +// Try to liquidate one debt market against the seized bStock. Returns "OK" or a skip reason. +async function sweepOne( + owner: any, + c: Contract, + pcs: Contract, + mkt: BStockMarket, + mock: Contract, + liq: Contract, + vDebt: string, + fund: (t: string, to: string, a: BigNumber) => Promise, +): Promise { + if (await c.actionPaused(vDebt, Action.BORROW)) return "borrow paused"; + if (await c.actionPaused(vDebt, Action.LIQUIDATE)) return "liquidate paused"; + + const v = new ethers.Contract(vDebt, VTOKEN_ABI, owner); + let underlying: string; + let isBnb = false; + try { + underlying = await v.underlying(); + } catch { + underlying = TOK.WBNB; + isBnb = true; // vBNB exposes no underlying() + } + + const cash: BigNumber = isBnb + ? await ethers.provider.getBalance(vDebt) + : await new ethers.Contract(underlying, ERC20_ABI, owner).balanceOf(vDebt); + if (cash.eq(0)) return "zero cash"; + + // Non-USDT, non-BNB markets need a live USDT->debt PCS route; probe it. + const isUsdt = !isBnb && ethers.utils.getAddress(underlying) === ethers.utils.getAddress(TOK.USDT); + const twoHop = !isUsdt; + const path = isBnb ? [TOK.USDT, TOK.WBNB] : [TOK.USDT, underlying]; + if (twoHop) { + try { + const q = await pcs.getAmountsOut(parseUnits("100", 18), path); + if (q[q.length - 1].eq(0)) return "no PCS route"; + } catch { + return "no PCS route"; + } + } + + // Size the borrow from the borrower's collateral POWER (100 bStock @ $250, CF 0.6 = ~$15k), targeting + // ~$8k of debt — above the post-crash power (~$3k) so a shortfall is guaranteed — and capped at 20% + // of market cash so we never exhaust liquidity. Uses the live debt oracle price (USD * 1e(36-decimals)). + const resilient = new ethers.Contract( + A.RESILIENT_ORACLE, + ["function getUnderlyingPrice(address) view returns (uint256)"], + owner, + ); + let debtBorrow: BigNumber; + try { + const price: BigNumber = await resilient.getUnderlyingPrice(vDebt); + if (price.eq(0)) return "dead oracle"; + debtBorrow = parseUnits("8000", 18).mul(ONE).div(price); // 8000 / priceUsd, in the debt's own decimals + } catch { + return "dead oracle"; + } + const cap = cash.mul(20).div(100); + if (debtBorrow.gt(cap)) debtBorrow = cap; + if (debtBorrow.eq(0)) return "unpriceable/dust"; + + // Build the underwater borrower defensively — quirky markets revert borrow ("math error"), hit caps, + // or leave the account not-underwater; classify any of these as a skip rather than failing the sweep. + const borrower = ethers.utils.getAddress( + "0x" + (0xca120000 + (parseInt(vDebt.slice(-4), 16) % 60000)).toString(16).padStart(40, "0"), + ); + try { + await makeUnderwaterBorrower({ + mkt, + vDebt, + borrower, + collateralBStock: parseUnits("100", 18), + borrowAmount: debtBorrow, + crashPriceTo: P_CRASH, + }); + } catch (e: any) { + const data = e.error?.data?.data || e.error?.data || e.data || ""; + const sel = typeof data === "string" && data.length >= 10 ? data.slice(0, 10) : ""; + return `borrow (${e.reason || sel || (e.message || "").slice(0, 24)})`; + } + await mock.setRate(P_CRASH); + const repay = debtBorrow.div(4); + try { + await fund(underlying, liq.address, repay); // seed inventory; some tokens hide their balances slot + } catch { + return "cannot seed inventory"; + } + + let params: any; + if (twoHop) { + const { swapCalldata, swapCalldata2, expectedOut } = await buildTwoHopMockThenPcs( + owner, + mkt, + mock, + vDebt, + repay, + path, + liq.address, + P_CRASH, + ); + params = { + borrower, + vDebt, + vBStock: mkt.vBStock.address, + repayAmount: repay, + router: mock.address, + swapCalldata, + minOut: expectedOut.mul(85).div(100), + router2: A.PCS_ROUTER, + swapCalldata2, + intermediateToken: TOK.USDT, + deadline: ethers.constants.MaxUint256, + }; + } else { + params = { + borrower, + vDebt, + vBStock: mkt.vBStock.address, + repayAmount: repay, + router: mock.address, + swapCalldata: buildSingleHopMock(mkt, mock, liq.address), + minOut: BigNumber.from(1), + router2: ZERO, + swapCalldata2: "0x", + intermediateToken: ZERO, + deadline: ethers.constants.MaxUint256, + }; + } + + const borrowBefore = await v.borrowBalanceStored(borrower); + try { + const out = await liq.connect(owner).callStatic.liquidate({ ...params, minOut: 1 }); + await liq.connect(owner).liquidate({ ...params, minOut: out.mul(80).div(100) }); + } catch (e: any) { + return `liquidation (${(e.reason || e.message || "").slice(0, 32)})`; + } + expect(await v.borrowBalanceStored(borrower)).to.be.lt(borrowBefore); + return "OK"; +} + if (FORK_MAINNET) { - forking(107565173, test); + forking(FORK_BLOCK, test); } diff --git a/tests/hardhat/Fork/helpers/bstock.ts b/tests/hardhat/Fork/helpers/bstock.ts index 96e3d4afb..3fbbae994 100644 --- a/tests/hardhat/Fork/helpers/bstock.ts +++ b/tests/hardhat/Fork/helpers/bstock.ts @@ -11,6 +11,7 @@ // quote); on a fork we cannot reproduce that, so hop-1 is a MockNativeRouter pre-funded with USDT that // models the firm quote, while hop-2 (USDT->debt) uses REAL PancakeSwap where liquidity actually exists. import { setStorageAt } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; import { BigNumber, Contract } from "ethers"; import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; @@ -92,6 +93,11 @@ export const ERC20_ABI = [ "function approve(address,uint256) returns (bool)", "function transfer(address,uint256) returns (bool)", ]; +const PCS_ABI = [ + "function swapExactTokensForTokens(uint256,uint256,address[],address,uint256) returns (uint256[])", + "function getAmountsOut(uint256,address[]) view returns (uint256[])", +]; +const RESILIENT_READ_ABI = ["function getUnderlyingPrice(address) view returns (uint256)"]; /* ------------------------------------------------------------------ */ /* deterministic PRNG (mulberry32) */ @@ -124,32 +130,46 @@ export function randBn(rnd: () => number, min: BigNumber, max: BigNumber): BigNu // Brute-force the ERC20 balances mapping slot, then write a balance directly — used to fund the mock // Native router with USDT and to top up thin debt markets without needing a whale. -export async function findBalanceSlot(token: string): Promise { +// +// The balances mapping location varies: the base slot index differs per token (proxies/OZ gaps push it +// higher), and the mapping-key hashing order differs between Solidity `keccak(key,slot)` and Vyper-style +// `keccak(slot,key)`. Probe a wide range in BOTH orders and return whichever reproduces balanceOf; some +// tokens (unusual/ERC-7201-namespaced layouts) still won't be found and must be seeded via a whale. +export interface BalanceSlot { + slot: number; + slotFirst: boolean; // true => key hashed as keccak(slot, key) (Vyper), false => keccak(key, slot) +} + +function mappingSlot(account: string, loc: BalanceSlot): string { + const types = loc.slotFirst ? ["uint256", "address"] : ["address", "uint256"]; + const values = loc.slotFirst ? [loc.slot, account] : [account, loc.slot]; + return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(types, values)); +} + +export async function findBalanceSlot(token: string): Promise { const probe = "0x" + "ba1".padStart(40, "0"); const probeAmount = BigNumber.from("1234567890"); const erc20 = new ethers.Contract(token, ERC20_ABI, ethers.provider); - for (let slot = 0; slot <= 12; slot++) { - const storageSlot = ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode(["address", "uint256"], [probe, slot]), - ); - const prev = await ethers.provider.getStorageAt(token, storageSlot); - await setStorageAt(token, storageSlot, ethers.utils.hexZeroPad(probeAmount.toHexString(), 32)); - try { - const bal = await erc20.balanceOf(probe); - await setStorageAt(token, storageSlot, prev); - if (bal.eq(probeAmount)) return slot; - } catch { - await setStorageAt(token, storageSlot, prev); + for (let slot = 0; slot <= 60; slot++) { + for (const slotFirst of [false, true]) { + const loc = { slot, slotFirst }; + const storageSlot = mappingSlot(probe, loc); + const prev = await ethers.provider.getStorageAt(token, storageSlot); + await setStorageAt(token, storageSlot, ethers.utils.hexZeroPad(probeAmount.toHexString(), 32)); + try { + const bal = await erc20.balanceOf(probe); + await setStorageAt(token, storageSlot, prev); + if (bal.eq(probeAmount)) return loc; + } catch { + await setStorageAt(token, storageSlot, prev); + } } } return null; } -export async function setTokenBalance(token: string, account: string, amount: BigNumber, slot: number) { - const storageSlot = ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode(["address", "uint256"], [account, slot]), - ); - await setStorageAt(token, storageSlot, ethers.utils.hexZeroPad(amount.toHexString(), 32)); +export async function setTokenBalance(token: string, account: string, amount: BigNumber, loc: BalanceSlot) { + await setStorageAt(token, mappingSlot(account, loc), ethers.utils.hexZeroPad(amount.toHexString(), 32)); } /* ------------------------------------------------------------------ */ @@ -266,3 +286,142 @@ export async function listBStockMarket(owner: any, initialPriceUsd = parseUnits( }, }; } + +/* ------------------------------------------------------------------ */ +/* mock Native RFQ router */ +/* ------------------------------------------------------------------ */ + +// Deploy the MockNativeRouter used for the illiquid bStock->USDT hop and pre-fund it with USDT so it +// can pay out the "firm quote". `rate` (1e18) is USDT-per-bStock, i.e. the MM's quoted bStock price. +export async function deployFundedMockNative(owner: any, rate: BigNumber, usdtFloat = parseUnits("5000000", 18)) { + const mock = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); + await mock.setRate(rate); + const slot = await findBalanceSlot(TOK.USDT); + if (slot === null) throw new Error("could not locate USDT balance slot"); + await setTokenBalance(TOK.USDT, mock.address, usdtFloat, slot); + return { mock, usdtSlot: slot }; +} + +/* ------------------------------------------------------------------ */ +/* underwater borrower factory */ +/* ------------------------------------------------------------------ */ + +export interface UnderwaterCfg { + mkt: BStockMarket; + vDebt: string; // debt market to borrow from + borrower: string; // a CODELESS EOA (required for the native disbursement path) + collateralBStock: BigNumber; // raw bStock supplied as collateral (18-dec) + borrowAmount: BigNumber; // debt underlying to borrow (debt decimals) + crashPriceTo?: BigNumber; // if set, bStock oracle price is dropped here to force shortfall +} + +// Build a real underwater position: supply bStock, enter the market, borrow the debt asset, then push +// the account into shortfall by crashing the bStock oracle price (the realistic trigger for a bStock +// backstop liquidation — the stock gapped down). Returns the impersonated borrower signer. +export async function makeUnderwaterBorrower(cfg: UnderwaterCfg): Promise { + const borrower = await initMainnetUser(cfg.borrower, parseUnits("10", 18)); + if ((await ethers.provider.getCode(cfg.borrower)) !== "0x") { + throw new Error(`borrower ${cfg.borrower} must be a codeless EOA (native path relies on it)`); + } + await cfg.mkt.bStock.mint(cfg.borrower, cfg.collateralBStock); + await cfg.mkt.bStock.connect(borrower).approve(cfg.mkt.vBStock.address, cfg.collateralBStock); + await new ethers.Contract(cfg.mkt.vBStock.address, VTOKEN_ABI, borrower).mint(cfg.collateralBStock); + await new ethers.Contract(A.COMPTROLLER, COMPTROLLER_ABI, borrower).enterMarkets([cfg.mkt.vBStock.address]); + // Legacy vTokens return an error CODE from borrow() instead of reverting, so a capped/paused/illiquid + // market silently leaves zero debt — which would later surface as an opaque "LiquidationFailed(3)". + // Probe the code first and fail loudly, then execute and assert the debt actually materialized. + const vd = new ethers.Contract(cfg.vDebt, VTOKEN_ABI, borrower); + const code: BigNumber = await vd.callStatic.borrow(cfg.borrowAmount); + if (!code.eq(0)) throw new Error(`borrow(${cfg.vDebt}) returned code ${code.toString()} (cap/paused/liquidity)`); + await vd.borrow(cfg.borrowAmount); + if ((await vd.borrowBalanceStored(cfg.borrower)).eq(0)) throw new Error(`borrow(${cfg.vDebt}) produced no debt`); + if (cfg.crashPriceTo) await cfg.mkt.setPrice(cfg.crashPriceTo); + return borrower; +} + +/* ------------------------------------------------------------------ */ +/* swap-hop builders */ +/* ------------------------------------------------------------------ */ + +// Single hop (USDT debt): sell the whole seized bStock balance to USDT through the mock RFQ router. +export function buildSingleHopMock(mkt: BStockMarket, mock: Contract, liqAddr: string): string { + return mock.interface.encodeFunctionData("swapAll", [mkt.bStock.address, TOK.USDT, liqAddr]); +} + +// Two hops (non-USDT debt): hop-1 mock bStock->USDT (swapAll), hop-2 REAL PancakeSwap USDT->debt. +// The hop-2 amountIn is under-shot so the fixed-amount PCS calldata always fits under the on-chain +// approval (the actual USDT from hop-1 varies with the treasury cut). minOut tracks the live quote. +export async function buildTwoHopMockThenPcs( + owner: any, + mkt: BStockMarket, + mock: Contract, + vDebt: string, + repay: BigNumber, + pathUsdtToDebt: string[], + liqAddr: string, + rate: BigNumber, +): Promise<{ swapCalldata: string; swapCalldata2: string; expectedOut: BigNumber }> { + const comptroller = new ethers.Contract(A.COMPTROLLER, COMPTROLLER_ABI, owner); + const [, seizeTokens] = await comptroller.liquidateCalculateSeizeTokens(vDebt, mkt.vBStock.address, repay); + const xr: BigNumber = await mkt.vBStock.exchangeRateStored(); + const grossBStock = seizeTokens.mul(xr).div(ONE); // bStock the seized vTokens redeem to (pre treasury cut) + const usdtFromHop1 = grossBStock.mul(rate).div(ONE); // mock output at the quoted rate + const x2 = usdtFromHop1.mul(90).div(100); // under-shoot 10% to stay under the real approval after the cut + const pcs = new ethers.Contract(A.PCS_ROUTER, PCS_ABI, owner); + const expectedOut: BigNumber = (await pcs.getAmountsOut(x2, pathUsdtToDebt))[pathUsdtToDebt.length - 1]; + const deadline = ethers.constants.MaxUint256; + return { + swapCalldata: mock.interface.encodeFunctionData("swapAll", [mkt.bStock.address, TOK.USDT, liqAddr]), + swapCalldata2: pcs.interface.encodeFunctionData("swapExactTokensForTokens", [ + x2, + 0, + pathUsdtToDebt, + liqAddr, + deadline, + ]), + expectedOut, + }; +} + +/* ------------------------------------------------------------------ */ +/* invariant assertions */ +/* ------------------------------------------------------------------ */ + +// Post-liquidation invariants that must hold on EVERY successful settle: the liquidator never keeps the +// RFQ-only bStock, never strands native BNB, and the borrower's debt strictly shrank. Proceeds land as +// the debt asset (callers check the returned debtOut against minOut). +export async function assertSettledFor( + liq: Contract, + mkt: BStockMarket, + vDebt: string, + borrower: string, + borrowBefore: BigNumber, +) { + expect(await mkt.bStock.balanceOf(liq.address)).to.equal(0); + expect(await ethers.provider.getBalance(liq.address)).to.equal(0); + const borrowAfter: BigNumber = await new ethers.Contract(vDebt, VTOKEN_ABI, ethers.provider).borrowBalanceStored( + borrower, + ); + expect(borrowAfter).to.be.lt(borrowBefore); +} + +// Rollback invariants: on any revert the borrow is untouched and the liquidator's inventory is intact. +export async function assertRolledBack( + liq: Contract, + mkt: BStockMarket, + vDebt: string, + borrower: string, + borrowBefore: BigNumber, + debtToken: string, + inventoryBefore: BigNumber, +) { + const borrowAfter: BigNumber = await new ethers.Contract(vDebt, VTOKEN_ABI, ethers.provider).borrowBalanceStored( + borrower, + ); + expect(borrowAfter).to.be.gte(borrowBefore); // borrow not reduced (may accrue up) + expect(await new ethers.Contract(debtToken, ERC20_ABI, ethers.provider).balanceOf(liq.address)).to.equal( + inventoryBefore, + ); + expect(await mkt.bStock.balanceOf(liq.address)).to.equal(0); + expect(await ethers.provider.getBalance(liq.address)).to.equal(0); +} diff --git a/yarn.lock b/yarn.lock index 0697b4e48..84a2087e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3396,6 +3396,7 @@ __metadata: eslint: ^8.25.0 eslint-config-prettier: ^8.5.0 ethers: ^5.7.0 + fast-check: ^4.8.0 hardhat: 2.22.18 hardhat-contract-sizer: ^2.8.0 hardhat-dependency-compiler: ^1.2.1 @@ -5928,6 +5929,15 @@ __metadata: languageName: node linkType: hard +"fast-check@npm:^4.8.0": + version: 4.8.0 + resolution: "fast-check@npm:4.8.0" + dependencies: + pure-rand: ^8.0.0 + checksum: f3fd5e07753ba0248eb77359ac8c5fb66c84ed460c086e8673cd87d7ee174cb1fc4aec60acbda7762bbf2127750b5ed0a1041a10b24de5e4ee691bcee2ef70f4 + languageName: node + linkType: hard + "fast-content-type-parse@npm:^3.0.0": version: 3.0.0 resolution: "fast-content-type-parse@npm:3.0.0" @@ -10083,6 +10093,13 @@ __metadata: languageName: node linkType: hard +"pure-rand@npm:^8.0.0": + version: 8.4.1 + resolution: "pure-rand@npm:8.4.1" + checksum: 6f424714a95ad990159ded14c8a5618f0c97a9ea8df9f209e88fba4c902f7fe93e9f960a7ad38411231720b6dee0b9f6502358c34b5b7db3c2b0838a1811c4b8 + languageName: node + linkType: hard + "qrcode-terminal@npm:^0.12.0": version: 0.12.0 resolution: "qrcode-terminal@npm:0.12.0" From a8c58d03464254760d784e6e24e22c5c63cd2c6e Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 20:17:48 +0530 Subject: [PATCH 25/78] =?UTF-8?q?test(bstock):=20richer=20fork=20sweep=20?= =?UTF-8?q?=E2=80=94=20market=20names,=20decoded=20skips,=20cash-seed,=20s?= =?UTF-8?q?ummary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/hardhat/Fork/BStockLiquidatorFork.ts | 65 ++++++++++++++++++++-- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts index 0dfd7f4aa..f2af9bacf 100644 --- a/tests/hardhat/Fork/BStockLiquidatorFork.ts +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -471,9 +471,13 @@ const test = () => { const markets: string[] = await c.getAllMarkets(); let liquidated = 0; const report: string[] = []; + const tally: Record = {}; // reason bucket -> count for (const vDebt of markets) { if (ethers.utils.getAddress(vDebt) === ethers.utils.getAddress(mkt.vBStock.address)) continue; + // Skip markets whose BORROW action is paused: we cannot open a fresh test position there, so + // there is nothing meaningful to liquidate — drop them entirely rather than list them as skips. + if (await c.actionPaused(vDebt, Action.BORROW)) continue; const sym = await marketSymbol(vDebt); const label = `${sym} (${vDebt})`; const snap = await ethers.provider.send("evm_snapshot", []); @@ -482,8 +486,16 @@ const test = () => { if (reason === "OK") { liquidated++; report.push(` ${label}: liquidated`); + tally["liquidated"] = (tally["liquidated"] || 0) + 1; } else { report.push(` ${label}: skipped (${reason})`); + // Bucket by the reason head (e.g. "BorrowNotAllowedInPool", "no PCS route"). + const bucket = reason + .replace(/^borrow \(/, "") + .replace(/\)$/, "") + .split("(")[0] + .trim(); + tally[bucket] = (tally[bucket] || 0) + 1; } } catch (e: any) { // A skip reason is acceptable; any OTHER revert is a genuine failure. @@ -493,7 +505,12 @@ const test = () => { } await ethers.provider.send("evm_revert", [snap]); } + const summary = Object.entries(tally) + .sort((a, b) => b[1] - a[1]) + .map(([k, v]) => `${v} ${k}`) + .join(", "); console.log(" sweep:\n" + report.join("\n")); + console.log(` summary: ${summary}`); expect(liquidated).to.be.gt(0); // at least the USDT/CAKE/BNB markets must liquidate }); }); @@ -672,7 +689,7 @@ async function sweepOne( vDebt: string, fund: (t: string, to: string, a: BigNumber) => Promise, ): Promise { - if (await c.actionPaused(vDebt, Action.BORROW)) return "borrow paused"; + // (BORROW-paused markets are filtered out by the caller before we get here.) if (await c.actionPaused(vDebt, Action.LIQUIDATE)) return "liquidate paused"; const v = new ethers.Contract(vDebt, VTOKEN_ABI, owner); @@ -719,10 +736,21 @@ async function sweepOne( } catch { return "dead oracle"; } - const cap = cash.mul(20).div(100); - if (debtBorrow.gt(cap)) debtBorrow = cap; if (debtBorrow.eq(0)) return "unpriceable/dust"; + // Thin markets don't hold enough cash for an $8k borrow, which would leave the account not-underwater + // (gate LiquidationFailed code=3). Seed the vToken's underlying cash directly so the full borrow fits; + // native BNB cash is the vToken's native balance. If the underlying can't be seeded, classify as skip. + try { + const need = debtBorrow.mul(3); + if (cash.lt(need)) { + if (isBnb) await setBalance(vDebt, need); + else await fund(underlying, vDebt, need); + } + } catch { + return "cannot seed market cash"; + } + // Build the underwater borrower defensively — quirky markets revert borrow ("math error"), hit caps, // or leave the account not-underwater; classify any of these as a skip rather than failing the sweep. const borrower = ethers.utils.getAddress( @@ -740,7 +768,13 @@ async function sweepOne( } catch (e: any) { const data = e.error?.data?.data || e.error?.data || e.data || ""; const sel = typeof data === "string" && data.length >= 10 ? data.slice(0, 10) : ""; - return `borrow (${e.reason || sel || (e.message || "").slice(0, 24)})`; + // Map the common Venus revert selectors to names so the sweep log reads clearly. + const known: Record = { + "0x5b80c790": "BorrowNotAllowedInPool", // market is scoped to a non-core e-mode pool + "0x2e649eed": "BorrowCapExceeded", + "0x48c25881": "BorrowCashNotAvailable", + }; + return `borrow (${e.reason || known[sel] || sel || (e.message || "").slice(0, 24)})`; } await mock.setRate(P_CRASH); const repay = debtBorrow.div(4); @@ -796,12 +830,33 @@ async function sweepOne( const out = await liq.connect(owner).callStatic.liquidate({ ...params, minOut: 1 }); await liq.connect(owner).liquidate({ ...params, minOut: out.mul(80).div(100) }); } catch (e: any) { - return `liquidation (${(e.reason || e.message || "").slice(0, 32)})`; + return `liquidation (${decodeLiqRevert(liq, e)})`; } expect(await v.borrowBalanceStored(borrower)).to.be.lt(borrowBefore); return "OK"; } +// Decode a liquidation revert into a readable reason: the contract's own custom errors (InsufficientOut, +// SwapFailed, RedeemFailed, …) via its ABI, the gate's LiquidationFailed(code), or a plain Error string. +function decodeLiqRevert(liq: Contract, e: any): string { + if (e.reason) return e.reason; + const data = e.error?.data?.data || e.error?.data || e.data || ""; + if (typeof data !== "string" || data.length < 10) return (e.message || "").slice(0, 32); + try { + const parsed = liq.interface.parseError(data); + const args = parsed.args?.length ? `(${parsed.args.map((a: any) => a.toString()).join(",")})` : ""; + return `${parsed.name}${args}`; + } catch { + // Gate error LiquidationFailed(uint256) — the uint is the underlying vToken error code + // (Venus ComptrollerErrorReporter.Error: 3 = INSUFFICIENT_SHORTFALL, 4 = INSUFFICIENT_LIQUIDITY, …). + if (data.slice(0, 10) === "0x125a96ab") { + const code = BigNumber.from("0x" + data.slice(10, 74)); + return `gate LiquidationFailed(code=${code.toString()})`; + } + return data.slice(0, 10); + } +} + if (FORK_MAINNET) { forking(FORK_BLOCK, test); } From 1784444ecb86296eef3edd006b925d6bf7f8229f Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 20:23:26 +0530 Subject: [PATCH 26/78] chore: fix lint --- scripts/bstock/README.md | 56 ++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/scripts/bstock/README.md b/scripts/bstock/README.md index 93c15f6ac..314523398 100644 --- a/scripts/bstock/README.md +++ b/scripts/bstock/README.md @@ -3,11 +3,11 @@ Operator runbook for liquidating bStock (tokenized-stock) collateral in Venus Core. Three entrypoints, one shared goal: repay a borrower's debt, seize their bStock, and offload it. -| Script | Path | What it does | Chain writes? | -|---|---|---|---| -| **Atomic** | `atomic-liquidate.ts` | Primary path. Drives the on-chain `BStockLiquidator` — repay, seize, redeem, and sell bStock in ONE tx via a Native RFQ quote (+ optional AMM hop). | Yes | -| **Safe fallback** | `safe-fallback.ts` | Backstop when Native is unavailable (API down / halt / weekend / thin depth). Emits a Safe{Wallet} batch JSON; signers repay from Safe funds and ship raw bStock to Binance. | No — writes JSON | -| **Native smoke** | `native-smoke.ts` | Pre-flight check. Prints the Native orderbook + a live firm-quote (price, spread, TTL). No chain interaction. | No | +| Script | Path | What it does | Chain writes? | +| ----------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| **Atomic** | `atomic-liquidate.ts` | Primary path. Drives the on-chain `BStockLiquidator` — repay, seize, redeem, and sell bStock in ONE tx via a Native RFQ quote (+ optional AMM hop). | Yes | +| **Safe fallback** | `safe-fallback.ts` | Backstop when Native is unavailable (API down / halt / weekend / thin depth). Emits a Safe{Wallet} batch JSON; signers repay from Safe funds and ship raw bStock to Binance. | No — writes JSON | +| **Native smoke** | `native-smoke.ts` | Pre-flight check. Prints the Native orderbook + a live firm-quote (price, spread, TTL). No chain interaction. | No | Pick **Atomic** first. Fall back to **Safe** only if the Native quote path fails. @@ -53,19 +53,19 @@ contract` → for non-USDT debt, append an AMM hop (USDT→debt) → call `liqui ### Env -| Var | Req | Default | Notes | -|---|---|---|---| -| `LIQUIDATOR` | ✓ | | Deployed `BStockLiquidator` address | -| `BORROWER` | ✓ | | Account to liquidate (must have shortfall) | -| `VBSTOCK` | ✓ | | bStock collateral market (e.g. vTSLAB) | -| `VDEBT` | ✓ | | Borrowed market to repay (e.g. vUSDT) | -| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | -| `MODE` | | `inventory` | `inventory` (own funds) or `flash` (Venus flash-loan) | -| `DRY_RUN` | | | `1` → callStatic only, sends nothing | -| `SLIPPAGE` | | `0.5` | Native slippage % | -| `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) | -| `AMM_PROVIDER` | | `kyberswap` | Hop-2 route for non-USDT debt: `kyberswap` / `openocean` / `pcsv2` | -| `WBNB_ADDR` | | BSC WBNB | Only for a vBNB debt market (native BNB auto-detected; contract unwraps) | +| Var | Req | Default | Notes | +| ---------------- | --- | ----------- | ------------------------------------------------------------------------ | +| `LIQUIDATOR` | ✓ | | Deployed `BStockLiquidator` address | +| `BORROWER` | ✓ | | Account to liquidate (must have shortfall) | +| `VBSTOCK` | ✓ | | bStock collateral market (e.g. vTSLAB) | +| `VDEBT` | ✓ | | Borrowed market to repay (e.g. vUSDT) | +| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | +| `MODE` | | `inventory` | `inventory` (own funds) or `flash` (Venus flash-loan) | +| `DRY_RUN` | | | `1` → callStatic only, sends nothing | +| `SLIPPAGE` | | `0.5` | Native slippage % | +| `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) | +| `AMM_PROVIDER` | | `kyberswap` | Hop-2 route for non-USDT debt: `kyberswap` / `openocean` / `pcsv2` | +| `WBNB_ADDR` | | BSC WBNB | Only for a vBNB debt market (native BNB auto-detected; contract unwraps) | _Fork/local testing:_ `MOCK_NATIVE="router:calldata"` (hop 1), `MOCK_AMM` (hop 2), `MOCK_OUT` (final debt out), `IMPERSONATE=0x..` to run as an operator. @@ -88,16 +88,16 @@ The batch is 4 txs (approve → liquidateBorrow → redeem → transfer): the Sa funds, seizes the bStock, and ships raw bStock to `TARGET` (Binance top-up / custody) for finance to offload on the CEX. -| Var | Req | Default | Notes | -|---|---|---|---| -| `BORROWER` | ✓ | | Account to liquidate | -| `VBSTOCK` | ✓ | | bStock collateral market | -| `VDEBT` | ✓ | | Borrowed market to repay | -| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | -| `TARGET` | ✓ | | Binance top-up / custody address for the bStock (or `ALLOW_PLACEHOLDER=1` for a draft) | -| `SAFE` | | `0xdc6E…2029` | Executing Safe | -| `RPC_URL` | | public dataseed | BSC RPC | -| `OUT` | | `out/bstock-safe-fallback.json` | Output path | +| Var | Req | Default | Notes | +| -------------- | --- | ------------------------------- | -------------------------------------------------------------------------------------- | +| `BORROWER` | ✓ | | Account to liquidate | +| `VBSTOCK` | ✓ | | bStock collateral market | +| `VDEBT` | ✓ | | Borrowed market to repay | +| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | +| `TARGET` | ✓ | | Binance top-up / custody address for the bStock (or `ALLOW_PLACEHOLDER=1` for a draft) | +| `SAFE` | | `0xdc6E…2029` | Executing Safe | +| `RPC_URL` | | public dataseed | BSC RPC | +| `OUT` | | `out/bstock-safe-fallback.json` | Output path | > The batch is a **snapshot** at the current block. If the borrower's position changes before the Safe > executes, **regenerate** — a stale redeem/transfer that exceeds the seized balance reverts the batch. From 9f452db2083ad377231f3e4d8b07c04326ab122d Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 20:37:23 +0530 Subject: [PATCH 27/78] test(bstock): render dynamic sweep results as an aligned table --- tests/hardhat/Fork/BStockLiquidatorFork.ts | 31 +++++++++++++++------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts index f2af9bacf..e6df1cee1 100644 --- a/tests/hardhat/Fork/BStockLiquidatorFork.ts +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -470,7 +470,7 @@ const test = () => { ); const markets: string[] = await c.getAllMarkets(); let liquidated = 0; - const report: string[] = []; + const rows: { sym: string; addr: string; status: string; reason: string }[] = []; const tally: Record = {}; // reason bucket -> count for (const vDebt of markets) { @@ -479,38 +479,49 @@ const test = () => { // there is nothing meaningful to liquidate — drop them entirely rather than list them as skips. if (await c.actionPaused(vDebt, Action.BORROW)) continue; const sym = await marketSymbol(vDebt); - const label = `${sym} (${vDebt})`; const snap = await ethers.provider.send("evm_snapshot", []); try { const reason = await sweepOne(owner, c, pcs, mkt, mock, liq, vDebt, fund); if (reason === "OK") { liquidated++; - report.push(` ${label}: liquidated`); + rows.push({ sym, addr: vDebt, status: "LIQUIDATED", reason: "" }); tally["liquidated"] = (tally["liquidated"] || 0) + 1; } else { - report.push(` ${label}: skipped (${reason})`); - // Bucket by the reason head (e.g. "BorrowNotAllowedInPool", "no PCS route"). - const bucket = reason + // Normalize the reason head for the table + tally (e.g. "BorrowNotAllowedInPool"). + const clean = reason .replace(/^borrow \(/, "") .replace(/\)$/, "") .split("(")[0] .trim(); - tally[bucket] = (tally[bucket] || 0) + 1; + rows.push({ sym, addr: vDebt, status: "skipped", reason: clean }); + tally[clean] = (tally[clean] || 0) + 1; } } catch (e: any) { // A skip reason is acceptable; any OTHER revert is a genuine failure. - report.push(` ${label}: ERROR ${(e.message || "").slice(0, 80)}`); + rows.push({ sym, addr: vDebt, status: "ERROR", reason: (e.message || "").slice(0, 60) }); await ethers.provider.send("evm_revert", [snap]); throw e; } await ethers.provider.send("evm_revert", [snap]); } + + // Render an aligned table: MARKET | ADDRESS | STATUS | REASON. + const w = (s: string, n: number) => s.padEnd(n); + const cSym = Math.max(6, ...rows.map(r => r.sym.length)); + const cStatus = Math.max(6, ...rows.map(r => r.status.length)); + const bar = ` ${"-".repeat(cSym)} ${"-".repeat(42)} ${"-".repeat(cStatus)} ------`; + const lines = [ + ` ${w("MARKET", cSym)} ${w("ADDRESS", 42)} ${w("STATUS", cStatus)} REASON`, + bar, + ...rows.map(r => ` ${w(r.sym, cSym)} ${w(r.addr, 42)} ${w(r.status, cStatus)} ${r.reason}`), + ]; const summary = Object.entries(tally) .sort((a, b) => b[1] - a[1]) .map(([k, v]) => `${v} ${k}`) .join(", "); - console.log(" sweep:\n" + report.join("\n")); - console.log(` summary: ${summary}`); + console.log("\n dynamic sweep — one liquidation attempt per eligible Core market:\n"); + console.log(lines.join("\n")); + console.log(`\n SUMMARY: ${summary}\n`); expect(liquidated).to.be.gt(0); // at least the USDT/CAKE/BNB markets must liquidate }); }); From 159c85e32c1dca8fd744efb67dc6e98df06b2c3d Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 2 Jul 2026 22:03:16 +0530 Subject: [PATCH 28/78] test(bstock): restore chain snapshot after suite to prevent signer state leak The sweepNative test calls setBalance on default signer[3], draining it. Without snapshot isolation this leaked into Fork/TokenRedeemer, whose treasury reuses signer[3] and mints 3000 ETH vBNB in setup, failing under coverage with 'Sender doesn't have enough funds'. Snapshot before the suite and restore after for a clean signer handoff. --- tests/hardhat/BStockLiquidator.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index 0bdea8966..51a91d768 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -1,4 +1,4 @@ -import { setBalance } from "@nomicfoundation/hardhat-network-helpers"; +import { SnapshotRestorer, setBalance, takeSnapshot } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; import { BigNumber, Contract } from "ethers"; import { ethers, upgrades } from "hardhat"; @@ -103,6 +103,17 @@ describe("BStockLiquidator (atomic)", () => { }; } + // Snapshot the pristine chain before the suite and restore it after, so per-test mutations of + // shared default signers (e.g. setBalance(stranger) in the sweepNative test) don't leak into + // later test files that reuse the same signer indexes (e.g. Fork/TokenRedeemer's treasury). + let pristine: SnapshotRestorer; + before(async () => { + pristine = await takeSnapshot(); + }); + after(async () => { + await pristine.restore(); + }); + beforeEach(deploy); describe("inventory mode", () => { From 327a36264eabc90778a1dc3892cf884434cd5cbe Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 3 Jul 2026 10:59:19 +0530 Subject: [PATCH 29/78] chore: consolidate router validation into a single function --- contracts/BStock/BStockLiquidator.sol | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index ce25d9d97..0e67b123b 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -193,8 +193,7 @@ contract BStockLiquidator is function liquidate( LiquidationParams calldata params ) external override onlyOperator nonReentrant returns (uint256 debtOut) { - _validateRouter(params.router); - if (params.router2 != address(0)) _validateRouter(params.router2); + _validateRouters(params.router, params.router2); if (params.minOut == 0) revert ZeroMinOut(); if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp); @@ -217,8 +216,7 @@ contract BStockLiquidator is /// @inheritdoc IBStockLiquidator function flashLiquidate(LiquidationParams calldata params) external override onlyOperator nonReentrant { - _validateRouter(params.router); - if (params.router2 != address(0)) _validateRouter(params.router2); + _validateRouters(params.router, params.router2); if (params.minOut == 0) revert ZeroMinOut(); if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp); @@ -294,11 +292,13 @@ contract BStockLiquidator is // Core // // --------------------------------------------------------------------- // - /// @dev Pre-flight: the swap router must be allowlisted. Liquidatability itself is not - /// pre-checked here — Core's `liquidateBorrowAllowed` already enforces it, and pre-checking - /// shortfall would wrongly block forced liquidations (which liquidate healthy accounts). - function _validateRouter(address router) private view { + /// @dev Pre-flight: every swap router must be allowlisted. `router2` is optional (single-hop when + /// zero) so it is only checked when set. Liquidatability itself is not pre-checked here — Core's + /// `liquidateBorrowAllowed` already enforces it, and pre-checking shortfall would wrongly block + /// forced liquidations (which liquidate healthy accounts). + function _validateRouters(address router, address router2) private view { if (!isRouter[router]) revert RouterNotAllowed(router); + if (router2 != address(0) && !isRouter[router2]) revert RouterNotAllowed(router2); } /// @dev One swap hop: approve the exact `amount` to the allowlisted `router`, forward the opaque From 08f8c328e15be4686888203051d258987aec84e3 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 3 Jul 2026 11:25:19 +0530 Subject: [PATCH 30/78] docs: clarify comments regarding native BNB debt handling in atomicLiquidate function --- scripts/bstock/atomic-liquidate.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 59b1f0690..7b6e3833c 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -31,12 +31,12 @@ * SLIPPAGE Native slippage %, default 0.5 * MIN_OUT_BUFFER extra haircut on minOut beyond slippage, default 0.5 (%) * AMM_PROVIDER hop-2 route source for non-USDT debt: kyberswap (default) | openocean | pcsv2 - * WBNB_ADDR WBNB token, used only for a vBNB (native BNB) debt market (default: BSC WBNB). - * Native BNB debt is auto-detected (vBNB has no underlying()) and accounted in WBNB; - * the contract unwraps the repay, so pre-fund inventory in WBNB (MODE=inventory). * DRY_RUN "1" -> callStatic only, send nothing * MOCK_NATIVE hop-1 "router:calldata" for fork/local tests (see below) * MOCK_AMM hop-2 "router:calldata" for fork/local tests (two-hop); MOCK_OUT = final debt out + * + * Native BNB debt (vBNB) is auto-detected — vBNB has no underlying() — and accounted in WBNB at its + * canonical BSC address; the contract unwraps the repay, so pre-fund inventory in WBNB (MODE=inventory). */ import { BigNumber, Contract, Signer } from "ethers"; import { ethers } from "hardhat"; @@ -91,6 +91,8 @@ export async function atomicLiquidate(signer: Signer) { // vBNB has no underlying(): a native-BNB debt is accounted in WBNB (1:1 with BNB). The contract // unwraps the repay internally, so off-chain the debt asset for the swap chain + minOut is WBNB. + // WBNB is immutable on BSC, so it is the canonical constant; WBNB_ADDR only overrides it so a + // non-fork test can point at a freshly-deployed mock (mirrors USDT_ADDR below). let debtAddr: string; let isBnb = false; try { From 28ba5945ef78eac5b3b372279cf27122d39da752 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 3 Jul 2026 11:38:39 +0530 Subject: [PATCH 31/78] fix(bstock):: enforce Venus Liquidator gate checks in atomicLiquidate and safe-fallback scripts --- scripts/bstock/atomic-liquidate.ts | 33 ++++++++------- scripts/bstock/safe-fallback.ts | 57 ++++++++++++++------------ tests/hardhat/BStockAtomicLiquidate.ts | 9 ++++ tests/hardhat/BStockSafeFallback.ts | 17 ++------ 4 files changed, 63 insertions(+), 53 deletions(-) diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 7b6e3833c..520e46ff6 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -123,21 +123,26 @@ export async function atomicLiquidate(signer: Signer) { const exchangeRate: BigNumber = await vBStock.exchangeRateStored(); const ONE = BigNumber.from(10).pow(18); - // The seize is routed through the pool-wide Venus Liquidator, which keeps a treasury cut of the - // liquidation BONUS (see Liquidator._splitLiquidationIncentive), so this contract receives fewer - // vTokens than `seizeTokens`. Deduct that cut, else the precomputed amount overstates our holdings - // and the fixed-amountIn router pull reverts. 0 today, but governance-settable. - let vReceived = seizeTokens; + // The contract routes EVERY repay through the pool-wide Venus Liquidator gate and reverts + // (ensureNonzeroAddress) when it is unset, so abort here rather than build a call that would revert. const gate: string = await comptroller.liquidatorContract(); - if (gate !== ethers.constants.AddressZero) { - const venusLiquidator = new Contract(gate, VENUS_LIQUIDATOR_ABI, signer); - const liqTreasuryPct: BigNumber = await venusLiquidator.treasuryPercentMantissa(); - if (!liqTreasuryPct.eq(0)) { - const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); - const bonusAmount = seizeTokens.mul(totalIncentive.sub(ONE)).div(totalIncentive); - const treasuryCut = bonusAmount.mul(liqTreasuryPct).div(ONE); - vReceived = seizeTokens.sub(treasuryCut); - } + if (gate === ethers.constants.AddressZero) { + throw new Error( + "Venus Liquidator gate (comptroller.liquidatorContract) is unset — the contract routes every repay through it and reverts when unset", + ); + } + + // The gate keeps a treasury cut of the liquidation BONUS (see Liquidator._splitLiquidationIncentive), + // so this contract receives fewer vTokens than `seizeTokens`. Deduct that cut, else the precomputed + // amount overstates our holdings and the fixed-amountIn router pull reverts. 0 today, but governance-settable. + let vReceived = seizeTokens; + const venusLiquidator = new Contract(gate, VENUS_LIQUIDATOR_ABI, signer); + const liqTreasuryPct: BigNumber = await venusLiquidator.treasuryPercentMantissa(); + if (!liqTreasuryPct.eq(0)) { + const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); + const bonusAmount = seizeTokens.mul(totalIncentive.sub(ONE)).div(totalIncentive); + const treasuryCut = bonusAmount.mul(liqTreasuryPct).div(ONE); + vReceived = seizeTokens.sub(treasuryCut); } // Core redeem then routes `treasuryPercent` of the redeemed underlying to the treasury, so we hold diff --git a/scripts/bstock/safe-fallback.ts b/scripts/bstock/safe-fallback.ts index 06a181720..cd4c1fd49 100644 --- a/scripts/bstock/safe-fallback.ts +++ b/scripts/bstock/safe-fallback.ts @@ -10,16 +10,17 @@ * batch JSON for the signers to review and execute. * * Two logical actions, four transactions in one atomic batch. The repay is routed through the pool-wide - * Venus Liquidator gate when one is set (a direct vDebt.liquidateBorrow would revert UNAUTHORIZED); when - * unset, the Safe liquidates directly: + * Venus Liquidator gate (a direct vDebt.liquidateBorrow would revert UNAUTHORIZED). The on-chain + * BStockLiquidator routes every repay through this gate and reverts when it is unset, so this script + * aborts if the gate is unset rather than emit a batch that would revert on execution: * Action 1 — fund + repay + seize: - * 1. debtUnderlying.approve(gate | vDebt, repay) // let liquidateBorrow pull Safe funds - * 2. gate.liquidateBorrow(vDebt, borrower, repay, vBStock) // routed; or vDebt.liquidateBorrow(borrower, repay, vBStock) + * 1. debtUnderlying.approve(gate, repay) // let the gate's liquidateBorrow pull Safe funds + * 2. gate.liquidateBorrow(vDebt, borrower, repay, vBStock) // routed through the Venus Liquidator * 3. vBStock.redeem(received) // the vBStock the Safe was credited -> raw bStock * Action 2 — hand off: * 4. bStock.transfer(TARGET, seizedRaw) // raw bStock -> Binance top-up * - * Seize amount is the on-chain truth from `Comptroller.liquidateCalculateSeizeTokens`. When routed, the + * Seize amount is the on-chain truth from `Comptroller.liquidateCalculateSeizeTokens`. The Venus * Liquidator keeps a treasury cut of the liquidation bonus, so the Safe is credited fewer vTokens than * `seizeTokens`; we redeem only that credited amount (`received`). Snapshotted at the current block — if * the borrower's position changes before the Safe executes, REGENERATE, else a stale redeem/transfer @@ -106,15 +107,18 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { const closeFactor: BigNumber = await comptroller.closeFactorMantissa(); console.log(`repay: ${env("REPAY_AMOUNT")} ${debtSym} (closeFactor=${utils.formatEther(closeFactor)})`); - // Pool-wide Venus Liquidator gate: when set, a direct vDebt.liquidateBorrow from the Safe reverts - // UNAUTHORIZED, so we route the repay through that contract (its permissionless entry). When unset, - // the Safe liquidates directly. `repaySpender` is whichever pulls the repay. + // Pool-wide Venus Liquidator gate: while it is set, a direct vDebt.liquidateBorrow from the Safe + // reverts UNAUTHORIZED, so the repay is routed through that contract (its permissionless entry). The + // on-chain BStockLiquidator routes every repay through this gate and reverts (ensureNonzeroAddress) + // when unset, so align here and abort rather than emit a batch that would revert on execution. const gate: string = await comptroller.liquidatorContract(); - const routed = gate !== ZERO; - const repaySpender = routed ? utils.getAddress(gate) : vDebt.address; - console.log( - routed ? `routing repay through Venus Liquidator ${gate}` : "liquidatorContract unset — liquidating directly", - ); + if (gate === ZERO) { + throw new Error( + "Venus Liquidator gate (comptroller.liquidatorContract) is unset — the liquidation routes every repay through it and reverts when unset", + ); + } + const repaySpender = utils.getAddress(gate); + console.log(`routing repay through Venus Liquidator ${gate}`); const safeDebtBal: BigNumber = await debt.balanceOf(safe); if (safeDebtBal.lt(repay)) { @@ -133,18 +137,16 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { ); if (!seizeErr.eq(0)) throw new Error(`liquidateCalculateSeizeTokens error code ${seizeErr}`); - // When routed, the Venus Liquidator keeps a treasury cut of the liquidation BONUS (see + // The Venus Liquidator keeps a treasury cut of the liquidation BONUS (see // Liquidator._splitLiquidationIncentive), so the Safe is credited fewer vTokens than seizeTokens. // Redeem only the credited amount, else the batch reverts. let vReceived = seizeTokens; - if (routed) { - const liqTreasuryPct: BigNumber = await new Contract(gate, LIQUIDATOR_ABI, provider).treasuryPercentMantissa(); - if (!liqTreasuryPct.eq(0)) { - const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); - const bonusAmount = seizeTokens.mul(totalIncentive.sub(ONE)).div(totalIncentive); - const treasuryCut = bonusAmount.mul(liqTreasuryPct).div(ONE); - vReceived = seizeTokens.sub(treasuryCut); - } + const liqTreasuryPct: BigNumber = await new Contract(gate, LIQUIDATOR_ABI, provider).treasuryPercentMantissa(); + if (!liqTreasuryPct.eq(0)) { + const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); + const bonusAmount = seizeTokens.mul(totalIncentive.sub(ONE)).div(totalIncentive); + const treasuryCut = bonusAmount.mul(liqTreasuryPct).div(ONE); + vReceived = seizeTokens.sub(treasuryCut); } // Raw bStock from redeeming vReceived, after Core's redeem treasuryPercent fee, FLOORED at the current @@ -169,9 +171,12 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { } // --- build batch --- - const liquidateTx = routed - ? call(gate, "liquidateBorrow(address,address,uint256,address)", [vDebt.address, borrower, repay, vBStock.address]) - : call(vDebt.address, "liquidateBorrow(address,uint256,address)", [borrower, repay, vBStock.address]); + const liquidateTx = call(gate, "liquidateBorrow(address,address,uint256,address)", [ + vDebt.address, + borrower, + repay, + vBStock.address, + ]); const txs = [ call(debt.address, "approve(address,uint256)", [repaySpender, repay]), @@ -192,7 +197,7 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { transactions: txs, }); - return { batch, txs, routed, gate, seizeTokens, vReceived, seizedRaw, target }; + return { batch, txs, gate, seizeTokens, vReceived, seizedRaw, target }; } async function main() { diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index 547784d88..226b1c4a9 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -146,6 +146,15 @@ describe("bStock atomic liquidation script", () => { expect(await usdt.balanceOf(liq.address)).to.equal(REPAY); // untouched }); + it("refuses when the Venus Liquidator gate is unset (aligns with the contract)", async () => { + await usdt.mint(liq.address, REPAY); + await comptroller.setLiquidatorContract(ethers.constants.AddressZero); + setEnv(); + + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/unset/i); + expect(await usdt.balanceOf(liq.address)).to.equal(REPAY); // untouched + }); + it("refuses a router that is not allowlisted on the contract", async () => { await usdt.mint(liq.address, REPAY); // Deploy a second router that was never allowlisted, and point the (mock) quote at it. diff --git a/tests/hardhat/BStockSafeFallback.ts b/tests/hardhat/BStockSafeFallback.ts index 07ca770fa..4a5387df5 100644 --- a/tests/hardhat/BStockSafeFallback.ts +++ b/tests/hardhat/BStockSafeFallback.ts @@ -13,9 +13,8 @@ const REPAY = U("5000"); const INCENTIVE = U("1.1"); const SEIZE = REPAY.mul(INCENTIVE).div(ONE); // 5500 vBStock at the mock's 1.1x incentive -// liquidateBorrow selectors: 4-arg (routed, ILiquidator) vs 3-arg (direct, VBep20). +// liquidateBorrow selector: the 4-arg ILiquidator (gate) form the script always routes through. const SEL_ROUTED = ethers.utils.id("liquidateBorrow(address,address,uint256,address)").slice(0, 10); -const SEL_DIRECT = ethers.utils.id("liquidateBorrow(address,uint256,address)").slice(0, 10); describe("BStock safe-fallback batch generator", () => { let owner: any, borrower: any, target: any; @@ -62,9 +61,8 @@ describe("BStock safe-fallback batch generator", () => { it("routes the repay through the Venus Liquidator gate (T1)", async () => { setEnv(); - const { txs, routed, gate, vReceived, seizedRaw } = await buildSafeFallbackBatch(ethers.provider); + const { txs, gate, vReceived, seizedRaw } = await buildSafeFallbackBatch(ethers.provider); - expect(routed).to.equal(true); expect(ethers.utils.getAddress(gate)).to.equal(venusLiq.address); expect(txs).to.have.length(4); @@ -92,17 +90,10 @@ describe("BStock safe-fallback batch generator", () => { expect(seizedRaw).to.equal(SEIZE); // cut 0, treasuryPercent 0 → full seize at 1:1 rate }); - it("falls back to a direct liquidateBorrow when the gate is unset", async () => { + it("throws when the gate is unset, aligning with the on-chain liquidator", async () => { await comptroller.setLiquidatorContract(ethers.constants.AddressZero); setEnv(); - const { txs, routed } = await buildSafeFallbackBatch(ethers.provider); - - expect(routed).to.equal(false); - // approve spender is vDebt, liquidateBorrow is the 3-arg VBep20 form on vDebt - const approve = ethers.utils.defaultAbiCoder.decode(["address", "uint256"], "0x" + txs[0].data.slice(10)); - expect(approve[0]).to.equal(vDebt.address); - expect(ethers.utils.getAddress(txs[1].to)).to.equal(vDebt.address); - expect(txs[1].data.slice(0, 10)).to.equal(SEL_DIRECT); + await expect(buildSafeFallbackBatch(ethers.provider)).to.be.rejectedWith(/unset/i); }); it("deducts the Liquidator bonus-cut so redeem matches what the Safe is credited", async () => { From 3cef17c4aa485b50f883b6133b3a8c2a34d642d9 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 3 Jul 2026 11:58:08 +0530 Subject: [PATCH 32/78] feat(bstock): add SEIZE_BUFFER parameter to atomicLiquidate for improved quote accuracy --- scripts/bstock/atomic-liquidate.ts | 26 +++++++++++++++-- tests/hardhat/BStockAtomicLiquidate.ts | 40 ++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 520e46ff6..7208677c7 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -30,6 +30,10 @@ * MODE "inventory" (default) | "flash" * SLIPPAGE Native slippage %, default 0.5 * MIN_OUT_BUFFER extra haircut on minOut beyond slippage, default 0.5 (%) + * SEIZE_BUFFER haircut on the QUOTED seize so a small oracle uptick can't make the router pull more + * bStock than was seized (which reverts). Default 0.1 (%); unsold remainder is sweepable. + * Keep it small: in MODE=flash the quoted proceeds must still cover principal + premium, + * so an oversized buffer trips the on-chain InsufficientOut (run DRY_RUN first). * AMM_PROVIDER hop-2 route source for non-USDT debt: kyberswap (default) | openocean | pcsv2 * DRY_RUN "1" -> callStatic only, send nothing * MOCK_NATIVE hop-1 "router:calldata" for fork/local tests (see below) @@ -81,6 +85,13 @@ export async function atomicLiquidate(signer: Signer) { const mode = (process.env.MODE || "inventory").toLowerCase(); const slippage = Number(process.env.SLIPPAGE || "0.5"); const minOutBufferPct = Number(process.env.MIN_OUT_BUFFER || "0.5"); + const seizeBufferPct = Number(process.env.SEIZE_BUFFER || "0.1"); + // Bound the haircut: a garbage or oversized value would silently under-quote (and in flash mode + // starve the principal + premium repay). The on-chain InsufficientOut still backstops it, but fail + // loudly here instead of after burning a Native quote. + if (!Number.isFinite(seizeBufferPct) || seizeBufferPct < 0 || seizeBufferPct >= 100) { + throw new Error(`SEIZE_BUFFER must be a percent in [0, 100), got "${process.env.SEIZE_BUFFER}"`); + } const liquidator = new Contract(env("LIQUIDATOR"), LIQUIDATOR_ABI, signer); const borrower = ethers.utils.getAddress(env("BORROWER")); @@ -153,6 +164,15 @@ export async function atomicLiquidate(signer: Signer) { const seizedHuman = ethers.utils.formatUnits(seizedRaw, bStockDec); console.log(`seize ${ethers.utils.formatUnits(seizeTokens, 8)} v${bStockSym} -> ~${seizedHuman} ${bStockSym}`); + // `seizedRaw` is derived from oracle prices at quote time. If the bStock price ticks UP before the + // settle tx lands, Comptroller seizes FEWER bStock than this — but the Native firm quote bakes in a + // FIXED amountIn and the contract approves only the actual seized amount to the router, so an amountIn + // above the real seize makes the router pull more than approved and revert SwapFailed(). Quote for + // `seizeBufferPct` LESS so amountIn stays at/below the real seize across small upward drift; the tiny + // unsold remainder just accrues as bStock inventory (recoverable via sweep). + const seizedForQuote = seizedRaw.mul(Math.round((100 - seizeBufferPct) * 100)).div(10000); + const seizedHumanQuote = ethers.utils.formatUnits(seizedForQuote, bStockDec); + // 3. Build the swap, taker = the LIQUIDATOR CONTRACT (so it can submit + receive the debt asset). // The contract measures proceeds in the DEBT asset and enforces `minOut` in it. Native only quotes // bStock->USDT on BSC (every bStock pair in the live orderbook is *<->USDT), so: @@ -191,7 +211,7 @@ export async function atomicLiquidate(signer: Signer) { fromAddress: liquidator.address, tokenIn: bStock.address, tokenOut: nativeOut, - amount: seizedHuman, + amount: seizedHumanQuote, slippage, }); const ttl = quoteDeadline(q) - Math.floor(Date.now() / 1000); @@ -218,13 +238,13 @@ export async function atomicLiquidate(signer: Signer) { intermediateToken = nativeOut; amountOut = BigNumber.from(amm.expectedOut); console.log( - `Native: ${seizedHuman} ${bStockSym} -> ${ethers.utils.formatUnits(midOut, 18)} USDT (TTL ${ttl}s, ${router}); ` + + `Native: ${seizedHumanQuote} ${bStockSym} -> ${ethers.utils.formatUnits(midOut, 18)} USDT (TTL ${ttl}s, ${router}); ` + `AMM: -> ${ethers.utils.formatUnits(amountOut, debtDec)} ${debtSym} (${router2})`, ); } else { amountOut = midOut; console.log( - `Native quote: ${seizedHuman} ${bStockSym} -> ${ethers.utils.formatUnits(amountOut, debtDec)} ${debtSym} (TTL ${ttl}s, router ${router})`, + `Native quote: ${seizedHumanQuote} ${bStockSym} -> ${ethers.utils.formatUnits(amountOut, debtDec)} ${debtSym} (TTL ${ttl}s, router ${router})`, ); } } diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index 226b1c4a9..4559d1490 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -36,6 +36,8 @@ const SCRIPT_ENV = [ "DRY_RUN", "SLIPPAGE", "MIN_OUT_BUFFER", + "SEIZE_BUFFER", + "NATIVE_API_KEY", ] as const; describe("bStock atomic liquidation script", () => { @@ -125,6 +127,44 @@ describe("bStock atomic liquidation script", () => { expect(await bStock.balanceOf(liq.address)).to.equal(0); }); + it("buffers the quoted seize below the on-chain seize (SEIZE_BUFFER)", async () => { + await usdt.mint(liq.address, REPAY); // inventory float + // Exercise the REAL Native quote path (no MOCK_NATIVE) by stubbing fetch, so the buffer applied to + // the quote `amount` is observable. The mock router's swapAll pulls the whole balance regardless of + // the quoted amountIn, so this asserts the quote is scaled down; it can't reproduce the on-chain + // over-pull the buffer guards against. + let quotedAmount: string | null = null; + const realFetch = globalThis.fetch; + globalThis.fetch = (async (url: unknown) => { + quotedAmount = new URL(String(url)).searchParams.get("amount"); + return { + json: async () => ({ + success: true, + recipient: liq.address, + amountIn: "0", + amountOut: OUT.toString(), + orders: [{ deadlineTimestamp: Math.floor(Date.now() / 1000) + 3600 }], + txRequest: { target: router.address, calldata: swapAllCalldata(liq.address), value: "0" }, + }), + }; + }) as unknown as typeof fetch; + + try { + setEnv({ MOCK_NATIVE: "", MOCK_OUT: "", NATIVE_API_KEY: "test-key", SEIZE_BUFFER: "10" }); + await atomicLiquidate(owner); + } finally { + globalThis.fetch = realFetch; + } + + // Seize is 5500 bStock (1:1 redeem, 0 cut, 0 treasuryPercent); a 10% buffer quotes for 4950. + expect(quotedAmount).to.equal("4950.0"); + }); + + it("rejects an out-of-range SEIZE_BUFFER before quoting", async () => { + setEnv({ SEIZE_BUFFER: "150" }); + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/SEIZE_BUFFER/); + }); + it("flash mode: routes through flashLiquidate, repays principal + premium, keeps the rest", async () => { await usdt.mint(vDebt.address, REPAY); // the vToken lends the principal await comptroller.setFlashPremium(U("0.001")); // 0.1% premium From 812c60004ef2832b93a7eb54a1ab778ec2f874fe Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 3 Jul 2026 12:05:29 +0530 Subject: [PATCH 33/78] feat(bstock): support native BNB debt in safe fallback batch - vBNB has no underlying(), so detect it by catching the revert and account the debt as native BNB (18 decimals) instead of crashing - drop the ERC20 approve on the native path and send the repay as msg.value, matching the Liquidator's msg.value == repayAmount check - read the Safe's native balance for the pre-execution fund check - extend the Safe-tx helper to carry an optional native value --- scripts/bstock/lib/safe.ts | 8 ++--- scripts/bstock/safe-fallback.ts | 51 ++++++++++++++++++----------- tests/hardhat/BStockSafeFallback.ts | 29 ++++++++++++++++ 3 files changed, 65 insertions(+), 23 deletions(-) diff --git a/scripts/bstock/lib/safe.ts b/scripts/bstock/lib/safe.ts index ecab0bd22..cb030e8f1 100644 --- a/scripts/bstock/lib/safe.ts +++ b/scripts/bstock/lib/safe.ts @@ -8,7 +8,7 @@ * * Schema ref: https://github.com/safe-global/safe-react-apps (tx-builder batch). */ -import { utils } from "ethers"; +import { BigNumber, BigNumberish, utils } from "ethers"; export interface SafeTx { to: string; @@ -31,13 +31,13 @@ export interface SafeBatch { transactions: SafeTx[]; } -/** Encode a single call: `tx(to, encode(signature, args))`. */ -export function call(to: string, signature: string, args: unknown[]): SafeTx { +/** Encode a single call: `tx(to, encode(signature, args))`, optionally carrying native `value` (wei). */ +export function call(to: string, signature: string, args: unknown[], value: BigNumberish = 0): SafeTx { const iface = new utils.Interface([`function ${signature}`]); const fn = signature.slice(0, signature.indexOf("(")); return { to: utils.getAddress(to), - value: "0", + value: BigNumber.from(value).toString(), data: iface.encodeFunctionData(fn, args), contractMethod: null, contractInputsValues: null, diff --git a/scripts/bstock/safe-fallback.ts b/scripts/bstock/safe-fallback.ts index cd4c1fd49..41827c857 100644 --- a/scripts/bstock/safe-fallback.ts +++ b/scripts/bstock/safe-fallback.ts @@ -9,13 +9,15 @@ * This script does NOT send anything. It READS chain state and EMITS a Safe Transaction Builder * batch JSON for the signers to review and execute. * - * Two logical actions, four transactions in one atomic batch. The repay is routed through the pool-wide - * Venus Liquidator gate (a direct vDebt.liquidateBorrow would revert UNAUTHORIZED). The on-chain + * Two logical actions, three or four transactions in one atomic batch. The repay is routed through the + * pool-wide Venus Liquidator gate (a direct vDebt.liquidateBorrow would revert UNAUTHORIZED). The on-chain * BStockLiquidator routes every repay through this gate and reverts when it is unset, so this script * aborts if the gate is unset rather than emit a batch that would revert on execution: * Action 1 — fund + repay + seize: * 1. debtUnderlying.approve(gate, repay) // let the gate's liquidateBorrow pull Safe funds + * // (ERC20 debt only; skipped for native BNB) * 2. gate.liquidateBorrow(vDebt, borrower, repay, vBStock) // routed through the Venus Liquidator + * // native BNB debt (vBNB): repay is sent as msg.value * 3. vBStock.redeem(received) // the vBStock the Safe was credited -> raw bStock * Action 2 — hand off: * 4. bStock.transfer(TARGET, seizedRaw) // raw bStock -> Binance top-up @@ -88,15 +90,21 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { const vBStock = new Contract(env("VBSTOCK"), VTOKEN_ABI, provider); const vDebt = new Contract(env("VDEBT"), VTOKEN_ABI, provider); const comptroller = new Contract(await vBStock.comptroller(), COMPTROLLER_ABI, provider); - const debt = new Contract(await vDebt.underlying(), ERC20_ABI, provider); + + // vBNB has no underlying(): a native-BNB debt market is repaid in native BNB, so there is no ERC20 + // debt token to approve or read. Detect it the way the atomic script does — try underlying(), treat + // a revert as native BNB (18 decimals, "BNB"). `debt` stays undefined on the native path. + let isBnb = false; + let debt: Contract | undefined; + try { + debt = new Contract(await vDebt.underlying(), ERC20_ABI, provider); + } catch { + isBnb = true; + } const bStock = new Contract(await vBStock.underlying(), ERC20_ABI, provider); - const [debtDec, debtSym, bStockDec, bStockSym] = await Promise.all([ - debt.decimals(), - debt.symbol(), - bStock.decimals(), - bStock.symbol(), - ]); + const [bStockDec, bStockSym] = await Promise.all([bStock.decimals(), bStock.symbol()]); + const [debtDec, debtSym] = isBnb ? [18, "BNB"] : await Promise.all([debt!.decimals(), debt!.symbol()]); const repay = utils.parseUnits(env("REPAY_AMOUNT"), debtDec); // --- read-only sanity checks (warn, do not block) --- @@ -120,7 +128,7 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { const repaySpender = utils.getAddress(gate); console.log(`routing repay through Venus Liquidator ${gate}`); - const safeDebtBal: BigNumber = await debt.balanceOf(safe); + const safeDebtBal: BigNumber = isBnb ? await provider.getBalance(safe) : await debt!.balanceOf(safe); if (safeDebtBal.lt(repay)) { console.warn( `WARN: Safe ${safe} holds ${utils.formatUnits(safeDebtBal, debtDec)} ${debtSym} < repay ` + @@ -171,20 +179,25 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { } // --- build batch --- - const liquidateTx = call(gate, "liquidateBorrow(address,address,uint256,address)", [ - vDebt.address, - borrower, - repay, - vBStock.address, - ]); + // Native BNB debt (vBNB): the Liquidator forwards msg.value to vBNB.liquidateBorrow and requires + // msg.value == repay (see Liquidator.liquidateBorrow), so the Safe sends repay as native value and + // there is no ERC20 to approve. ERC20 debt: approve the gate first, then a zero-value liquidateBorrow. + const liquidateTx = call( + gate, + "liquidateBorrow(address,address,uint256,address)", + [vDebt.address, borrower, repay, vBStock.address], + isBnb ? repay : 0, + ); - const txs = [ - call(debt.address, "approve(address,uint256)", [repaySpender, repay]), - liquidateTx, + const seizeTxs = [ call(vBStock.address, "redeem(uint256)", [vReceived]), call(bStock.address, "transfer(address,uint256)", [target, seizedRaw]), ]; + const txs = isBnb + ? [liquidateTx, ...seizeTxs] + : [call(debt!.address, "approve(address,uint256)", [repaySpender, repay]), liquidateTx, ...seizeTxs]; + const batch = buildBatch({ chainId: CHAIN_ID, safe, diff --git a/tests/hardhat/BStockSafeFallback.ts b/tests/hardhat/BStockSafeFallback.ts index 4a5387df5..b5fcc2801 100644 --- a/tests/hardhat/BStockSafeFallback.ts +++ b/tests/hardhat/BStockSafeFallback.ts @@ -16,6 +16,10 @@ const SEIZE = REPAY.mul(INCENTIVE).div(ONE); // 5500 vBStock at the mock's 1.1x // liquidateBorrow selector: the 4-arg ILiquidator (gate) form the script always routes through. const SEL_ROUTED = ethers.utils.id("liquidateBorrow(address,address,uint256,address)").slice(0, 10); +// Sentinel native BNB market (vBNB); no code, so underlying() reverts and the script treats the debt +// as native BNB — mirrors the atomic suite. +const VBNB = ethers.utils.getAddress("0x0000000000000000000000000000000000000b0b"); + describe("BStock safe-fallback batch generator", () => { let owner: any, borrower: any, target: any; let usdt: Contract, bStock: Contract; @@ -120,4 +124,29 @@ describe("BStock safe-fallback batch generator", () => { const expected = SEIZE.mul(ONE.sub(U("0.2"))).div(ONE); expect(seizedRaw).to.equal(expected); }); + + // Native BNB debt: VDEBT is vBNB (no underlying()), so the script auto-detects it, drops the approve + // (nothing to approve), and sends repay as msg.value on the routed liquidateBorrow — the Liquidator + // forwards it to vBNB and requires msg.value == repay. + it("bnb debt: auto-detects vBNB, drops the approve, sends repay as msg.value", async () => { + setEnv({ VDEBT: VBNB }); + const { txs, seizedRaw } = await buildSafeFallbackBatch(ethers.provider); + + // three txs: no ERC20 approve on the native path + expect(txs).to.have.length(3); + + // 1. liquidateBorrow → the gate, 4-arg selector, repay carried as native value + expect(ethers.utils.getAddress(txs[0].to)).to.equal(venusLiq.address); + expect(txs[0].data.slice(0, 10)).to.equal(SEL_ROUTED); + expect(txs[0].value).to.equal(REPAY.toString()); + + // 2. redeem → the credited amount (cut 0 here, so == full seize) + expect(ethers.utils.getAddress(txs[1].to)).to.equal(vBStock.address); + + // 3. transfer raw bStock to the target + expect(ethers.utils.getAddress(txs[2].to)).to.equal(bStock.address); + const xfer = ethers.utils.defaultAbiCoder.decode(["address", "uint256"], "0x" + txs[2].data.slice(10)); + expect(xfer[0]).to.equal(target.address); + expect(xfer[1]).to.equal(seizedRaw); + }); }); From 43409ff8f08fa3abe0ab368e2aad14dc3041e4bf Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 3 Jul 2026 16:36:18 +0530 Subject: [PATCH 34/78] chore(bstock): deploy BStockLiquidator to bscmainnet Add the 019 deploy script (owner Safe via initialize, timelock proxy admin, BscScan verify) and record the bscmainnet deployment artifacts and migration entry. --- deploy/019-deploy-bstock-liquidator.ts | 53 +- deployments/bscmainnet/.migrations.json | 3 +- deployments/bscmainnet/BStockLiquidator.json | 991 ++++++++++++++ .../BStockLiquidator_Implementation.json | 1190 +++++++++++++++++ .../bscmainnet/BStockLiquidator_Proxy.json | 271 ++++ .../b16b9fd9d739d9f7b43142447745f751.json | 142 ++ 6 files changed, 2638 insertions(+), 12 deletions(-) create mode 100644 deployments/bscmainnet/BStockLiquidator.json create mode 100644 deployments/bscmainnet/BStockLiquidator_Implementation.json create mode 100644 deployments/bscmainnet/BStockLiquidator_Proxy.json create mode 100644 deployments/bscmainnet/solcInputs/b16b9fd9d739d9f7b43142447745f751.json diff --git a/deploy/019-deploy-bstock-liquidator.ts b/deploy/019-deploy-bstock-liquidator.ts index 115b46400..6f831c2bc 100644 --- a/deploy/019-deploy-bstock-liquidator.ts +++ b/deploy/019-deploy-bstock-liquidator.ts @@ -1,22 +1,31 @@ import { DeployFunction } from "hardhat-deploy/types"; import { HardhatRuntimeEnvironment } from "hardhat/types"; +// OPERATIONAL owner of the bStock backstop (setRouter / setOperator / sweep / sweepNative). The Safe +// owns the contract from t0, so there is no transient EOA ownership over a fund-custody contract. +const BSTOCK_LIQUIDATOR_OWNER = "0x83f426233B358A36953F6951161E76FB7c866a7A"; + const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const { deployments, network, getNamedAccounts } = hre; const { deploy, catchUnknownSigner } = deployments; const { deployer } = await getNamedAccounts(); - // Core Comptroller (diamond) + the three BNB-debt immutables. vBNB is the native market (detected as - // BNB debt), vWBNB is the ERC20 flash-borrow source (vBNB itself cannot be flash-repaid), and WBNB is - // the debt-accounting token unwrapped for the native repay. The immutable vBNB MUST match the address - // the pool-wide Liquidator gate is configured with, else the gate takes its BEP20 branch and reverts. + // Constructor immutables. vBNB is the native BNB market (a debt equal to it is settled in native BNB); + // vWBNB is the ERC20 flash-borrow source for BNB debt (vBNB itself cannot be flash-repaid); WBNB is the + // debt-accounting token unwrapped for the native repay. The immutable vBNB MUST match the address the + // pool-wide Liquidator gate is configured with, else the gate takes its BEP20 branch and reverts. const comptrollerAddress = (await deployments.get("Unitroller")).address; const vBnbAddress = (await deployments.get("vBNB")).address; const vWbnbAddress = (await deployments.get("vWBNB")).address; const wBnbAddress = (await deployments.get("WBNB")).address; - const timelockAddress = (await deployments.get("NormalTimelock")).address; - const owner = network.name === "hardhat" ? deployer : timelockAddress; + // Two distinct authorities: + // - proxy admin (UPGRADE rights) -> Venus governance timelock, changed via a VIP. + // - Ownable owner (OPERATIONAL admin) -> the bStock owner Safe, set directly in `initialize`. + // On hardhat both collapse to the deployer so tests can drive them. + const timelockAddress = (await deployments.get("NormalTimelock")).address; + const proxyAdmin = network.name === "hardhat" ? deployer : timelockAddress; + const contractOwner = network.name === "hardhat" ? deployer : BSTOCK_LIQUIDATOR_OWNER; await catchUnknownSigner( deploy("BStockLiquidator", { @@ -26,20 +35,42 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { log: true, autoMine: true, proxy: { - owner, + owner: proxyAdmin, proxyContract: "OpenZeppelinTransparentProxy", execute: { methodName: "initialize", - args: [owner], + args: [contractOwner], }, }, }), ); + + // Verify the implementation on the block explorer (BscScan). The proxy is a standard OZ transparent + // proxy the explorer recognizes; only the implementation carries the constructor args. Non-fatal — a + // failure (e.g. already verified, or no API key) is logged, not thrown, so it never blocks the deploy. + if (network.live) { + const impl = await deployments.get("BStockLiquidator_Implementation"); + try { + await hre.run("verify:verify", { + address: impl.address, + constructorArguments: [comptrollerAddress, vBnbAddress, vWbnbAddress, wBnbAddress], + }); + } catch (err) { + console.warn(`BscScan verify skipped/failed for ${impl.address}: ${(err as Error).message}`); + } + } + + // POST-DEPLOY (executed by the owner Safe, since it owns the contract from t0): + // 1. setRouter(nativeRfqRouter, true) — hop-1 Native RFQ router (bStock -> USDT) + // 2. setRouter(ammRouter, true) — hop-2 AMM/aggregator router(s) for non-USDT / BNB debt + // 3. setOperator(operatorKey, true) — each liquidation-bot key (or each Safe signer) + // These are onlyOwner, so they cannot run here (deployer is not the owner); ship them as a Safe batch. + + return hre.network.live; // record as executed on a live network to prevent re-execution }; -func.tags = ["bstock-liquidator"]; -// bStock (RFQ-only via Native) and the vWBNB flash source are bscmainnet-only; wire the vBNB/vWBNB/WBNB -// deployments before enabling this on any other network. +func.id = "bstock_liquidator_deploy"; // id required to prevent re-execution +func.tags = ["bstock-liquidator", "BStockLiquidator"]; func.skip = async (hre: HardhatRuntimeEnvironment) => hre.network.name !== "bscmainnet"; export default func; diff --git a/deployments/bscmainnet/.migrations.json b/deployments/bscmainnet/.migrations.json index 522d782a7..00a282445 100644 --- a/deployments/bscmainnet/.migrations.json +++ b/deployments/bscmainnet/.migrations.json @@ -4,5 +4,6 @@ "xvs_vault_configuration": 1697032901, "configure_prime": 1697032901, "configure_xvs_vaults": 1697032901, - "vai_controller_config": 40555052 + "vai_controller_config": 40555052, + "bstock_liquidator_deploy": 1783076577 } diff --git a/deployments/bscmainnet/BStockLiquidator.json b/deployments/bscmainnet/BStockLiquidator.json new file mode 100644 index 000000000..a7077318a --- /dev/null +++ b/deployments/bscmainnet/BStockLiquidator.json @@ -0,0 +1,991 @@ +{ + "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "changeAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "initiator", + "type": "address" + } + ], + "name": "BadInitiator", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nowTs", + "type": "uint256" + } + ], + "name": "DeadlineExpired", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "got", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minOut", + "type": "uint256" + } + ], + "name": "InsufficientOut", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidIntermediate", + "type": "error" + }, + { + "inputs": [], + "name": "NativeTransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "NotOperator", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyComptroller", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errCode", + "type": "uint256" + } + ], + "name": "RedeemFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "RouterNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "SwapFailed", + "type": "error" + }, + { + "inputs": [], + "name": "WrongFlashAsset", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroMinOut", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vBStock", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vDebt", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "seizedBStock", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "debtOut", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "flash", + "type": "bool" + } + ], + "name": "Liquidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "OperatorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "RouterSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Swept", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SweptNative", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "comptroller", + "outputs": [ + { + "internalType": "contract IComptroller", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "premiums", + "type": "uint256[]" + }, + { + "internalType": "address", + "name": "initiator", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "bytes", + "name": "param", + "type": "bytes" + } + ], + "name": "executeOperation", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "uint256[]", + "name": "repayAmounts", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vDebt", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vBStock", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "minOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router2", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata2", + "type": "bytes" + }, + { + "internalType": "address", + "name": "intermediateToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct IBStockLiquidator.LiquidationParams", + "name": "params", + "type": "tuple" + } + ], + "name": "flashLiquidate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "initialOwner", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isRouter", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vDebt", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vBStock", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "minOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router2", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata2", + "type": "bytes" + }, + { + "internalType": "address", + "name": "intermediateToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct IBStockLiquidator.LiquidationParams", + "name": "params", + "type": "tuple" + } + ], + "name": "liquidate", + "outputs": [ + { + "internalType": "uint256", + "name": "debtOut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "setOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "setRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "sweep", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "sweepNative", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vBNB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vWBNB", + "outputs": [ + { + "internalType": "contract IVToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "wbnb", + "outputs": [ + { + "internalType": "contract IWBNB", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + } + ], + "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "receipt": { + "to": null, + "from": "0x8D43F1776B4d5eB69cDbE2e79899520754a0cd9A", + "contractAddress": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "transactionIndex": 41, + "gasUsed": "797587", + "logsBloom": "0x000000040000000000000000000000004020000000000000008000000000000000000000000000000000000000000800200000000000000000000000000000000000000000000000000000000000020000010000000000000000000000000000000000000200000000000010000008000000008000000000000000801000004000000000000080000000000000000000000000000000c0000000000000800000000000000000010000000000000400000000000000400000000000000000000000000220000000000000000000040000000000000400000000000000000020000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db", + "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "logs": [ + { + "transactionIndex": 41, + "blockNumber": 107817335, + "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x000000000000000000000000883c36bade4babe393fef2fcfcfc24a5e8e1a3d5" + ], + "data": "0x", + "logIndex": 131, + "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + }, + { + "transactionIndex": 41, + "blockNumber": 107817335, + "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000008d43f1776b4d5eb69cdbe2e79899520754a0cd9a" + ], + "data": "0x", + "logIndex": 132, + "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + }, + { + "transactionIndex": 41, + "blockNumber": 107817335, + "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000008d43f1776b4d5eb69cdbe2e79899520754a0cd9a", + "0x00000000000000000000000083f426233b358a36953f6951161e76fb7c866a7a" + ], + "data": "0x", + "logIndex": 133, + "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + }, + { + "transactionIndex": 41, + "blockNumber": 107817335, + "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logIndex": 134, + "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + }, + { + "transactionIndex": 41, + "blockNumber": 107817335, + "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "topics": ["0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f"], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001bb765b741a5f3c2a338369dab539385534e3343", + "logIndex": 135, + "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + } + ], + "blockNumber": 107817335, + "cumulativeGasUsed": "5080085", + "status": 1, + "byzantium": true + }, + "args": [ + "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", + "0x1BB765b741A5f3C2A338369DAb539385534E3343", + "0xc4d66de800000000000000000000000083f426233b358a36953f6951161e76fb7c866a7a" + ], + "numDeployments": 1, + "solcInputHash": "0e89febeebc7444140de8e67c9067d2c", + "metadata": "{\"compiler\":{\"version\":\"0.8.10+commit.fc410830\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_logic\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"admin_\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"previousAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"AdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"beacon\",\"type\":\"address\"}],\"name\":\"BeaconUpgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"admin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"admin_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"changeAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"implementation\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"implementation_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"}],\"name\":\"upgradeTo\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"details\":\"This contract implements a proxy that is upgradeable by an admin. To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector clashing], which can potentially be used in an attack, this contract uses the https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two things that go hand in hand: 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if that call matches one of the admin functions exposed by the proxy itself. 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the implementation. If the admin tries to call a function on the implementation it will fail with an error that says \\\"admin cannot fallback to proxy target\\\". These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to call a function from the proxy implementation. Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.\",\"kind\":\"dev\",\"methods\":{\"admin()\":{\"details\":\"Returns the current admin. NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}. TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\"},\"changeAdmin(address)\":{\"details\":\"Changes the admin of the proxy. Emits an {AdminChanged} event. NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.\"},\"constructor\":{\"details\":\"Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\"},\"implementation()\":{\"details\":\"Returns the current implementation. NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}. TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`\"},\"upgradeTo(address)\":{\"details\":\"Upgrade the implementation of the proxy. NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\"},\"upgradeToAndCall(address,bytes)\":{\"details\":\"Upgrade the implementation of the proxy, and then call a function from the new implementation as specified by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the proxied contract. NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"solc_0.8/openzeppelin/proxy/transparent/TransparentUpgradeableProxy.sol\":\"TransparentUpgradeableProxy\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"solc_0.8/openzeppelin/interfaces/draft-IERC1822.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (interfaces/draft-IERC1822.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified\\n * proxy whose upgrades are fully controlled by the current implementation.\\n */\\ninterface IERC1822Proxiable {\\n /**\\n * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation\\n * address.\\n *\\n * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks\\n * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this\\n * function revert if invoked through a proxy.\\n */\\n function proxiableUUID() external view returns (bytes32);\\n}\\n\",\"keccak256\":\"0x93b4e21c931252739a1ec13ea31d3d35a5c068be3163ccab83e4d70c40355f03\",\"license\":\"MIT\"},\"solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Proxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Proxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../Proxy.sol\\\";\\nimport \\\"./ERC1967Upgrade.sol\\\";\\n\\n/**\\n * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an\\n * implementation address that can be changed. This address is stored in storage in the location specified by\\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the\\n * implementation behind the proxy.\\n */\\ncontract ERC1967Proxy is Proxy, ERC1967Upgrade {\\n /**\\n * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.\\n *\\n * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded\\n * function call, and allows initializating the storage of the proxy like a Solidity constructor.\\n */\\n constructor(address _logic, bytes memory _data) payable {\\n assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256(\\\"eip1967.proxy.implementation\\\")) - 1));\\n _upgradeToAndCall(_logic, _data, false);\\n }\\n\\n /**\\n * @dev Returns the current implementation address.\\n */\\n function _implementation() internal view virtual override returns (address impl) {\\n return ERC1967Upgrade._getImplementation();\\n }\\n}\\n\",\"keccak256\":\"0x6309f9f39dc6f4f45a24f296543867aa358e32946cd6b2874627a996d606b3a0\",\"license\":\"MIT\"},\"solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Upgrade.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/ERC1967/ERC1967Upgrade.sol)\\n\\npragma solidity ^0.8.2;\\n\\nimport \\\"../beacon/IBeacon.sol\\\";\\nimport \\\"../../interfaces/draft-IERC1822.sol\\\";\\nimport \\\"../../utils/Address.sol\\\";\\nimport \\\"../../utils/StorageSlot.sol\\\";\\n\\n/**\\n * @dev This abstract contract provides getters and event emitting update functions for\\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.\\n *\\n * _Available since v4.1._\\n *\\n * @custom:oz-upgrades-unsafe-allow delegatecall\\n */\\nabstract contract ERC1967Upgrade {\\n // This is the keccak-256 hash of \\\"eip1967.proxy.rollback\\\" subtracted by 1\\n bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;\\n\\n /**\\n * @dev Storage slot with the address of the current implementation.\\n * This is the keccak-256 hash of \\\"eip1967.proxy.implementation\\\" subtracted by 1, and is\\n * validated in the constructor.\\n */\\n bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n\\n /**\\n * @dev Emitted when the implementation is upgraded.\\n */\\n event Upgraded(address indexed implementation);\\n\\n /**\\n * @dev Returns the current implementation address.\\n */\\n function _getImplementation() internal view returns (address) {\\n return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new address in the EIP1967 implementation slot.\\n */\\n function _setImplementation(address newImplementation) private {\\n require(Address.isContract(newImplementation), \\\"ERC1967: new implementation is not a contract\\\");\\n StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\\n }\\n\\n /**\\n * @dev Perform implementation upgrade\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeTo(address newImplementation) internal {\\n _setImplementation(newImplementation);\\n emit Upgraded(newImplementation);\\n }\\n\\n /**\\n * @dev Perform implementation upgrade with additional setup call.\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeToAndCall(\\n address newImplementation,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n _upgradeTo(newImplementation);\\n if (data.length > 0 || forceCall) {\\n Address.functionDelegateCall(newImplementation, data);\\n }\\n }\\n\\n /**\\n * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeToAndCallUUPS(\\n address newImplementation,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n // Upgrades from old implementations will perform a rollback test. This test requires the new\\n // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing\\n // this special case will break upgrade paths from old UUPS implementation to new ones.\\n if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {\\n _setImplementation(newImplementation);\\n } else {\\n try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {\\n require(slot == _IMPLEMENTATION_SLOT, \\\"ERC1967Upgrade: unsupported proxiableUUID\\\");\\n } catch {\\n revert(\\\"ERC1967Upgrade: new implementation is not UUPS\\\");\\n }\\n _upgradeToAndCall(newImplementation, data, forceCall);\\n }\\n }\\n\\n /**\\n * @dev Storage slot with the admin of the contract.\\n * This is the keccak-256 hash of \\\"eip1967.proxy.admin\\\" subtracted by 1, and is\\n * validated in the constructor.\\n */\\n bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\\n\\n /**\\n * @dev Emitted when the admin account has changed.\\n */\\n event AdminChanged(address previousAdmin, address newAdmin);\\n\\n /**\\n * @dev Returns the current admin.\\n */\\n function _getAdmin() internal view virtual returns (address) {\\n return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new address in the EIP1967 admin slot.\\n */\\n function _setAdmin(address newAdmin) private {\\n require(newAdmin != address(0), \\\"ERC1967: new admin is the zero address\\\");\\n StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;\\n }\\n\\n /**\\n * @dev Changes the admin of the proxy.\\n *\\n * Emits an {AdminChanged} event.\\n */\\n function _changeAdmin(address newAdmin) internal {\\n emit AdminChanged(_getAdmin(), newAdmin);\\n _setAdmin(newAdmin);\\n }\\n\\n /**\\n * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.\\n * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.\\n */\\n bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;\\n\\n /**\\n * @dev Emitted when the beacon is upgraded.\\n */\\n event BeaconUpgraded(address indexed beacon);\\n\\n /**\\n * @dev Returns the current beacon.\\n */\\n function _getBeacon() internal view returns (address) {\\n return StorageSlot.getAddressSlot(_BEACON_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new beacon in the EIP1967 beacon slot.\\n */\\n function _setBeacon(address newBeacon) private {\\n require(Address.isContract(newBeacon), \\\"ERC1967: new beacon is not a contract\\\");\\n require(Address.isContract(IBeacon(newBeacon).implementation()), \\\"ERC1967: beacon implementation is not a contract\\\");\\n StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;\\n }\\n\\n /**\\n * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does\\n * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).\\n *\\n * Emits a {BeaconUpgraded} event.\\n */\\n function _upgradeBeaconToAndCall(\\n address newBeacon,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n _setBeacon(newBeacon);\\n emit BeaconUpgraded(newBeacon);\\n if (data.length > 0 || forceCall) {\\n Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);\\n }\\n }\\n}\\n\",\"keccak256\":\"0x17668652127feebed0ce8d9431ef95ccc8c4292f03e3b8cf06c6ca16af396633\",\"license\":\"MIT\"},\"solc_0.8/openzeppelin/proxy/Proxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/Proxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM\\n * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to\\n * be specified by overriding the virtual {_implementation} function.\\n *\\n * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a\\n * different contract through the {_delegate} function.\\n *\\n * The success and return data of the delegated call will be returned back to the caller of the proxy.\\n */\\nabstract contract Proxy {\\n /**\\n * @dev Delegates the current call to `implementation`.\\n *\\n * This function does not return to its internal call site, it will return directly to the external caller.\\n */\\n function _delegate(address implementation) internal virtual {\\n assembly {\\n // Copy msg.data. We take full control of memory in this inline assembly\\n // block because it will not return to Solidity code. We overwrite the\\n // Solidity scratch pad at memory position 0.\\n calldatacopy(0, 0, calldatasize())\\n\\n // Call the implementation.\\n // out and outsize are 0 because we don't know the size yet.\\n let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)\\n\\n // Copy the returned data.\\n returndatacopy(0, 0, returndatasize())\\n\\n switch result\\n // delegatecall returns 0 on error.\\n case 0 {\\n revert(0, returndatasize())\\n }\\n default {\\n return(0, returndatasize())\\n }\\n }\\n }\\n\\n /**\\n * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function\\n * and {_fallback} should delegate.\\n */\\n function _implementation() internal view virtual returns (address);\\n\\n /**\\n * @dev Delegates the current call to the address returned by `_implementation()`.\\n *\\n * This function does not return to its internall call site, it will return directly to the external caller.\\n */\\n function _fallback() internal virtual {\\n _beforeFallback();\\n _delegate(_implementation());\\n }\\n\\n /**\\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other\\n * function in the contract matches the call data.\\n */\\n fallback() external payable virtual {\\n _fallback();\\n }\\n\\n /**\\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data\\n * is empty.\\n */\\n receive() external payable virtual {\\n _fallback();\\n }\\n\\n /**\\n * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`\\n * call, or as part of the Solidity `fallback` or `receive` functions.\\n *\\n * If overriden should call `super._beforeFallback()`.\\n */\\n function _beforeFallback() internal virtual {}\\n}\\n\",\"keccak256\":\"0xd5d1fd16e9faff7fcb3a52e02a8d49156f42a38a03f07b5f1810c21c2149a8ab\",\"license\":\"MIT\"},\"solc_0.8/openzeppelin/proxy/beacon/IBeacon.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev This is the interface that {BeaconProxy} expects of its beacon.\\n */\\ninterface IBeacon {\\n /**\\n * @dev Must return an address that can be used as a delegate call target.\\n *\\n * {BeaconProxy} will check that this address is a contract.\\n */\\n function implementation() external view returns (address);\\n}\\n\",\"keccak256\":\"0xd50a3421ac379ccb1be435fa646d66a65c986b4924f0849839f08692f39dde61\",\"license\":\"MIT\"},\"solc_0.8/openzeppelin/proxy/transparent/TransparentUpgradeableProxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/TransparentUpgradeableProxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../ERC1967/ERC1967Proxy.sol\\\";\\n\\n/**\\n * @dev This contract implements a proxy that is upgradeable by an admin.\\n *\\n * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector\\n * clashing], which can potentially be used in an attack, this contract uses the\\n * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two\\n * things that go hand in hand:\\n *\\n * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if\\n * that call matches one of the admin functions exposed by the proxy itself.\\n * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the\\n * implementation. If the admin tries to call a function on the implementation it will fail with an error that says\\n * \\\"admin cannot fallback to proxy target\\\".\\n *\\n * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing\\n * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due\\n * to sudden errors when trying to call a function from the proxy implementation.\\n *\\n * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,\\n * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.\\n */\\ncontract TransparentUpgradeableProxy is ERC1967Proxy {\\n /**\\n * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and\\n * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\\n */\\n constructor(\\n address _logic,\\n address admin_,\\n bytes memory _data\\n ) payable ERC1967Proxy(_logic, _data) {\\n assert(_ADMIN_SLOT == bytes32(uint256(keccak256(\\\"eip1967.proxy.admin\\\")) - 1));\\n _changeAdmin(admin_);\\n }\\n\\n /**\\n * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.\\n */\\n modifier ifAdmin() {\\n if (msg.sender == _getAdmin()) {\\n _;\\n } else {\\n _fallback();\\n }\\n }\\n\\n /**\\n * @dev Returns the current admin.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.\\n *\\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\\n * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\\n */\\n function admin() external ifAdmin returns (address admin_) {\\n admin_ = _getAdmin();\\n }\\n\\n /**\\n * @dev Returns the current implementation.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.\\n *\\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\\n * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`\\n */\\n function implementation() external ifAdmin returns (address implementation_) {\\n implementation_ = _implementation();\\n }\\n\\n /**\\n * @dev Changes the admin of the proxy.\\n *\\n * Emits an {AdminChanged} event.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.\\n */\\n function changeAdmin(address newAdmin) external virtual ifAdmin {\\n _changeAdmin(newAdmin);\\n }\\n\\n /**\\n * @dev Upgrade the implementation of the proxy.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\\n */\\n function upgradeTo(address newImplementation) external ifAdmin {\\n _upgradeToAndCall(newImplementation, bytes(\\\"\\\"), false);\\n }\\n\\n /**\\n * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified\\n * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the\\n * proxied contract.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.\\n */\\n function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {\\n _upgradeToAndCall(newImplementation, data, true);\\n }\\n\\n /**\\n * @dev Returns the current admin.\\n */\\n function _admin() internal view virtual returns (address) {\\n return _getAdmin();\\n }\\n\\n /**\\n * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.\\n */\\n function _beforeFallback() internal virtual override {\\n require(msg.sender != _getAdmin(), \\\"TransparentUpgradeableProxy: admin cannot fallback to proxy target\\\");\\n super._beforeFallback();\\n }\\n}\\n\",\"keccak256\":\"0x140055a64cf579d622e04f5a198595832bf2cb193cd0005f4f2d4d61ca906253\",\"license\":\"MIT\"},\"solc_0.8/openzeppelin/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x3777e696b62134e6177440dbe6e6601c0c156a443f57167194b67e75527439de\",\"license\":\"MIT\"},\"solc_0.8/openzeppelin/utils/StorageSlot.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Library for reading and writing primitive types to specific storage slots.\\n *\\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\\n * This library helps with reading and writing to such slots without the need for inline assembly.\\n *\\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\\n *\\n * Example usage to set ERC1967 implementation slot:\\n * ```\\n * contract ERC1967 {\\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n *\\n * function _getImplementation() internal view returns (address) {\\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\\n * }\\n *\\n * function _setImplementation(address newImplementation) internal {\\n * require(Address.isContract(newImplementation), \\\"ERC1967: new implementation is not a contract\\\");\\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\\n * }\\n * }\\n * ```\\n *\\n * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._\\n */\\nlibrary StorageSlot {\\n struct AddressSlot {\\n address value;\\n }\\n\\n struct BooleanSlot {\\n bool value;\\n }\\n\\n struct Bytes32Slot {\\n bytes32 value;\\n }\\n\\n struct Uint256Slot {\\n uint256 value;\\n }\\n\\n /**\\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\\n */\\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `BooleanSlot` with member `value` located at `slot`.\\n */\\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.\\n */\\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `Uint256Slot` with member `value` located at `slot`.\\n */\\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n}\\n\",\"keccak256\":\"0xfe1b7a9aa2a530a9e705b220e26cd584e2fbdc9602a3a1066032b12816b46aca\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x6080604052604051620011b2380380620011b2833981016040819052620000269162000519565b82816200005560017f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbd620005f9565b6000805160206200116b833981519152146200007557620000756200061f565b6200008382826000620000e7565b50620000b3905060017fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6104620005f9565b6000805160206200114b83398151915214620000d357620000d36200061f565b620000de8262000124565b50505062000688565b620000f2836200017f565b600082511180620001005750805b156200011f576200011d8383620001c160201b620002ff1760201c565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6200014f620001f0565b604080516001600160a01b03928316815291841660208301520160405180910390a16200017c8162000229565b50565b6200018a81620002de565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6060620001e983836040518060600160405280602781526020016200118b6027913962000381565b9392505050565b60006200021a6000805160206200114b83398151915260001b6200046760201b620002731760201c565b546001600160a01b0316919050565b6001600160a01b038116620002945760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b80620002bd6000805160206200114b83398151915260001b6200046760201b620002731760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b620002f4816200046a60201b6200032b1760201c565b620003585760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016200028b565b80620002bd6000805160206200116b83398151915260001b6200046760201b620002731760201c565b60606001600160a01b0384163b620003eb5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b60648201526084016200028b565b600080856001600160a01b03168560405162000408919062000635565b600060405180830381855af49150503d806000811462000445576040519150601f19603f3d011682016040523d82523d6000602084013e6200044a565b606091505b5090925090506200045d82828662000479565b9695505050505050565b90565b6001600160a01b03163b151590565b606083156200048a575081620001e9565b8251156200049b5782518084602001fd5b8160405162461bcd60e51b81526004016200028b919062000653565b80516001600160a01b0381168114620004cf57600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101562000507578181015183820152602001620004ed565b838111156200011d5750506000910152565b6000806000606084860312156200052f57600080fd5b6200053a84620004b7565b92506200054a60208501620004b7565b60408501519092506001600160401b03808211156200056857600080fd5b818601915086601f8301126200057d57600080fd5b815181811115620005925762000592620004d4565b604051601f8201601f19908116603f01168101908382118183101715620005bd57620005bd620004d4565b81604052828152896020848701011115620005d757600080fd5b620005ea836020830160208801620004ea565b80955050505050509250925092565b6000828210156200061a57634e487b7160e01b600052601160045260246000fd5b500390565b634e487b7160e01b600052600160045260246000fd5b6000825162000649818460208701620004ea565b9190910192915050565b602081526000825180602084015262000674816040850160208701620004ea565b601f01601f19169190910160400192915050565b610ab380620006986000396000f3fe60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100a85780638f283970146100e6578063f851a440146101065761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61011b565b005b61006b61011b565b34801561008157600080fd5b5061006b61009036600461091f565b610135565b61006b6100a336600461093a565b610196565b3480156100b457600080fd5b506100bd610221565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100f257600080fd5b5061006b61010136600461091f565b610276565b34801561011257600080fd5b506100bd6102ba565b610123610347565b61013361012e610435565b61043f565b565b61013d610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561018e5761018b816040518060200160405280600081525060006104a3565b50565b61018b61011b565b61019e610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610219576102148383838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250600192506104a3915050565b505050565b61021461011b565b600061022b610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561026b57610266610435565b905090565b61027361011b565b90565b61027e610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561018e5761018b816104ce565b60006102c4610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561026b57610266610463565b60606103248383604051806060016040528060278152602001610a576027913961052f565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b61034f610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610133576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f6574000000000000000000000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b6000610266610657565b3660008037600080366000845af43d6000803e80801561045e573d6000f35b3d6000fd5b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b5473ffffffffffffffffffffffffffffffffffffffff16919050565b6104ac8361067f565b6000825111806104b95750805b15610214576104c883836102ff565b50505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6104f7610463565b6040805173ffffffffffffffffffffffffffffffffffffffff928316815291841660208301520160405180910390a161018b816106cc565b606073ffffffffffffffffffffffffffffffffffffffff84163b6105d5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f60448201527f6e74726163740000000000000000000000000000000000000000000000000000606482015260840161042c565b6000808573ffffffffffffffffffffffffffffffffffffffff16856040516105fd91906109e9565b600060405180830381855af49150503d8060008114610638576040519150601f19603f3d011682016040523d82523d6000602084013e61063d565b606091505b509150915061064d8282866107d8565b9695505050505050565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610487565b6106888161082b565b60405173ffffffffffffffffffffffffffffffffffffffff8216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b73ffffffffffffffffffffffffffffffffffffffff811661076f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f6464726573730000000000000000000000000000000000000000000000000000606482015260840161042c565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9290921691909117905550565b606083156107e7575081610324565b8251156107f75782518084602001fd5b816040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161042c9190610a05565b73ffffffffffffffffffffffffffffffffffffffff81163b6108cf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e747261637400000000000000000000000000000000000000606482015260840161042c565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610792565b803573ffffffffffffffffffffffffffffffffffffffff8116811461091a57600080fd5b919050565b60006020828403121561093157600080fd5b610324826108f6565b60008060006040848603121561094f57600080fd5b610958846108f6565b9250602084013567ffffffffffffffff8082111561097557600080fd5b818601915086601f83011261098957600080fd5b81358181111561099857600080fd5b8760208285010111156109aa57600080fd5b6020830194508093505050509250925092565b60005b838110156109d85781810151838201526020016109c0565b838111156104c85750506000910152565b600082516109fb8184602087016109bd565b9190910192915050565b6020815260008251806020840152610a248160408501602087016109bd565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220b29caa54336b3ee836679675e9732ec5e526fb3f803cca2fe336cc3555aba62264736f6c634300080a0033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564", + "deployedBytecode": "0x60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100a85780638f283970146100e6578063f851a440146101065761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61011b565b005b61006b61011b565b34801561008157600080fd5b5061006b61009036600461091f565b610135565b61006b6100a336600461093a565b610196565b3480156100b457600080fd5b506100bd610221565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100f257600080fd5b5061006b61010136600461091f565b610276565b34801561011257600080fd5b506100bd6102ba565b610123610347565b61013361012e610435565b61043f565b565b61013d610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561018e5761018b816040518060200160405280600081525060006104a3565b50565b61018b61011b565b61019e610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610219576102148383838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250600192506104a3915050565b505050565b61021461011b565b600061022b610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561026b57610266610435565b905090565b61027361011b565b90565b61027e610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561018e5761018b816104ce565b60006102c4610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561026b57610266610463565b60606103248383604051806060016040528060278152602001610a576027913961052f565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b61034f610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610133576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f6574000000000000000000000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b6000610266610657565b3660008037600080366000845af43d6000803e80801561045e573d6000f35b3d6000fd5b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b5473ffffffffffffffffffffffffffffffffffffffff16919050565b6104ac8361067f565b6000825111806104b95750805b15610214576104c883836102ff565b50505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6104f7610463565b6040805173ffffffffffffffffffffffffffffffffffffffff928316815291841660208301520160405180910390a161018b816106cc565b606073ffffffffffffffffffffffffffffffffffffffff84163b6105d5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f60448201527f6e74726163740000000000000000000000000000000000000000000000000000606482015260840161042c565b6000808573ffffffffffffffffffffffffffffffffffffffff16856040516105fd91906109e9565b600060405180830381855af49150503d8060008114610638576040519150601f19603f3d011682016040523d82523d6000602084013e61063d565b606091505b509150915061064d8282866107d8565b9695505050505050565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610487565b6106888161082b565b60405173ffffffffffffffffffffffffffffffffffffffff8216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b73ffffffffffffffffffffffffffffffffffffffff811661076f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f6464726573730000000000000000000000000000000000000000000000000000606482015260840161042c565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9290921691909117905550565b606083156107e7575081610324565b8251156107f75782518084602001fd5b816040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161042c9190610a05565b73ffffffffffffffffffffffffffffffffffffffff81163b6108cf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e747261637400000000000000000000000000000000000000606482015260840161042c565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610792565b803573ffffffffffffffffffffffffffffffffffffffff8116811461091a57600080fd5b919050565b60006020828403121561093157600080fd5b610324826108f6565b60008060006040848603121561094f57600080fd5b610958846108f6565b9250602084013567ffffffffffffffff8082111561097557600080fd5b818601915086601f83011261098957600080fd5b81358181111561099857600080fd5b8760208285010111156109aa57600080fd5b6020830194508093505050509250925092565b60005b838110156109d85781810151838201526020016109c0565b838111156104c85750506000910152565b600082516109fb8184602087016109bd565b9190910192915050565b6020815260008251806020840152610a248160408501602087016109bd565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220b29caa54336b3ee836679675e9732ec5e526fb3f803cca2fe336cc3555aba62264736f6c634300080a0033", + "execute": { + "methodName": "initialize", + "args": ["0x83f426233B358A36953F6951161E76FB7c866a7A"] + }, + "implementation": "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", + "devdoc": { + "details": "This contract implements a proxy that is upgradeable by an admin. To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector clashing], which can potentially be used in an attack, this contract uses the https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two things that go hand in hand: 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if that call matches one of the admin functions exposed by the proxy itself. 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the implementation. If the admin tries to call a function on the implementation it will fail with an error that says \"admin cannot fallback to proxy target\". These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to call a function from the proxy implementation. Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.", + "kind": "dev", + "methods": { + "admin()": { + "details": "Returns the current admin. NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}. TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`" + }, + "changeAdmin(address)": { + "details": "Changes the admin of the proxy. Emits an {AdminChanged} event. NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}." + }, + "constructor": { + "details": "Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}." + }, + "implementation()": { + "details": "Returns the current implementation. NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}. TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`" + }, + "upgradeTo(address)": { + "details": "Upgrade the implementation of the proxy. NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}." + }, + "upgradeToAndCall(address,bytes)": { + "details": "Upgrade the implementation of the proxy, and then call a function from the new implementation as specified by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the proxied contract. NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + }, + "storageLayout": { + "storage": [], + "types": null + } +} diff --git a/deployments/bscmainnet/BStockLiquidator_Implementation.json b/deployments/bscmainnet/BStockLiquidator_Implementation.json new file mode 100644 index 000000000..442725f94 --- /dev/null +++ b/deployments/bscmainnet/BStockLiquidator_Implementation.json @@ -0,0 +1,1190 @@ +{ + "address": "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", + "abi": [ + { + "inputs": [ + { + "internalType": "contract IComptroller", + "name": "comptroller_", + "type": "address" + }, + { + "internalType": "address", + "name": "vBNB_", + "type": "address" + }, + { + "internalType": "contract IVToken", + "name": "vWBNB_", + "type": "address" + }, + { + "internalType": "contract IWBNB", + "name": "wbnb_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "initiator", + "type": "address" + } + ], + "name": "BadInitiator", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nowTs", + "type": "uint256" + } + ], + "name": "DeadlineExpired", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "got", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minOut", + "type": "uint256" + } + ], + "name": "InsufficientOut", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidIntermediate", + "type": "error" + }, + { + "inputs": [], + "name": "NativeTransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "NotOperator", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyComptroller", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errCode", + "type": "uint256" + } + ], + "name": "RedeemFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "RouterNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "SwapFailed", + "type": "error" + }, + { + "inputs": [], + "name": "WrongFlashAsset", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroMinOut", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vBStock", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vDebt", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "seizedBStock", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "debtOut", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "flash", + "type": "bool" + } + ], + "name": "Liquidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "OperatorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "RouterSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Swept", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SweptNative", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "comptroller", + "outputs": [ + { + "internalType": "contract IComptroller", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "premiums", + "type": "uint256[]" + }, + { + "internalType": "address", + "name": "initiator", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "bytes", + "name": "param", + "type": "bytes" + } + ], + "name": "executeOperation", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "uint256[]", + "name": "repayAmounts", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vDebt", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vBStock", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "minOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router2", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata2", + "type": "bytes" + }, + { + "internalType": "address", + "name": "intermediateToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct IBStockLiquidator.LiquidationParams", + "name": "params", + "type": "tuple" + } + ], + "name": "flashLiquidate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "initialOwner", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isRouter", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vDebt", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vBStock", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "minOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router2", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata2", + "type": "bytes" + }, + { + "internalType": "address", + "name": "intermediateToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct IBStockLiquidator.LiquidationParams", + "name": "params", + "type": "tuple" + } + ], + "name": "liquidate", + "outputs": [ + { + "internalType": "uint256", + "name": "debtOut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "setOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "setRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "sweep", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "sweepNative", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vBNB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vWBNB", + "outputs": [ + { + "internalType": "contract IVToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "wbnb", + "outputs": [ + { + "internalType": "contract IWBNB", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "transactionHash": "0xd47c6d5d424453aacc3a414e9b25efc624926c2d58a60896a0c92a6549a14b6d", + "receipt": { + "to": null, + "from": "0x8D43F1776B4d5eB69cDbE2e79899520754a0cd9A", + "contractAddress": "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", + "transactionIndex": 75, + "gasUsed": "2362520", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000800000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xbfd34df4c415f62f4e3c0c58c502e5b35dc26b191928c7288de6cbc68cb31349", + "transactionHash": "0xd47c6d5d424453aacc3a414e9b25efc624926c2d58a60896a0c92a6549a14b6d", + "logs": [ + { + "transactionIndex": 75, + "blockNumber": 107817331, + "transactionHash": "0xd47c6d5d424453aacc3a414e9b25efc624926c2d58a60896a0c92a6549a14b6d", + "address": "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", + "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], + "data": "0x00000000000000000000000000000000000000000000000000000000000000ff", + "logIndex": 208, + "blockHash": "0xbfd34df4c415f62f4e3c0c58c502e5b35dc26b191928c7288de6cbc68cb31349" + } + ], + "blockNumber": 107817331, + "cumulativeGasUsed": "10381817", + "status": 1, + "byzantium": true + }, + "args": [ + "0xfD36E2c2a6789Db23113685031d7F16329158384", + "0xA07c5b74C9B40447a954e1466938b865b6BBea36", + "0x6bCa74586218dB34cdB402295796b79663d816e9", + "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" + ], + "numDeployments": 1, + "solcInputHash": "b16b9fd9d739d9f7b43142447745f751", + "metadata": "{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"contract IComptroller\",\"name\":\"comptroller_\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"vBNB_\",\"type\":\"address\"},{\"internalType\":\"contract IVToken\",\"name\":\"vWBNB_\",\"type\":\"address\"},{\"internalType\":\"contract IWBNB\",\"name\":\"wbnb_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"}],\"name\":\"BadInitiator\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nowTs\",\"type\":\"uint256\"}],\"name\":\"DeadlineExpired\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"got\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minOut\",\"type\":\"uint256\"}],\"name\":\"InsufficientOut\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidIntermediate\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NativeTransferFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotOperator\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyComptroller\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"errCode\",\"type\":\"uint256\"}],\"name\":\"RedeemFailed\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"}],\"name\":\"RouterNotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SwapFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WrongFlashAsset\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddressNotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroMinOut\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"borrower\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"vBStock\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"vDebt\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"repayAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"seizedBStock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"debtOut\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"flash\",\"type\":\"bool\"}],\"name\":\"Liquidated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"OperatorSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"RouterSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Swept\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"SweptNative\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"comptroller\",\"outputs\":[{\"internalType\":\"contract IComptroller\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract VToken[]\",\"name\":\"vTokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"amounts\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"premiums\",\"type\":\"uint256[]\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"param\",\"type\":\"bytes\"}],\"name\":\"executeOperation\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"uint256[]\",\"name\":\"repayAmounts\",\"type\":\"uint256[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"borrower\",\"type\":\"address\"},{\"internalType\":\"contract IVBep20\",\"name\":\"vDebt\",\"type\":\"address\"},{\"internalType\":\"contract IVBep20\",\"name\":\"vBStock\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"repayAmount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"swapCalldata\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"minOut\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"router2\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"swapCalldata2\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"intermediateToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"internalType\":\"struct IBStockLiquidator.LiquidationParams\",\"name\":\"params\",\"type\":\"tuple\"}],\"name\":\"flashLiquidate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"initialOwner\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isOperator\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isRouter\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"borrower\",\"type\":\"address\"},{\"internalType\":\"contract IVBep20\",\"name\":\"vDebt\",\"type\":\"address\"},{\"internalType\":\"contract IVBep20\",\"name\":\"vBStock\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"repayAmount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"swapCalldata\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"minOut\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"router2\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"swapCalldata2\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"intermediateToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"internalType\":\"struct IBStockLiquidator.LiquidationParams\",\"name\":\"params\",\"type\":\"tuple\"}],\"name\":\"liquidate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"debtOut\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"setOperator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"setRouter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"sweep\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"sweepNative\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vBNB\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vWBNB\",\"outputs\":[{\"internalType\":\"contract IVToken\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"wbnb\",\"outputs\":[{\"internalType\":\"contract IWBNB\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"author\":\"Venus\",\"events\":{\"Initialized(uint8)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"},\"Liquidated(address,address,address,uint256,uint256,uint256,bool)\":{\"params\":{\"borrower\":\"The liquidated account.\",\"debtOut\":\"Debt-asset proceeds of the swap.\",\"flash\":\"True if funded by a flash loan, false if from inventory.\",\"repayAmount\":\"Debt underlying repaid.\",\"seizedBStock\":\"Raw bStock redeemed and sold.\",\"vBStock\":\"The seized bStock collateral market.\",\"vDebt\":\"The repaid debt market.\"}}},\"kind\":\"dev\",\"methods\":{\"acceptOwnership()\":{\"details\":\"The new owner accepts the ownership transfer.\"},\"constructor\":{\"custom:oz-upgrades-unsafe-allow\":\"constructor\",\"params\":{\"comptroller_\":\"Venus Core Comptroller (diamond) \\u2014 gates liquidation and provides flash loans.\",\"vBNB_\":\"Native BNB market; a debt equal to this address is settled in native BNB.\",\"vWBNB_\":\"WBNB market; the flash-borrow source for BNB debt (vBNB itself cannot be flash-repaid).\",\"wbnb_\":\"WBNB token; the debt-accounting asset for BNB debt, unwrapped for the native repay.\"}},\"executeOperation(address[],uint256[],uint256[],address,address,bytes)\":{\"details\":\"Implementation of this function must ensure at least the premium (fee) is repaid within the same transaction. Any unpaid balance (principal + premium - repaid amount) will be added to the onBehalf address's borrow balance.\",\"params\":{\"amounts\":\"The amounts of each underlying asset that were flash-borrowed.\",\"initiator\":\"The address that initiated the flash loan.\",\"onBehalf\":\"The address of the user whose debt position will be used for any unpaid flash loan balance.\",\"param\":\"Additional parameters encoded as bytes. These can be used to pass custom data to the receiver contract.\",\"premiums\":\"The premiums (fees) associated with each flash-borrowed asset.\",\"vTokens\":\"The vToken contracts corresponding to the flash-borrowed underlying assets.\"},\"returns\":{\"_0\":\"True if the operation succeeds (regardless of repayment amount), false if the operation fails.\",\"repayAmounts\":\"Array of uint256 representing the amounts to be repaid for each asset. The receiver contract must approve these amounts to the respective vToken contracts before this function returns.\"}},\"flashLiquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))\":{\"details\":\"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`.\",\"params\":{\"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).\"}},\"initialize(address)\":{\"params\":{\"initialOwner\":\"Address that owns the contract (admin + default operator).\"}},\"liquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))\":{\"details\":\"INVENTORY mode spends the contract's OWN debt-asset capital, so \\u2014 unlike FLASH mode, where `executeOperation` forces the swap proceeds to cover principal + premium \\u2014 there is no built-in floor tying `debtOut` to `repayAmount`. This asymmetry is intentional: a repay can legitimately out-cost its proceeds (e.g. the Venus Liquidator keeps a treasury cut of the seized collateral, so proceeds land a few % under the repay). `minOut` IS the operator's chosen loss floor for inventory mode \\u2014 set it to the lowest acceptable debt-asset return.\",\"params\":{\"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).\"},\"returns\":{\"debtOut\":\"Debt-asset proceeds realized by the swap chain.\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"pendingOwner()\":{\"details\":\"Returns the address of the pending owner.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"setOperator(address,bool)\":{\"params\":{\"allowed\":\"True to allow, false to remove.\",\"operator\":\"Address to allowlist or remove.\"}},\"setRouter(address,bool)\":{\"params\":{\"allowed\":\"True to allow, false to remove.\",\"router\":\"Address to allowlist or remove.\"}},\"sweep(address,address,uint256)\":{\"params\":{\"amount\":\"Amount to withdraw.\",\"to\":\"Recipient.\",\"token\":\"Token to withdraw.\"}},\"sweepNative(address,uint256)\":{\"params\":{\"amount\":\"Amount of native BNB to withdraw.\",\"to\":\"Recipient.\"}},\"transferOwnership(address)\":{\"details\":\"Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. Can only be called by the current owner.\"}},\"stateVariables\":{\"__gap\":{\"details\":\"Reserved storage to allow new state variables in future upgrades without layout clashes.\"},\"comptroller\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"},\"vBNB\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"},\"vWBNB\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"},\"wbnb\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"}},\"title\":\"BStockLiquidator\",\"version\":1},\"userdoc\":{\"errors\":{\"BadInitiator(address)\":[{\"notice\":\"Thrown when the flash-loan initiator is not this contract.\"}],\"DeadlineExpired(uint256,uint256)\":[{\"notice\":\"Thrown when the call is submitted after `params.deadline`.\"}],\"InsufficientOut(uint256,uint256)\":[{\"notice\":\"Thrown when swap proceeds are below `minOut`.\"}],\"InvalidIntermediate()\":[{\"notice\":\"Thrown when a two-hop `intermediateToken` is zero, or equals the debt or bStock token.\"}],\"NativeTransferFailed()\":[{\"notice\":\"Thrown when a native BNB transfer (the `sweepNative` payout) fails.\"}],\"NotOperator()\":[{\"notice\":\"Thrown when the caller is neither the owner nor an allowlisted operator.\"}],\"OnlyComptroller()\":[{\"notice\":\"Thrown when `executeOperation` is called by something other than the Comptroller.\"}],\"RedeemFailed(uint256)\":[{\"notice\":\"Thrown when `vBStock.redeem` returns a non-zero error code.\"}],\"RouterNotAllowed(address)\":[{\"notice\":\"Thrown when the supplied swap router is not allowlisted.\"}],\"SwapFailed()\":[{\"notice\":\"Thrown when the low-level call to the router reverts.\"}],\"WrongFlashAsset()\":[{\"notice\":\"Thrown when the flashed asset does not match `params.vDebt`.\"}],\"ZeroAddressNotAllowed()\":[{\"notice\":\"Thrown if the supplied address is a zero address where it is not allowed\"}],\"ZeroMinOut()\":[{\"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).\"}]},\"events\":{\"Liquidated(address,address,address,uint256,uint256,uint256,bool)\":{\"notice\":\"Emitted on a successful liquidation.\"},\"OperatorSet(address,bool)\":{\"notice\":\"Emitted when an operator is allowlisted or removed.\"},\"RouterSet(address,bool)\":{\"notice\":\"Emitted when a swap router is allowlisted or removed.\"},\"Swept(address,address,uint256)\":{\"notice\":\"Emitted when the owner withdraws a token.\"},\"SweptNative(address,uint256)\":{\"notice\":\"Emitted when the owner withdraws stuck native BNB.\"}},\"kind\":\"user\",\"methods\":{\"comptroller()\":{\"notice\":\"Core Comptroller (diamond): reads the liquidation gate and provides the flash loan via `executeFlashLoan`.\"},\"constructor\":{\"notice\":\"Constructor for the implementation contract. Sets the immutables and locks initializers.\"},\"executeOperation(address[],uint256[],uint256[],address,address,bytes)\":{\"notice\":\"Executes an operation after receiving the flash-borrowed assets.\"},\"flashLiquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))\":{\"notice\":\"Liquidate by flash-borrowing the repay amount from Venus, repaid (+ premium) in the same tx.\"},\"initialize(address)\":{\"notice\":\"Initializes the proxy: sets the owner and the reentrancy guard.\"},\"isOperator(address)\":{\"notice\":\"Addresses allowed to trigger a liquidation.\"},\"isRouter(address)\":{\"notice\":\"Routers allowed as the swap target (defends the low-level call).\"},\"liquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))\":{\"notice\":\"Liquidate using the contract's own debt-asset inventory.\"},\"setOperator(address,bool)\":{\"notice\":\"Allow or disallow an address to trigger liquidations.\"},\"setRouter(address,bool)\":{\"notice\":\"Allow or disallow a router as the swap target (e.g. the Native router).\"},\"sweep(address,address,uint256)\":{\"notice\":\"Withdraw any token (profit, leftover inventory, stuck dust) to `to`.\"},\"sweepNative(address,uint256)\":{\"notice\":\"Withdraw stuck native BNB (a stray transfer, or a gate refund) to `to`.\"},\"vBNB()\":{\"notice\":\"The native BNB market (vBNB). A debt equal to this address is settled with native BNB: WBNB is the debt-accounting token, and only the repay amount is unwrapped (see `_liquidate`).\"},\"vWBNB()\":{\"notice\":\"The WBNB market (vWBNB). BNB debt is flash-funded from here, NOT from vBNB: vBNB cannot be flash-repaid (its `doTransferIn` needs `msg.value`), whereas vWBNB's underlying is a plain ERC20 repaid via `transferFrom`.\"},\"wbnb()\":{\"notice\":\"WBNB token: the debt-accounting asset for BNB debt, unwrapped to native BNB for the repay.\"}},\"notice\":\"Atomic backstop liquidator for bStock (ERC-8056 tokenized stock) collateral. In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken, redeems it to the raw bStock, and sells that bStock to the debt asset in one or two hops. Hop 1 always sells bStock through the Native RFQ router using a pre-fetched, MM-signed firm-quote `txRequest`. For USDT debt that single hop lands the debt asset directly. For non-USDT debt an OPTIONAL hop 2 (Native quotes bStock->USDT only) converts the USDT to the debt asset through a second allowlisted router (an AMM/aggregator). Because seize and sell happen in the same tx there is no price-drift window, and the realized debt-asset amount must clear `minOut` or the whole call reverts \\u2014 the protocol never ends up holding the RFQ-only asset or the intermediate. Two funding modes share the same core (`_liquidate`): - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance. - FLASH: the debt asset is flash-borrowed from Venus (`Comptroller.executeFlashLoan`) and repaid (+ premium) within the same tx; no capital is locked in the contract. Native BNB debt (vBNB): supported in both modes with WBNB as the debt-accounting token. The repay must be native BNB, so exactly the repay amount of WBNB is unwrapped and forwarded to the gate's payable path; the two-hop swap lands WBNB (bStock->USDT->WBNB) and `minOut` is measured in WBNB (1:1 with BNB). FLASH mode borrows from vWBNB, NOT vBNB: vBNB cannot be flash-repaid (its `doTransferIn` requires `msg.value`), whereas vWBNB's underlying is a plain ERC20. Ownership / scope: this is Venus's OWN backstop tool, NOT a public utility \\u2014 `liquidate` and `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus path with their own funds and their own offload. This contract is intentionally gated because it custodies funds (debt-asset inventory / flash principal) and forwards a CALLER-SUPPLIED calldata blob to an external router \\u2014 the swap's recipient (`to`) lives inside that calldata, so an open entrypoint would let anyone route the proceeds to themselves and drain the contract. The router allowlist and `minOut` bound the blast radius but cannot replace operator-gating. Security model: - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator). - BOTH swap targets (`router` and, when set, `router2`) must be allowlisted (`isRouter`) \\u2014 defends the low-level `router.call(swapCalldata)` on each hop. - the approval to each router is the exact amount being sold on that hop, reset to 0 afterwards (bStock on hop 1; the measured intermediate balance delta on hop 2, so pre-existing inventory is never exposed). - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started). - the realized debt-asset amount must clear `minOut` or the tx reverts. Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`, which is always configured on the networks this contract targets. While it is set, a direct `vToken.liquidateBorrow` from an arbitrary caller reverts UNAUTHORIZED, so this contract reads the gate at call time and routes the repay through that Venus Liquidator (the permissionless entry anyone may call), reverting if the gate is ever unset. Routing through the gate needs no governance change, and no other Core market is affected. Note: setting THIS contract as `liquidatorContract` is NOT an option \\u2014 the gate is pool-wide, so every other market's liquidations would be forced through here.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/BStock/BStockLiquidator.sol\":\"BStockLiquidator\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./OwnableUpgradeable.sol\\\";\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Contract module which provides access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership} and {acceptOwnership}.\\n *\\n * This module is used through inheritance. It will make available all functions\\n * from parent (Ownable).\\n */\\nabstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {\\n address private _pendingOwner;\\n\\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\\n\\n function __Ownable2Step_init() internal onlyInitializing {\\n __Ownable_init_unchained();\\n }\\n\\n function __Ownable2Step_init_unchained() internal onlyInitializing {\\n }\\n /**\\n * @dev Returns the address of the pending owner.\\n */\\n function pendingOwner() public view virtual returns (address) {\\n return _pendingOwner;\\n }\\n\\n /**\\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual override onlyOwner {\\n _pendingOwner = newOwner;\\n emit OwnershipTransferStarted(owner(), newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual override {\\n delete _pendingOwner;\\n super._transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev The new owner accepts the ownership transfer.\\n */\\n function acceptOwnership() public virtual {\\n address sender = _msgSender();\\n require(pendingOwner() == sender, \\\"Ownable2Step: caller is not the new owner\\\");\\n _transferOwnership(sender);\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n}\\n\",\"keccak256\":\"0x9140dabc466abab21b48b72dbda26736b1183a310d0e677d3719d201df026510\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../utils/ContextUpgradeable.sol\\\";\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n function __Ownable_init() internal onlyInitializing {\\n __Ownable_init_unchained();\\n }\\n\\n function __Ownable_init_unchained() internal onlyInitializing {\\n _transferOwnership(_msgSender());\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n require(owner() == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby disabling any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n}\\n\",\"keccak256\":\"0x359a1ab89b46b9aba7bcad3fb651924baf4893d15153049b9976b0fc9be1358e\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)\\n\\npragma solidity ^0.8.2;\\n\\nimport \\\"../../utils/AddressUpgradeable.sol\\\";\\n\\n/**\\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\\n *\\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\\n * reused. This mechanism prevents re-execution of each \\\"step\\\" but allows the creation of new initialization steps in\\n * case an upgrade adds a module that needs to be initialized.\\n *\\n * For example:\\n *\\n * [.hljs-theme-light.nopadding]\\n * ```solidity\\n * contract MyToken is ERC20Upgradeable {\\n * function initialize() initializer public {\\n * __ERC20_init(\\\"MyToken\\\", \\\"MTK\\\");\\n * }\\n * }\\n *\\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\\n * function initializeV2() reinitializer(2) public {\\n * __ERC20Permit_init(\\\"MyToken\\\");\\n * }\\n * }\\n * ```\\n *\\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\\n *\\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\\n *\\n * [CAUTION]\\n * ====\\n * Avoid leaving a contract uninitialized.\\n *\\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\\n *\\n * [.hljs-theme-light.nopadding]\\n * ```\\n * /// @custom:oz-upgrades-unsafe-allow constructor\\n * constructor() {\\n * _disableInitializers();\\n * }\\n * ```\\n * ====\\n */\\nabstract contract Initializable {\\n /**\\n * @dev Indicates that the contract has been initialized.\\n * @custom:oz-retyped-from bool\\n */\\n uint8 private _initialized;\\n\\n /**\\n * @dev Indicates that the contract is in the process of being initialized.\\n */\\n bool private _initializing;\\n\\n /**\\n * @dev Triggered when the contract has been initialized or reinitialized.\\n */\\n event Initialized(uint8 version);\\n\\n /**\\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\\n * `onlyInitializing` functions can be used to initialize parent contracts.\\n *\\n * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a\\n * constructor.\\n *\\n * Emits an {Initialized} event.\\n */\\n modifier initializer() {\\n bool isTopLevelCall = !_initializing;\\n require(\\n (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),\\n \\\"Initializable: contract is already initialized\\\"\\n );\\n _initialized = 1;\\n if (isTopLevelCall) {\\n _initializing = true;\\n }\\n _;\\n if (isTopLevelCall) {\\n _initializing = false;\\n emit Initialized(1);\\n }\\n }\\n\\n /**\\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\\n * used to initialize parent contracts.\\n *\\n * A reinitializer may be used after the original initialization step. This is essential to configure modules that\\n * are added through upgrades and that require initialization.\\n *\\n * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`\\n * cannot be nested. If one is invoked in the context of another, execution will revert.\\n *\\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\\n * a contract, executing them in the right order is up to the developer or operator.\\n *\\n * WARNING: setting the version to 255 will prevent any future reinitialization.\\n *\\n * Emits an {Initialized} event.\\n */\\n modifier reinitializer(uint8 version) {\\n require(!_initializing && _initialized < version, \\\"Initializable: contract is already initialized\\\");\\n _initialized = version;\\n _initializing = true;\\n _;\\n _initializing = false;\\n emit Initialized(version);\\n }\\n\\n /**\\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\\n */\\n modifier onlyInitializing() {\\n require(_initializing, \\\"Initializable: contract is not initializing\\\");\\n _;\\n }\\n\\n /**\\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\\n * through proxies.\\n *\\n * Emits an {Initialized} event the first time it is successfully executed.\\n */\\n function _disableInitializers() internal virtual {\\n require(!_initializing, \\\"Initializable: contract is initializing\\\");\\n if (_initialized != type(uint8).max) {\\n _initialized = type(uint8).max;\\n emit Initialized(type(uint8).max);\\n }\\n }\\n\\n /**\\n * @dev Returns the highest version that has been initialized. See {reinitializer}.\\n */\\n function _getInitializedVersion() internal view returns (uint8) {\\n return _initialized;\\n }\\n\\n /**\\n * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.\\n */\\n function _isInitializing() internal view returns (bool) {\\n return _initializing;\\n }\\n}\\n\",\"keccak256\":\"0x89be10e757d242e9b18d5a32c9fbe2019f6d63052bbe46397a430a1d60d7f794\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)\\n\\npragma solidity ^0.8.0;\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Contract module that helps prevent reentrant calls to a function.\\n *\\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\\n * available, which can be applied to functions to make sure there are no nested\\n * (reentrant) calls to them.\\n *\\n * Note that because there is a single `nonReentrant` guard, functions marked as\\n * `nonReentrant` may not call one another. This can be worked around by making\\n * those functions `private`, and then adding `external` `nonReentrant` entry\\n * points to them.\\n *\\n * TIP: If you would like to learn more about reentrancy and alternative ways\\n * to protect against it, check out our blog post\\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\\n */\\nabstract contract ReentrancyGuardUpgradeable is Initializable {\\n // Booleans are more expensive than uint256 or any type that takes up a full\\n // word because each write operation emits an extra SLOAD to first read the\\n // slot's contents, replace the bits taken up by the boolean, and then write\\n // back. This is the compiler's defense against contract upgrades and\\n // pointer aliasing, and it cannot be disabled.\\n\\n // The values being non-zero value makes deployment a bit more expensive,\\n // but in exchange the refund on every call to nonReentrant will be lower in\\n // amount. Since refunds are capped to a percentage of the total\\n // transaction's gas, it is best to keep them low in cases like this one, to\\n // increase the likelihood of the full refund coming into effect.\\n uint256 private constant _NOT_ENTERED = 1;\\n uint256 private constant _ENTERED = 2;\\n\\n uint256 private _status;\\n\\n function __ReentrancyGuard_init() internal onlyInitializing {\\n __ReentrancyGuard_init_unchained();\\n }\\n\\n function __ReentrancyGuard_init_unchained() internal onlyInitializing {\\n _status = _NOT_ENTERED;\\n }\\n\\n /**\\n * @dev Prevents a contract from calling itself, directly or indirectly.\\n * Calling a `nonReentrant` function from another `nonReentrant`\\n * function is not supported. It is possible to prevent this from happening\\n * by making the `nonReentrant` function external, and making it call a\\n * `private` function that does the actual work.\\n */\\n modifier nonReentrant() {\\n _nonReentrantBefore();\\n _;\\n _nonReentrantAfter();\\n }\\n\\n function _nonReentrantBefore() private {\\n // On the first call to nonReentrant, _status will be _NOT_ENTERED\\n require(_status != _ENTERED, \\\"ReentrancyGuard: reentrant call\\\");\\n\\n // Any calls to nonReentrant after this point will fail\\n _status = _ENTERED;\\n }\\n\\n function _nonReentrantAfter() private {\\n // By storing the original value once again, a refund is triggered (see\\n // https://eips.ethereum.org/EIPS/eip-2200)\\n _status = _NOT_ENTERED;\\n }\\n\\n /**\\n * @dev Returns true if the reentrancy guard is currently set to \\\"entered\\\", which indicates there is a\\n * `nonReentrant` function in the call stack.\\n */\\n function _reentrancyGuardEntered() internal view returns (bool) {\\n return _status == _ENTERED;\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n}\\n\",\"keccak256\":\"0x2025ccf05f6f1f2fd4e078e552836f525a1864e3854ed555047cd732320ab29b\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20Upgradeable {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `from` to `to` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\\n}\\n\",\"keccak256\":\"0x0e1f0f5f62f67a881cd1a9597acbc0a5e4071f3c2c10449a183b922ae7272e3f\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\\n *\\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\\n * need to send a transaction, and thus is not required to hold Ether at all.\\n *\\n * ==== Security Considerations\\n *\\n * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature\\n * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be\\n * considered as an intention to spend the allowance in any specific way. The second is that because permits have\\n * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should\\n * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be\\n * generally recommended is:\\n *\\n * ```solidity\\n * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {\\n * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}\\n * doThing(..., value);\\n * }\\n *\\n * function doThing(..., uint256 value) public {\\n * token.safeTransferFrom(msg.sender, address(this), value);\\n * ...\\n * }\\n * ```\\n *\\n * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of\\n * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also\\n * {SafeERC20-safeTransferFrom}).\\n *\\n * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so\\n * contracts should have entry points that don't rely on permit.\\n */\\ninterface IERC20PermitUpgradeable {\\n /**\\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\\n * given ``owner``'s signed approval.\\n *\\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\\n * ordering also apply here.\\n *\\n * Emits an {Approval} event.\\n *\\n * Requirements:\\n *\\n * - `spender` cannot be the zero address.\\n * - `deadline` must be a timestamp in the future.\\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\\n * over the EIP712-formatted function arguments.\\n * - the signature must use ``owner``'s current nonce (see {nonces}).\\n *\\n * For more information on the signature format, see the\\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\\n * section].\\n *\\n * CAUTION: See Security Considerations above.\\n */\\n function permit(\\n address owner,\\n address spender,\\n uint256 value,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external;\\n\\n /**\\n * @dev Returns the current nonce for `owner`. This value must be\\n * included whenever a signature is generated for {permit}.\\n *\\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\\n * prevents a signature from being used multiple times.\\n */\\n function nonces(address owner) external view returns (uint256);\\n\\n /**\\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\\n */\\n // solhint-disable-next-line func-name-mixedcase\\n function DOMAIN_SEPARATOR() external view returns (bytes32);\\n}\\n\",\"keccak256\":\"0x07e881de3b9f6d2c07909f193f24b96c7fe4ea60013260f3f25aecd8bab3c2f8\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20Upgradeable.sol\\\";\\nimport \\\"../extensions/IERC20PermitUpgradeable.sol\\\";\\nimport \\\"../../../utils/AddressUpgradeable.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20Upgradeable {\\n using AddressUpgradeable for address;\\n\\n /**\\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n */\\n function safeTransfer(IERC20Upgradeable token, address to, uint256 value) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n /**\\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\\n */\\n function safeTransferFrom(IERC20Upgradeable token, address from, address to, uint256 value) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(IERC20Upgradeable token, address spender, uint256 value) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n /**\\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n */\\n function safeIncreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));\\n }\\n\\n /**\\n * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n */\\n function safeDecreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));\\n }\\n }\\n\\n /**\\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\\n * to be set to zero before setting it to a non-zero value, such as USDT.\\n */\\n function forceApprove(IERC20Upgradeable token, address spender, uint256 value) internal {\\n bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);\\n\\n if (!_callOptionalReturnBool(token, approvalCall)) {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));\\n _callOptionalReturn(token, approvalCall);\\n }\\n }\\n\\n /**\\n * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.\\n * Revert on invalid signature.\\n */\\n function safePermit(\\n IERC20PermitUpgradeable token,\\n address owner,\\n address spender,\\n uint256 value,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) internal {\\n uint256 nonceBefore = token.nonces(owner);\\n token.permit(owner, spender, value, deadline, v, r, s);\\n uint256 nonceAfter = token.nonces(owner);\\n require(nonceAfter == nonceBefore + 1, \\\"SafeERC20: permit did not succeed\\\");\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20Upgradeable token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n require(returndata.length == 0 || abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n *\\n * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.\\n */\\n function _callOptionalReturnBool(IERC20Upgradeable token, bytes memory data) private returns (bool) {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false\\n // and not revert is the subcall reverts.\\n\\n (bool success, bytes memory returndata) = address(token).call(data);\\n return\\n success && (returndata.length == 0 || abi.decode(returndata, (bool))) && AddressUpgradeable.isContract(address(token));\\n }\\n}\\n\",\"keccak256\":\"0x23b997be73d3dd46885262704f0f8cfc7273fdadfe303d37969a9561373972b5\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary AddressUpgradeable {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n *\\n * Furthermore, `isContract` will also return true if the target contract within\\n * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,\\n * which only has an effect at the end of a transaction.\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\\n *\\n * _Available since v4.8._\\n */\\n function verifyCallResultFromTarget(\\n address target,\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n if (success) {\\n if (returndata.length == 0) {\\n // only check isContract if the call was successful and the return data is empty\\n // otherwise we already know that it was a contract\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n }\\n return returndata;\\n } else {\\n _revert(returndata, errorMessage);\\n }\\n }\\n\\n /**\\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason or using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n _revert(returndata, errorMessage);\\n }\\n }\\n\\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n /// @solidity memory-safe-assembly\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n}\\n\",\"keccak256\":\"0x9c80f545915582e63fe206c6ce27cbe85a86fc10b9cd2a0e8c9488fb7c2ee422\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract ContextUpgradeable is Initializable {\\n function __Context_init() internal onlyInitializing {\\n }\\n\\n function __Context_init_unchained() internal onlyInitializing {\\n }\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[50] private __gap;\\n}\\n\",\"keccak256\":\"0x75097e35253e7fb282ee4d7f27a80eaacfa759923185bf17302a89cbc059c5ef\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/IAccessControl.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev External interface of AccessControl declared to support ERC165 detection.\\n */\\ninterface IAccessControl {\\n /**\\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\\n *\\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\\n * {RoleAdminChanged} not being emitted signaling this.\\n *\\n * _Available since v3.1._\\n */\\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\\n\\n /**\\n * @dev Emitted when `account` is granted `role`.\\n *\\n * `sender` is the account that originated the contract call, an admin role\\n * bearer except when using {AccessControl-_setupRole}.\\n */\\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Emitted when `account` is revoked `role`.\\n *\\n * `sender` is the account that originated the contract call:\\n * - if using `revokeRole`, it is the admin role bearer\\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\\n */\\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Returns `true` if `account` has been granted `role`.\\n */\\n function hasRole(bytes32 role, address account) external view returns (bool);\\n\\n /**\\n * @dev Returns the admin role that controls `role`. See {grantRole} and\\n * {revokeRole}.\\n *\\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\\n */\\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function grantRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function revokeRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from the calling account.\\n *\\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\\n * purpose is to provide a mechanism for accounts to lose their privileges\\n * if they are compromised (such as when a trusted device is misplaced).\\n *\\n * If the calling account had been granted `role`, emits a {RoleRevoked}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must be `account`.\\n */\\n function renounceRole(bytes32 role, address account) external;\\n}\\n\",\"keccak256\":\"0x59ce320a585d7e1f163cd70390a0ef2ff9cec832e2aa544293a00692465a7a57\",\"license\":\"MIT\"},\"@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\nimport \\\"@openzeppelin/contracts/access/IAccessControl.sol\\\";\\n\\n/**\\n * @title IAccessControlManagerV8\\n * @author Venus\\n * @notice Interface implemented by the `AccessControlManagerV8` contract.\\n */\\ninterface IAccessControlManagerV8 is IAccessControl {\\n function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external;\\n\\n function revokeCallPermission(\\n address contractAddress,\\n string calldata functionSig,\\n address accountToRevoke\\n ) external;\\n\\n function isAllowedToCall(address account, string calldata functionSig) external view returns (bool);\\n\\n function hasPermission(\\n address account,\\n address contractAddress,\\n string calldata functionSig\\n ) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xaa29b098440d0b3a131c5ecdf25ce548790c1b5ac7bf9b5c0264b6af6f7a1e0b\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\ninterface IDeviationBoundedOracle {\\n // --- Enums ---\\n\\n /// @notice Identifies whether a price bound is a minimum or maximum\\n enum PriceBoundType {\\n MIN,\\n MAX\\n }\\n\\n /// @notice Identifies which keeper action a single syncPriceBoundsAndProtections item performs\\n enum KeeperAction {\\n SetMinPrice,\\n SetMaxPrice,\\n ExitProtectionMode\\n }\\n\\n // --- Structs ---\\n\\n /// @notice Per-asset protection state tracking the min/max price window\\n struct MarketProtectionState {\\n /// @notice Lowest price observed in the current window (packed with maxPrice in one slot)\\n uint128 minPrice;\\n /// @notice Highest price observed in the current window\\n uint128 maxPrice;\\n /// @notice Whether protected price is currently being used\\n bool currentlyUsingProtectedPrice;\\n /// @notice Whether this market is whitelisted for bounded pricing\\n bool isBoundedPricingEnabled;\\n /// @notice Timestamp of the last protection trigger \\u2014 reset on every trigger\\n uint64 lastProtectionTriggeredAt;\\n /// @notice Minimum time protection stays active after last trigger\\n uint64 cooldownPeriod;\\n /// @notice The underlying asset address, used to verify initialization\\n address asset;\\n /// @notice Entry deviation threshold (mantissa, e.g. 0.1667e18 = 16.67%); packed with resetThreshold\\n uint128 triggerThreshold;\\n /// @notice Exit threshold (mantissa); window must converge below this for protection to be disabled\\n uint128 resetThreshold;\\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\\n bool cachingEnabled;\\n }\\n\\n /// @notice One item in an syncPriceBoundsAndProtections payload\\n /// @dev `value` is interpreted per-action: the new bound price for SetMinPrice / SetMaxPrice, ignored for ExitProtectionMode\\n struct KeeperActionItem {\\n address asset;\\n KeeperAction action;\\n uint256 value;\\n }\\n\\n /// @notice One item in a setTokenConfigs payload\\n struct TokenConfigInput {\\n /// @notice The underlying asset address\\n address asset;\\n /// @notice Minimum time protection stays active after the last trigger (seconds)\\n uint64 cooldownPeriod;\\n /// @notice Entry deviation threshold (mantissa). Must be between 5% and 50%.\\n uint256 triggerThreshold;\\n /// @notice Exit deviation threshold (mantissa). Must be non-zero and below triggerThreshold.\\n uint256 resetThreshold;\\n /// @notice Whether to enable bounded pricing immediately upon initialization\\n bool enableBoundedPricing;\\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\\n bool enableCaching;\\n }\\n\\n // --- Events ---\\n\\n /// @notice Emitted when protection is initialized for an asset\\n event ProtectionInitialized(\\n address indexed asset,\\n uint128 minPrice,\\n uint128 maxPrice,\\n uint64 cooldownPeriod,\\n uint256 triggerThreshold\\n );\\n\\n /// @notice Emitted when protection mode is triggered for an asset\\n event ProtectionTriggered(address indexed asset, uint256 spotPrice, uint128 minPrice, uint128 maxPrice);\\n\\n /// @notice Emitted when protection mode is disabled for an asset\\n event ProtectionModeExited(address indexed asset);\\n\\n /// @notice Emitted when the keeper updates the minimum price for an asset\\n event MinPriceUpdated(address indexed asset, uint128 oldMin, uint128 newMin);\\n\\n /// @notice Emitted when the keeper updates the maximum price for an asset\\n event MaxPriceUpdated(address indexed asset, uint128 oldMax, uint128 newMax);\\n\\n /// @notice Emitted when the entry threshold is updated for an asset\\n event TriggerThresholdSet(address indexed asset, uint256 oldThreshold, uint256 newThreshold);\\n\\n /// @notice Emitted when the exit threshold is updated for an asset\\n event ResetThresholdSet(address indexed asset, uint256 oldExitThreshold, uint256 newExitThreshold);\\n\\n /// @notice Emitted when the cooldown period is updated for an asset\\n event CooldownPeriodSet(address indexed asset, uint64 oldCooldown, uint64 newCooldown);\\n\\n /// @notice Emitted when an asset's whitelist status changes\\n event BoundedPricingWhitelistUpdated(address indexed asset, bool whitelisted);\\n\\n /// @notice Emitted when the per-asset transient caching flag is toggled\\n event CachingEnabledUpdated(address indexed asset, bool oldEnabled, bool newEnabled);\\n\\n // --- Errors ---\\n\\n /// @notice Thrown when trying to use or update protection for an asset that has not been initialized\\n error MarketNotInitialized(address asset);\\n\\n /// @notice Thrown when trying to initialize an already initialized market\\n error MarketAlreadyInitialized(address asset);\\n\\n /// @notice Thrown when trying to disable protection that is not active\\n error ProtectedPriceInactive(address asset);\\n\\n /// @notice Thrown when trying to disable protection before cooldown has elapsed\\n error CooldownNotElapsed(address asset, uint64 lastProtectionTriggeredAt, uint64 cooldownPeriod);\\n\\n /// @notice Thrown when trying to disable protection before price range has converged\\n error PriceRangeNotConverged(address asset, uint256 currentRangeRatio, uint256 resetThreshold);\\n\\n /// @notice Thrown when keeper tries to set minPrice above current spot\\n error InvalidMinPrice(address asset, uint128 newMin, uint256 currentSpot);\\n\\n /// @notice Thrown when keeper tries to set maxPrice below current spot\\n error InvalidMaxPrice(address asset, uint128 newMax, uint256 currentSpot);\\n\\n /// @notice Thrown when threshold is set below the minimum allowed value\\n error ThresholdBelowMinimum(uint256 threshold, uint256 minimum);\\n\\n /// @notice Thrown when threshold is set above the maximum allowed value\\n error ThresholdAboveMaximum(uint256 threshold, uint256 maximum);\\n\\n /// @notice Thrown when a price exceeds uint128 max\\n error PriceExceedsUint128(uint256 price);\\n\\n /// @notice Thrown when a zero price is provided where a non-zero price is required\\n error ZeroPriceNotAllowed();\\n\\n /// @notice Thrown when trying to initialize protection for VAI\\n error VAINotAllowed();\\n\\n /// @notice Thrown when trying to disable bounded pricing for an asset while protection is active\\n error ProtectedPriceActive(address asset);\\n\\n /// @notice Thrown when the lengths of the arrays are not equal\\n error InvalidArrayLength();\\n\\n /// @notice Thrown when the exit threshold is set at or above the trigger threshold\\n error InvalidResetThreshold(uint256 resetThreshold);\\n\\n /// @notice Thrown when an syncPriceBoundsAndProtections item carries an unsupported action enum value\\n error InvalidKeeperAction(uint8 action);\\n\\n // --- Non-view price functions (update window + trigger protection) ---\\n\\n /**\\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice);\\n\\n /**\\n * @notice Gets the bounded debt price for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice);\\n\\n /**\\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice);\\n\\n // --- State update (call before view price reads to populate transient cache) ---\\n\\n /**\\n * @notice Updates the protection state for a given vToken, caching the resolved collateral and debt prices\\n * @dev Called by PolicyFacet before liquidity calculations so subsequent view price\\n * reads in the same transaction are served from transient storage. The transient\\n * cache is only populated when the asset's `cachingEnabled` flag is `true`.\\n * @param vToken vToken address\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function updateProtectionState(address vToken) external;\\n\\n // --- View price functions (read stored/cached state only) ---\\n\\n /**\\n * @notice Gets the bounded collateral price for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return price The bounded collateral price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 price);\\n\\n /**\\n * @notice Gets the bounded debt price for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return price The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 price);\\n\\n /**\\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice);\\n\\n // --- Keeper functions ---\\n\\n /**\\n * @notice Updates the minimum price in the rolling window for a given asset\\n * @param asset The underlying asset address\\n * @param newMin The new minimum price; must be at or below the current spot and below maxPrice\\n * @custom:access Only authorized keeper addresses\\n * @custom:error ZeroPriceNotAllowed if newMin is zero\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error InvalidMinPrice if newMin exceeds the current spot or is at or above maxPrice\\n * @custom:event MinPriceUpdated\\n */\\n function updateMinPrice(address asset, uint128 newMin) external;\\n\\n /**\\n * @notice Updates the maximum price in the rolling window for a given asset\\n * @param asset The underlying asset address\\n * @param newMax The new maximum price; must be at or above the current spot and above minPrice\\n * @custom:access Only authorized keeper addresses\\n * @custom:error ZeroPriceNotAllowed if newMax is zero\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error InvalidMaxPrice if newMax is below the current spot or is at or below minPrice\\n * @custom:event MaxPriceUpdated\\n */\\n function updateMaxPrice(address asset, uint128 newMax) external;\\n\\n /**\\n * @notice Exits protection mode for a given asset once conditions are met\\n * @param asset The underlying asset address\\n * @custom:access Only authorized monitor/keeper addresses\\n * @custom:error ProtectedPriceInactive if protection is not currently active\\n * @custom:error CooldownNotElapsed if the cooldown period has not elapsed since the last trigger\\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\\n * @custom:event ProtectionModeExited\\n */\\n function exitProtectionMode(address asset) external;\\n\\n /**\\n * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\\n * @dev Each item is processed in array order; any item revert rolls back the whole batch.\\n * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode.\\n * Empty `actions` is a no-op success.\\n * @param actions The list of keeper actions to apply\\n * @custom:access Only authorized keeper addresses\\n * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value\\n * @custom:error PriceExceedsUint128 if a SetMin/SetMax item value overflows uint128\\n * @custom:error ZeroPriceNotAllowed if a SetMin/SetMax item value is zero\\n * @custom:error MarketNotInitialized if any referenced asset has not been initialized\\n * @custom:error InvalidMinPrice if a SetMinPrice item violates the spot/maxPrice constraints\\n * @custom:error InvalidMaxPrice if a SetMaxPrice item violates the spot/minPrice constraints\\n * @custom:error ProtectedPriceInactive if an ExitProtectionMode item targets an asset whose protection is not active\\n * @custom:error CooldownNotElapsed if an ExitProtectionMode item is submitted before cooldown elapsed\\n * @custom:error PriceRangeNotConverged if an ExitProtectionMode item is submitted before window convergence\\n * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\\n */\\n function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external;\\n\\n // --- Admin functions (governance-gated) ---\\n\\n /**\\n * @notice Initializes protection parameters for a new asset\\n * @dev Seeds the initial min/max window from the current ResilientOracle spot price,\\n * confirming the oracle is live for this asset before it is listed.\\n * @param tokenConfig_ Token config input for the asset\\n * @custom:access Only Governance\\n * @custom:error ZeroAddressNotAllowed if asset is the zero address\\n * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero\\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\\n * @custom:error VAINotAllowed if asset is the VAI token\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event ProtectionInitialized\\n * @custom:event BoundedPricingWhitelistUpdated\\n */\\n function setTokenConfig(TokenConfigInput calldata tokenConfig_) external;\\n\\n /**\\n * @notice Batch-initializes protection parameters for multiple assets in a single transaction\\n * @param tokenConfigs_ Array of token config inputs, one per asset\\n * @custom:access Only Governance\\n * @custom:error InvalidArrayLength if the input array is empty\\n * @custom:error ZeroAddressNotAllowed if any asset is the zero address\\n * @custom:error ZeroValueNotAllowed if any cooldownPeriod, triggerThreshold, or resetThreshold is zero\\n * @custom:error MarketAlreadyInitialized if any asset has already been initialized\\n * @custom:error ThresholdBelowMinimum if any triggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if any triggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if any resetThreshold is at or above its triggerThreshold\\n * @custom:error VAINotAllowed if any asset is the VAI token\\n * @custom:error PriceExceedsUint128 if the spot price for any asset overflows uint128\\n * @custom:event ProtectionInitialized for each asset\\n * @custom:event BoundedPricingWhitelistUpdated for each asset\\n */\\n function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external;\\n\\n /**\\n * @notice Sets the cooldown period for an asset\\n * @param asset The underlying asset address\\n * @param newCooldown The new cooldown period in seconds; must be non-zero\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:event CooldownPeriodSet\\n */\\n function setCooldownPeriod(address asset, uint64 newCooldown) external;\\n\\n /**\\n * @notice Sets the trigger and reset thresholds for an asset\\n * @param asset The underlying asset address\\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\\n * @custom:event TriggerThresholdSet if the trigger threshold changed\\n * @custom:event ResetThresholdSet if the reset threshold changed\\n */\\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external;\\n\\n /**\\n * @notice Sets whether bounded pricing is enabled for an asset\\n * @param asset The underlying asset address\\n * @param enabled Whether bounded pricing should be enabled for the asset\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\\n * @custom:event BoundedPricingWhitelistUpdated\\n */\\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external;\\n\\n /**\\n * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset\\n * @dev When disabled, each view/non-view price call recomputes bounded prices from the\\n * live spot instead of reading or writing the transient slots. The initial value is\\n * set via the `enableCaching` argument of `setTokenConfig`.\\n * @param asset The underlying asset address\\n * @param enabled Whether transient caching is enabled for this asset\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:event CachingEnabledUpdated\\n */\\n function setCachingEnabled(address asset, bool enabled) external;\\n\\n // --- View helpers ---\\n\\n /**\\n * @notice Returns the full protection state for an asset\\n * @param asset The underlying asset address\\n * @return minPrice Lowest price observed in the current window\\n * @return maxPrice Highest price observed in the current window\\n * @return currentlyUsingProtectedPrice Whether protected price is currently active\\n * @return isBoundedPricingEnabled Whether the asset is whitelisted for bounded pricing\\n * @return lastProtectionTriggeredAt Timestamp of the last protection trigger\\n * @return cooldownPeriod Minimum time protection stays active after last trigger\\n * @return assetAddr The underlying asset address stored in the struct\\n * @return triggerThreshold Entry deviation threshold (mantissa) that activates protection\\n * @return resetThreshold Exit deviation threshold (mantissa) below which protection can be disabled\\n * @return cachingEnabled Whether transient caching of the bounded pair is enabled for the asset\\n */\\n function assetProtectionConfig(\\n address asset\\n )\\n external\\n view\\n returns (\\n uint128 minPrice,\\n uint128 maxPrice,\\n bool currentlyUsingProtectedPrice,\\n bool isBoundedPricingEnabled,\\n uint64 lastProtectionTriggeredAt,\\n uint64 cooldownPeriod,\\n address assetAddr,\\n uint128 triggerThreshold,\\n uint128 resetThreshold,\\n bool cachingEnabled\\n );\\n\\n /**\\n * @notice Checks if an asset is whitelisted for bounded pricing\\n * @param asset The underlying asset address\\n * @return True if the asset is whitelisted\\n */\\n function isBoundedPricingEnabled(address asset) external view returns (bool);\\n\\n /**\\n * @notice Checks if the asset is currently using the protected (bounded) price\\n * @param asset The underlying asset address\\n * @return True if the asset is currently using the protected price instead of spot\\n */\\n function currentlyUsingProtectedPrice(address asset) external view returns (bool);\\n\\n /**\\n * @notice Checks if protection can be exited for a given asset\\n * @param asset The underlying asset address\\n * @return True if both the cooldown has elapsed and the price range has converged below the exit threshold\\n */\\n function canExitProtection(address asset) external view returns (bool);\\n\\n /**\\n * @notice Returns the initialized asset at the given index (auto-generated array getter)\\n * @param index Array index\\n * @return The asset address at the given index\\n */\\n function allAssets(uint256 index) external view returns (address);\\n\\n /**\\n * @notice Returns all currently whitelisted asset addresses\\n * @return result Array of whitelisted asset addresses\\n */\\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory result);\\n\\n /**\\n * @notice Returns all asset addresses that have ever been initialized\\n * @return Array of all initialized asset addresses\\n */\\n function getInitializedAssets() external view returns (address[] memory);\\n\\n /**\\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the keeper deadband\\n * @param assets Array of asset addresses to check\\n * @param proposedMins Keeper's proposed window minimum prices\\n * @param proposedMaxs Keeper's proposed window maximum prices\\n * @return needsMinUpdate Whether minPrice drift exceeds the deadband for each asset\\n * @return needsMaxUpdate Whether maxPrice drift exceeds the deadband for each asset\\n * @custom:error InvalidArrayLength if the input array lengths do not match\\n */\\n function checkAndGetWindowDrift(\\n address[] calldata assets,\\n uint128[] calldata proposedMins,\\n uint128[] calldata proposedMaxs\\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate);\\n}\\n\",\"keccak256\":\"0xe223d88c17c0d752fdb33fa223b63ce124a798512f3d69f3800e9508e7dff931\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\ninterface OracleInterface {\\n function getPrice(address asset) external view returns (uint256);\\n}\\n\\ninterface ResilientOracleInterface is OracleInterface {\\n function updatePrice(address vToken) external;\\n\\n function updateAssetPrice(address asset) external;\\n\\n function getUnderlyingPrice(address vToken) external view returns (uint256);\\n}\\n\\ninterface BoundValidatorInterface {\\n function validatePriceWithAnchorPrice(\\n address asset,\\n uint256 reporterPrice,\\n uint256 anchorPrice\\n ) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xd3bbb7c9eef19e8f467342df6034ef95399a00964646fb8c82b438968ae3a8c0\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/solidity-utilities/contracts/constants.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\n/// @dev Base unit for computations, usually used in scaling (multiplications, divisions)\\nuint256 constant EXP_SCALE = 1e18;\\n\\n/// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions\\nuint256 constant MANTISSA_ONE = EXP_SCALE;\\n\\n/// @dev The approximate number of seconds per year\\nuint256 constant SECONDS_PER_YEAR = 31_536_000;\\n\",\"keccak256\":\"0x14de93ead464da249af31bea0e3bcfb62ec693bea3475fb4d90f055ac81dc5eb\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/solidity-utilities/contracts/validators.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\n/// @notice Thrown if the supplied address is a zero address where it is not allowed\\nerror ZeroAddressNotAllowed();\\n\\n/// @notice Thrown if the supplied value is 0 where it is not allowed\\nerror ZeroValueNotAllowed();\\n\\n/// @notice Checks if the provided address is nonzero, reverts otherwise\\n/// @param address_ Address to check\\n/// @custom:error ZeroAddressNotAllowed is thrown if the provided address is a zero address\\nfunction ensureNonzeroAddress(address address_) pure {\\n if (address_ == address(0)) {\\n revert ZeroAddressNotAllowed();\\n }\\n}\\n\\n/// @notice Checks if the provided value is nonzero, reverts otherwise\\n/// @param value_ Value to check\\n/// @custom:error ZeroValueNotAllowed is thrown if the provided value is 0\\nfunction ensureNonzeroValue(uint256 value_) pure {\\n if (value_ == 0) {\\n revert ZeroValueNotAllowed();\\n }\\n}\\n\",\"keccak256\":\"0xdb88e14d50dd21889ca3329d755673d022c47e8da005b6a545c7f69c2c4b7b86\",\"license\":\"BSD-3-Clause\"},\"contracts/BStock/BStockLiquidator.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { IERC20Upgradeable } from \\\"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\\\";\\nimport { SafeERC20Upgradeable } from \\\"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\\\";\\nimport { Ownable2StepUpgradeable } from \\\"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\\\";\\nimport { ReentrancyGuardUpgradeable } from \\\"@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol\\\";\\nimport { ensureNonzeroAddress } from \\\"@venusprotocol/solidity-utilities/contracts/validators.sol\\\";\\n\\nimport { VToken } from \\\"../Tokens/VTokens/VToken.sol\\\";\\nimport { IFlashLoanReceiver } from \\\"../FlashLoan/interfaces/IFlashLoanReceiver.sol\\\";\\nimport { IWBNB } from \\\"../external/IWBNB.sol\\\";\\n\\n// Shared Venus interfaces: IComptroller (Core diamond \\u2014 liquidator gate + flash loan), IVToken\\n// (flash-loan asset array element), and ILiquidator (the pool-wide Venus Liquidator gate that pulls\\n// the repay and returns our share of the seized collateral).\\nimport { IComptroller, IVToken, ILiquidator } from \\\"../InterfacesV8.sol\\\";\\nimport { IBStockLiquidator } from \\\"./IBStockLiquidator.sol\\\";\\n\\n/**\\n * @title BStockLiquidator\\n * @author Venus\\n * @notice Atomic backstop liquidator for bStock (ERC-8056 tokenized stock) collateral.\\n *\\n * In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken,\\n * redeems it to the raw bStock, and sells that bStock to the debt asset in one or two hops. Hop 1\\n * always sells bStock through the Native RFQ router using a pre-fetched, MM-signed firm-quote\\n * `txRequest`. For USDT debt that single hop lands the debt asset directly. For non-USDT debt an\\n * OPTIONAL hop 2 (Native quotes bStock->USDT only) converts the USDT to the debt asset through a\\n * second allowlisted router (an AMM/aggregator). Because seize and sell happen in the same tx there\\n * is no price-drift window, and the realized debt-asset amount must clear `minOut` or the whole call\\n * reverts \\u2014 the protocol never ends up holding the RFQ-only asset or the intermediate.\\n *\\n * Two funding modes share the same core (`_liquidate`):\\n * - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance.\\n * - FLASH: the debt asset is flash-borrowed from Venus (`Comptroller.executeFlashLoan`) and repaid\\n * (+ premium) within the same tx; no capital is locked in the contract.\\n *\\n * Native BNB debt (vBNB): supported in both modes with WBNB as the debt-accounting token. The repay\\n * must be native BNB, so exactly the repay amount of WBNB is unwrapped and forwarded to the gate's\\n * payable path; the two-hop swap lands WBNB (bStock->USDT->WBNB) and `minOut` is measured in WBNB\\n * (1:1 with BNB). FLASH mode borrows from vWBNB, NOT vBNB: vBNB cannot be flash-repaid (its\\n * `doTransferIn` requires `msg.value`), whereas vWBNB's underlying is a plain ERC20.\\n *\\n * Ownership / scope: this is Venus's OWN backstop tool, NOT a public utility \\u2014 `liquidate` and\\n * `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock\\n * liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus\\n * path with their own funds and their own offload. This contract is intentionally gated because it\\n * custodies funds (debt-asset inventory / flash principal) and forwards a CALLER-SUPPLIED calldata blob to\\n * an external router \\u2014 the swap's recipient (`to`) lives inside that calldata, so an open entrypoint\\n * would let anyone route the proceeds to themselves and drain the contract. The router allowlist and\\n * `minOut` bound the blast radius but cannot replace operator-gating.\\n *\\n * Security model:\\n * - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator).\\n * - BOTH swap targets (`router` and, when set, `router2`) must be allowlisted (`isRouter`) \\u2014 defends\\n * the low-level `router.call(swapCalldata)` on each hop.\\n * - the approval to each router is the exact amount being sold on that hop, reset to 0 afterwards\\n * (bStock on hop 1; the measured intermediate balance delta on hop 2, so pre-existing inventory\\n * is never exposed).\\n * - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started).\\n * - the realized debt-asset amount must clear `minOut` or the tx reverts.\\n *\\n * Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`,\\n * which is always configured on the networks this contract targets. While it is set, a direct\\n * `vToken.liquidateBorrow` from an arbitrary caller reverts UNAUTHORIZED, so this contract reads the gate\\n * at call time and routes the repay through that Venus Liquidator (the permissionless entry anyone may\\n * call), reverting if the gate is ever unset. Routing through the gate needs no governance change, and no\\n * other Core market is affected. Note: setting THIS contract as `liquidatorContract` is NOT an option \\u2014\\n * the gate is pool-wide, so every other market's liquidations would be forced through here.\\n */\\ncontract BStockLiquidator is\\n Ownable2StepUpgradeable,\\n ReentrancyGuardUpgradeable,\\n IBStockLiquidator,\\n IFlashLoanReceiver\\n{\\n using SafeERC20Upgradeable for IERC20Upgradeable;\\n\\n /// @notice Core Comptroller (diamond): reads the liquidation gate and provides the flash loan\\n /// via `executeFlashLoan`.\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n IComptroller public immutable comptroller;\\n\\n /// @notice The native BNB market (vBNB). A debt equal to this address is settled with native BNB:\\n /// WBNB is the debt-accounting token, and only the repay amount is unwrapped (see `_liquidate`).\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n address public immutable vBNB;\\n\\n /// @notice The WBNB market (vWBNB). BNB debt is flash-funded from here, NOT from vBNB: vBNB cannot be\\n /// flash-repaid (its `doTransferIn` needs `msg.value`), whereas vWBNB's underlying is a plain\\n /// ERC20 repaid via `transferFrom`.\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n IVToken public immutable vWBNB;\\n\\n /// @notice WBNB token: the debt-accounting asset for BNB debt, unwrapped to native BNB for the repay.\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n IWBNB public immutable wbnb;\\n\\n /// @notice Addresses allowed to trigger a liquidation.\\n mapping(address => bool) public isOperator;\\n\\n /// @notice Routers allowed as the swap target (defends the low-level call).\\n mapping(address => bool) public isRouter;\\n\\n /// @dev Reserved storage to allow new state variables in future upgrades without layout clashes.\\n uint256[50] private __gap;\\n\\n modifier onlyOperator() {\\n if (msg.sender != owner() && !isOperator[msg.sender]) revert NotOperator();\\n _;\\n }\\n\\n /// @notice Constructor for the implementation contract. Sets the immutables and locks initializers.\\n /// @param comptroller_ Venus Core Comptroller (diamond) \\u2014 gates liquidation and provides flash loans.\\n /// @param vBNB_ Native BNB market; a debt equal to this address is settled in native BNB.\\n /// @param vWBNB_ WBNB market; the flash-borrow source for BNB debt (vBNB itself cannot be flash-repaid).\\n /// @param wbnb_ WBNB token; the debt-accounting asset for BNB debt, unwrapped for the native repay.\\n /// @custom:oz-upgrades-unsafe-allow constructor\\n constructor(IComptroller comptroller_, address vBNB_, IVToken vWBNB_, IWBNB wbnb_) {\\n ensureNonzeroAddress(address(comptroller_));\\n ensureNonzeroAddress(vBNB_);\\n ensureNonzeroAddress(address(vWBNB_));\\n ensureNonzeroAddress(address(wbnb_));\\n comptroller = comptroller_;\\n vBNB = vBNB_;\\n vWBNB = vWBNB_;\\n wbnb = wbnb_;\\n _disableInitializers();\\n }\\n\\n /// @notice Initializes the proxy: sets the owner and the reentrancy guard.\\n /// @param initialOwner Address that owns the contract (admin + default operator).\\n function initialize(address initialOwner) external initializer {\\n ensureNonzeroAddress(initialOwner);\\n __Ownable2Step_init();\\n __ReentrancyGuard_init();\\n _transferOwnership(initialOwner);\\n }\\n\\n /// @notice Accept native BNB. The expected inflow is `wbnb.withdraw` during a BNB liquidation (the\\n /// unwrapped repay is forwarded to the gate in the same call, so no BNB is retained on the\\n /// happy path). Left permissive (not restricted to `wbnb`) both to keep the receive body\\n /// minimal for WBNB's 2300-gas `.transfer` stipend and to tolerate a stray transfer or a\\n /// future gate refund \\u2014 any such balance is recoverable via `sweepNative`.\\n receive() external payable {}\\n\\n // --------------------------------------------------------------------- //\\n // Admin //\\n // --------------------------------------------------------------------- //\\n\\n /// @inheritdoc IBStockLiquidator\\n function setOperator(address operator, bool allowed) external override onlyOwner {\\n ensureNonzeroAddress(operator);\\n isOperator[operator] = allowed;\\n emit OperatorSet(operator, allowed);\\n }\\n\\n /// @inheritdoc IBStockLiquidator\\n function setRouter(address router, bool allowed) external override onlyOwner {\\n ensureNonzeroAddress(router);\\n isRouter[router] = allowed;\\n emit RouterSet(router, allowed);\\n }\\n\\n /// @inheritdoc IBStockLiquidator\\n function sweep(address token, address to, uint256 amount) external override onlyOwner {\\n ensureNonzeroAddress(token);\\n ensureNonzeroAddress(to);\\n IERC20Upgradeable(token).safeTransfer(to, amount);\\n emit Swept(token, to, amount);\\n }\\n\\n /// @inheritdoc IBStockLiquidator\\n function sweepNative(address to, uint256 amount) external override onlyOwner {\\n ensureNonzeroAddress(to);\\n (bool ok, ) = to.call{ value: amount }(\\\"\\\");\\n if (!ok) revert NativeTransferFailed();\\n emit SweptNative(to, amount);\\n }\\n\\n // --------------------------------------------------------------------- //\\n // INVENTORY mode //\\n // --------------------------------------------------------------------- //\\n\\n /// @inheritdoc IBStockLiquidator\\n /// @dev INVENTORY mode spends the contract's OWN debt-asset capital, so \\u2014 unlike FLASH mode, where\\n /// `executeOperation` forces the swap proceeds to cover principal + premium \\u2014 there is no\\n /// built-in floor tying `debtOut` to `repayAmount`. This asymmetry is intentional: a repay can\\n /// legitimately out-cost its proceeds (e.g. the Venus Liquidator keeps a treasury cut of the\\n /// seized collateral, so proceeds land a few % under the repay). `minOut` IS the operator's\\n /// chosen loss floor for inventory mode \\u2014 set it to the lowest acceptable debt-asset return.\\n function liquidate(\\n LiquidationParams calldata params\\n ) external override onlyOperator nonReentrant returns (uint256 debtOut) {\\n _validateRouters(params.router, params.router2);\\n\\n if (params.minOut == 0) revert ZeroMinOut();\\n if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp);\\n uint256 seizedBStock;\\n (debtOut, seizedBStock) = _liquidate(params);\\n emit Liquidated(\\n params.borrower,\\n address(params.vBStock),\\n address(params.vDebt),\\n params.repayAmount,\\n seizedBStock,\\n debtOut,\\n false\\n );\\n }\\n\\n // --------------------------------------------------------------------- //\\n // FLASH mode //\\n // --------------------------------------------------------------------- //\\n\\n /// @inheritdoc IBStockLiquidator\\n function flashLiquidate(LiquidationParams calldata params) external override onlyOperator nonReentrant {\\n _validateRouters(params.router, params.router2);\\n\\n if (params.minOut == 0) revert ZeroMinOut();\\n if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp);\\n\\n // BNB debt is flash-funded from vWBNB (an ERC20 market), not vBNB: vBNB cannot be flash-repaid.\\n // The flashed WBNB is unwrapped to native BNB for the repay inside `_liquidate` (see `executeOperation`).\\n IVToken[] memory vTokens = new IVToken[](1);\\n vTokens[0] = (address(params.vDebt) == vBNB) ? vWBNB : IVToken(address(params.vDebt));\\n uint256[] memory amounts = new uint256[](1);\\n amounts[0] = params.repayAmount;\\n\\n comptroller.executeFlashLoan(\\n payable(address(this)),\\n payable(address(this)),\\n vTokens,\\n amounts,\\n abi.encode(params)\\n );\\n }\\n\\n /// @inheritdoc IFlashLoanReceiver\\n function executeOperation(\\n VToken[] calldata vTokens,\\n uint256[] calldata amounts,\\n uint256[] calldata premiums,\\n address initiator,\\n address /* onBehalf */,\\n bytes calldata param\\n ) external override returns (bool, uint256[] memory repayAmounts) {\\n if (msg.sender != address(comptroller)) revert OnlyComptroller();\\n // initiator == this proves the flash was started by our own flashLiquidate: the FlashLoanFacet\\n // passes msg.sender (the executeFlashLoan caller) as `initiator`, and only flashLiquidate calls it.\\n if (initiator != address(this)) revert BadInitiator(initiator);\\n\\n LiquidationParams memory params = abi.decode(param, (LiquidationParams));\\n // For BNB debt the flash is drawn from vWBNB, not vBNB (see `flashLiquidate`). Scoped so the\\n // temporary doesn't count against the stack depth of the rest of the function.\\n {\\n address expectedFlash = address(params.vDebt) == vBNB ? address(vWBNB) : address(params.vDebt);\\n if (address(vTokens[0]) != expectedFlash) revert WrongFlashAsset();\\n }\\n\\n // Repay was just funded by the flash loan; run the liquidation + swap.\\n (uint256 debtOut, uint256 seizedBStock) = _liquidate(params);\\n\\n // The swap proceeds alone MUST cover principal + premium. Without this, any debt-asset inventory\\n // held by the contract would silently backfill an underwater swap (a real loss), since the\\n // flash repayment is pulled from the total balance, not just the swap output.\\n repayAmounts = new uint256[](1);\\n repayAmounts[0] = amounts[0] + premiums[0];\\n if (debtOut < repayAmounts[0]) revert InsufficientOut(debtOut, repayAmounts[0]);\\n\\n // Approve the flashed vToken to pull back principal + premium. For BNB debt the flashed asset is\\n // WBNB (from vWBNB); the ternary short-circuits so `underlying()` is never called on vBNB (it has none).\\n IERC20Upgradeable(address(params.vDebt) == vBNB ? address(wbnb) : params.vDebt.underlying()).forceApprove(\\n address(vTokens[0]),\\n repayAmounts[0]\\n );\\n\\n emit Liquidated(\\n params.borrower,\\n address(params.vBStock),\\n address(params.vDebt),\\n params.repayAmount,\\n seizedBStock,\\n debtOut,\\n true\\n );\\n return (true, repayAmounts);\\n }\\n\\n // --------------------------------------------------------------------- //\\n // Core //\\n // --------------------------------------------------------------------- //\\n\\n /// @dev Pre-flight: every swap router must be allowlisted. `router2` is optional (single-hop when\\n /// zero) so it is only checked when set. Liquidatability itself is not pre-checked here \\u2014 Core's\\n /// `liquidateBorrowAllowed` already enforces it, and pre-checking shortfall would wrongly block\\n /// forced liquidations (which liquidate healthy accounts).\\n function _validateRouters(address router, address router2) private view {\\n if (!isRouter[router]) revert RouterNotAllowed(router);\\n if (router2 != address(0) && !isRouter[router2]) revert RouterNotAllowed(router2);\\n }\\n\\n /// @dev One swap hop: approve the exact `amount` to the allowlisted `router`, forward the opaque\\n /// calldata via a low-level call, then reset the approval to 0. The approval caps what the\\n /// router can pull; if the calldata sells less, the remainder stays as inventory.\\n function _swap(IERC20Upgradeable token, address router, bytes memory data, uint256 amount) private {\\n token.forceApprove(router, amount);\\n (bool ok, ) = router.call(data);\\n if (!ok) revert SwapFailed();\\n token.forceApprove(router, 0); // never leave a standing approval\\n }\\n\\n /**\\n * @dev The atomic sequence shared by both funding modes:\\n * approve debt -> liquidateBorrow (seize vBStock) -> redeem (raw bStock)\\n * -> swap bStock to the debt asset (one hop, or two via an intermediate) -> assert minOut.\\n * A `calldata` struct from `liquidate` is copied to memory on entry; `executeOperation`\\n * already holds it in memory (decoded from the flash callback).\\n * @param params Liquidation parameters.\\n * @return debtOut Debt-asset proceeds realized by the swap chain (reverts if below `minOut`).\\n * @return seizedBStock Raw bStock redeemed and sold (balance delta).\\n */\\n function _liquidate(LiquidationParams memory params) private returns (uint256 debtOut, uint256 seizedBStock) {\\n // A debt equal to `vBNB` is native BNB. vBNB has no `underlying()`, so the `isBnb` check MUST come\\n // first: the ternary short-circuits and `underlying()` is never evaluated for vBNB. WBNB is the\\n // debt-accounting token throughout (1:1 with BNB), so the whole swap/minOut path below is reused.\\n // KEEP THIS ORDER \\u2014 hoisting `underlying()` above the check reverts every BNB liquidation.\\n bool isBnb = address(params.vDebt) == vBNB;\\n // Native RFQ only quotes bStock->USDT, so a BNB debt is inherently two-hop (...->WBNB). Reject a\\n // single-hop BNB config up front instead of failing opaquely later on a zero WBNB delta.\\n if (isBnb && params.router2 == address(0)) revert InvalidIntermediate();\\n IERC20Upgradeable debt = isBnb\\n ? IERC20Upgradeable(address(wbnb))\\n : IERC20Upgradeable(params.vDebt.underlying());\\n IERC20Upgradeable bStock = IERC20Upgradeable(params.vBStock.underlying());\\n\\n // 1. Repay the borrow, seizing the bStock vToken to this contract.\\n // Core has a POOL-WIDE liquidator gate (`liquidatorContract`), which is always configured on\\n // the networks this contract targets. While it is set, a direct `vToken.liquidateBorrow`\\n // reverts UNAUTHORIZED, so we route the repay through that Venus Liquidator (it pulls our\\n // repay and sends us our share of the seized collateral; treasury keeps a cut). The seized\\n // amount is read as a BALANCE DELTA, so the Liquidator's cut is handled correctly. We guard\\n // against an unset gate so a misconfig fails loudly instead of silently no-op'ing a call to\\n // address(0) (a low-level call to a codeless address returns success).\\n uint256 vBefore = params.vBStock.balanceOf(address(this));\\n address gate = comptroller.liquidatorContract();\\n ensureNonzeroAddress(gate);\\n if (isBnb) {\\n // Unwrap EXACTLY the repay (WBNB held as inventory or drawn from the vWBNB flash) and forward\\n // native BNB to the gate's vBNB branch (`{value:}`). Only the repay portion is unwrapped, so\\n // pre-existing WBNB inventory is untouched; the swap proceeds below stay as WBNB. No approval\\n // is granted (value is forwarded), so there is no standing allowance to reset.\\n wbnb.withdraw(params.repayAmount);\\n ILiquidator(gate).liquidateBorrow{ value: params.repayAmount }(\\n address(params.vDebt),\\n params.borrower,\\n params.repayAmount,\\n params.vBStock\\n );\\n } else {\\n debt.forceApprove(gate, params.repayAmount);\\n ILiquidator(gate).liquidateBorrow(\\n address(params.vDebt),\\n params.borrower,\\n params.repayAmount,\\n params.vBStock\\n );\\n // Reset the gate approval: if the Liquidator pulled less than `repayAmount` (e.g. a close-factor\\n // cap), the remainder would otherwise linger as a standing allowance. Same invariant as `_swap`.\\n debt.forceApprove(gate, 0);\\n }\\n uint256 seizedV = params.vBStock.balanceOf(address(this)) - vBefore;\\n\\n // 2. Redeem the seized vBStock for raw bStock. Measure by DELTA so any pre-existing bStock\\n // (dust or a stray transfer) is excluded \\u2014 we only sell what this redeem actually returned.\\n uint256 rawBefore = bStock.balanceOf(address(this));\\n uint256 redeemErr = params.vBStock.redeem(seizedV);\\n if (redeemErr != 0) revert RedeemFailed(redeemErr);\\n seizedBStock = bStock.balanceOf(address(this)) - rawBefore;\\n\\n // 3. Sell the bStock to the debt asset (one hop, or two via an intermediate) and assert minOut.\\n // Extracted into `_sellToDebt` to keep this frame within the EVM stack limit.\\n debtOut = _sellToDebt(debt, bStock, seizedBStock, params);\\n }\\n\\n /**\\n * @dev Sell `seizedBStock` to the debt asset and enforce `minOut`. Single hop by default\\n * (bStock -> debt via the Native router). When `params.router2` is set, two hops\\n * (bStock -> intermediate -> debt): hop 1 sells bStock to the intermediate (USDT) via the\\n * Native router, hop 2 converts that intermediate to the debt asset via a second allowlisted\\n * router (AMM/aggregator). `minOut` is measured in the debt asset across the whole chain.\\n * @return debtOut Debt-asset proceeds (balance delta), reverting if below `minOut`.\\n */\\n function _sellToDebt(\\n IERC20Upgradeable debt,\\n IERC20Upgradeable bStock,\\n uint256 seizedBStock,\\n LiquidationParams memory params\\n ) private returns (uint256 debtOut) {\\n uint256 debtBefore = debt.balanceOf(address(this));\\n if (params.router2 == address(0)) {\\n _swap(bStock, params.router, params.swapCalldata, seizedBStock);\\n } else {\\n // The intermediate must be a real token distinct from both endpoints: if it equals `debt`,\\n // hop 1 would inflate the balance `debtBefore` snapshots against (breaking the proceeds\\n // delta); if it equals `bStock`, the hop-1 sell shrinks the balance and the midDelta\\n // subtraction underflows.\\n if (\\n params.intermediateToken == address(0) ||\\n params.intermediateToken == address(debt) ||\\n params.intermediateToken == address(bStock)\\n ) revert InvalidIntermediate();\\n\\n IERC20Upgradeable mid = IERC20Upgradeable(params.intermediateToken);\\n uint256 midBefore = mid.balanceOf(address(this));\\n _swap(bStock, params.router, params.swapCalldata, seizedBStock); // hop 1: bStock -> intermediate\\n // Only the hop-1 proceeds are sold onward; any pre-existing intermediate inventory is excluded.\\n uint256 midDelta = mid.balanceOf(address(this)) - midBefore;\\n _swap(mid, params.router2, params.swapCalldata2, midDelta); // hop 2: intermediate -> debt\\n }\\n\\n debtOut = debt.balanceOf(address(this)) - debtBefore;\\n if (debtOut < params.minOut) revert InsufficientOut(debtOut, params.minOut);\\n }\\n}\\n\",\"keccak256\":\"0x006270b08a8c02aa9b478175d604a8d8fc1772c49f7ca6b5fe1af513427cf183\",\"license\":\"BSD-3-Clause\"},\"contracts/BStock/IBStockLiquidator.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { IVBep20 } from \\\"../InterfacesV8.sol\\\";\\n\\n/// @title IBStockLiquidator\\n/// @author Venus\\n/// @notice External API, events, errors and parameter struct for {BStockLiquidator}.\\n/// @dev The flash-loan callback `executeOperation` is intentionally NOT part of this interface \\u2014 it is\\n/// declared by {IFlashLoanReceiver} (owned by the Core flash-loan subsystem) and implemented directly\\n/// by {BStockLiquidator}, keeping this interface free of the concrete `VToken` dependency.\\ninterface IBStockLiquidator {\\n /// @notice Parameters for a single liquidation.\\n /// @dev The swap can be one or two hops. When `router2 == address(0)` it is a single hop\\n /// (bStock -> debt) and `swapCalldata2` / `intermediateToken` are ignored \\u2014 behavior is\\n /// identical to the single-hop version. When `router2` is set it is two hops\\n /// (bStock -> `intermediateToken` -> debt): hop 1 sells bStock via `router`, hop 2 converts\\n /// the intermediate to the debt asset via `router2`. `minOut` is always the FINAL debt-asset\\n /// floor across the whole chain. `deadline` is a unix-timestamp expiry: the call reverts once\\n /// `block.timestamp` passes it, so a stale tx cannot settle against an expired quote.\\n struct LiquidationParams {\\n address borrower; // account to liquidate\\n IVBep20 vDebt; // borrowed market to repay (e.g. vUSDT)\\n IVBep20 vBStock; // bStock collateral market to seize (e.g. vTSLAB)\\n uint256 repayAmount; // debt underlying to repay (its own decimals)\\n address router; // hop-1 router = Native firm-quote txRequest.target (must be allowlisted)\\n bytes swapCalldata; // hop-1 calldata (MM-signed Native order): bStock -> intermediate (or -> debt if single-hop)\\n uint256 minOut; // minimum FINAL debt-asset amount the swap chain must yield, else revert\\n address router2; // hop-2 router (AMM/aggregator): intermediate -> debt; address(0) = single-hop\\n bytes swapCalldata2; // hop-2 calldata; the swap recipient inside it MUST be this contract\\n address intermediateToken; // token hop 1 outputs and hop 2 consumes (e.g. USDT); required when router2 set\\n uint256 deadline; // unix timestamp after which the call reverts; guards a stale tx sitting in the mempool\\n }\\n\\n /// @notice Emitted when an operator is allowlisted or removed.\\n event OperatorSet(address indexed operator, bool allowed);\\n\\n /// @notice Emitted when a swap router is allowlisted or removed.\\n event RouterSet(address indexed router, bool allowed);\\n\\n /// @notice Emitted on a successful liquidation.\\n /// @param borrower The liquidated account.\\n /// @param vBStock The seized bStock collateral market.\\n /// @param vDebt The repaid debt market.\\n /// @param repayAmount Debt underlying repaid.\\n /// @param seizedBStock Raw bStock redeemed and sold.\\n /// @param debtOut Debt-asset proceeds of the swap.\\n /// @param flash True if funded by a flash loan, false if from inventory.\\n event Liquidated(\\n address indexed borrower,\\n address indexed vBStock,\\n address indexed vDebt,\\n uint256 repayAmount,\\n uint256 seizedBStock,\\n uint256 debtOut,\\n bool flash\\n );\\n\\n /// @notice Emitted when the owner withdraws a token.\\n event Swept(address indexed token, address indexed to, uint256 amount);\\n\\n /// @notice Emitted when the owner withdraws stuck native BNB.\\n event SweptNative(address indexed to, uint256 amount);\\n\\n /// @notice Thrown when the caller is neither the owner nor an allowlisted operator.\\n error NotOperator();\\n\\n /// @notice Thrown when the supplied swap router is not allowlisted.\\n error RouterNotAllowed(address router);\\n\\n /// @notice Thrown when `vBStock.redeem` returns a non-zero error code.\\n error RedeemFailed(uint256 errCode);\\n\\n /// @notice Thrown when the low-level call to the router reverts.\\n error SwapFailed();\\n\\n /// @notice Thrown when swap proceeds are below `minOut`.\\n error InsufficientOut(uint256 got, uint256 minOut);\\n\\n /// @notice Thrown when `minOut` is zero: a liquidation must set a non-zero debt-asset floor,\\n /// else it would silently accept any proceeds (including zero).\\n error ZeroMinOut();\\n\\n /// @notice Thrown when a two-hop `intermediateToken` is zero, or equals the debt or bStock token.\\n error InvalidIntermediate();\\n\\n /// @notice Thrown when `executeOperation` is called by something other than the Comptroller.\\n error OnlyComptroller();\\n\\n /// @notice Thrown when the flash-loan initiator is not this contract.\\n error BadInitiator(address initiator);\\n\\n /// @notice Thrown when the flashed asset does not match `params.vDebt`.\\n error WrongFlashAsset();\\n\\n /// @notice Thrown when the call is submitted after `params.deadline`.\\n error DeadlineExpired(uint256 deadline, uint256 nowTs);\\n\\n /// @notice Thrown when a native BNB transfer (the `sweepNative` payout) fails.\\n error NativeTransferFailed();\\n\\n /// @notice Allow or disallow an address to trigger liquidations.\\n /// @param operator Address to allowlist or remove.\\n /// @param allowed True to allow, false to remove.\\n function setOperator(address operator, bool allowed) external;\\n\\n /// @notice Allow or disallow a router as the swap target (e.g. the Native router).\\n /// @param router Address to allowlist or remove.\\n /// @param allowed True to allow, false to remove.\\n function setRouter(address router, bool allowed) external;\\n\\n /// @notice Withdraw any token (profit, leftover inventory, stuck dust) to `to`.\\n /// @param token Token to withdraw.\\n /// @param to Recipient.\\n /// @param amount Amount to withdraw.\\n function sweep(address token, address to, uint256 amount) external;\\n\\n /// @notice Withdraw stuck native BNB (a stray transfer, or a gate refund) to `to`.\\n /// @param to Recipient.\\n /// @param amount Amount of native BNB to withdraw.\\n function sweepNative(address to, uint256 amount) external;\\n\\n /// @notice Liquidate using the contract's own debt-asset inventory.\\n /// @dev The contract must already hold >= `repayAmount` of `vDebt.underlying()`.\\n /// Profit (proceeds - repay) stays in the contract; withdraw it with `sweep`.\\n /// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final\\n /// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt).\\n /// @return debtOut Debt-asset proceeds realized by the swap chain.\\n function liquidate(LiquidationParams calldata params) external returns (uint256 debtOut);\\n\\n /// @notice Liquidate by flash-borrowing the repay amount from Venus, repaid (+ premium) in the same tx.\\n /// @dev Requires this contract to be `authorizedFlashLoan` in the Comptroller and `vDebt` flash-enabled.\\n /// Profit (proceeds - repay - premium) stays in the contract; withdraw it with `sweep`.\\n /// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final\\n /// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt).\\n function flashLiquidate(LiquidationParams calldata params) external;\\n}\\n\",\"keccak256\":\"0xdb76c41c7b9bc5e1f979b54c6623c28490ba0a9d1c09f8c2a5fae6cdd07b8ce9\",\"license\":\"BSD-3-Clause\"},\"contracts/Comptroller/ComptrollerInterface.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { ResilientOracleInterface } from \\\"@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol\\\";\\nimport { IDeviationBoundedOracle } from \\\"@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol\\\";\\n\\nimport { VToken } from \\\"../Tokens/VTokens/VToken.sol\\\";\\nimport { VAIControllerInterface } from \\\"../Tokens/VAI/VAIControllerInterface.sol\\\";\\nimport { WeightFunction } from \\\"./Diamond/interfaces/IFacetBase.sol\\\";\\n\\nenum Action {\\n MINT,\\n REDEEM,\\n BORROW,\\n REPAY,\\n SEIZE,\\n LIQUIDATE,\\n TRANSFER,\\n ENTER_MARKET,\\n EXIT_MARKET\\n}\\n\\ninterface ComptrollerInterface {\\n /// @notice Indicator that this is a Comptroller contract (for inspection)\\n function isComptroller() external pure returns (bool);\\n\\n /*** Assets You Are In ***/\\n\\n function enterMarkets(address[] calldata vTokens) external returns (uint[] memory);\\n\\n function exitMarket(address vToken) external returns (uint);\\n\\n /*** Policy Hooks ***/\\n\\n function mintAllowed(address vToken, address minter, uint mintAmount) external returns (uint);\\n\\n function mintVerify(address vToken, address minter, uint mintAmount, uint mintTokens) external;\\n\\n function redeemAllowed(address vToken, address redeemer, uint redeemTokens) external returns (uint);\\n\\n function redeemVerify(address vToken, address redeemer, uint redeemAmount, uint redeemTokens) external;\\n\\n function borrowAllowed(address vToken, address borrower, uint borrowAmount) external returns (uint);\\n\\n function borrowVerify(address vToken, address borrower, uint borrowAmount) external;\\n\\n function executeFlashLoan(\\n address payable onBehalf,\\n address payable receiver,\\n VToken[] calldata vTokens,\\n uint256[] calldata underlyingAmounts,\\n bytes calldata param\\n ) external;\\n\\n function repayBorrowAllowed(\\n address vToken,\\n address payer,\\n address borrower,\\n uint repayAmount\\n ) external returns (uint);\\n\\n function repayBorrowVerify(\\n address vToken,\\n address payer,\\n address borrower,\\n uint repayAmount,\\n uint borrowerIndex\\n ) external;\\n\\n function liquidateBorrowAllowed(\\n address vTokenBorrowed,\\n address vTokenCollateral,\\n address liquidator,\\n address borrower,\\n uint repayAmount\\n ) external returns (uint);\\n\\n function liquidateBorrowVerify(\\n address vTokenBorrowed,\\n address vTokenCollateral,\\n address liquidator,\\n address borrower,\\n uint repayAmount,\\n uint seizeTokens\\n ) external;\\n\\n function seizeAllowed(\\n address vTokenCollateral,\\n address vTokenBorrowed,\\n address liquidator,\\n address borrower,\\n uint seizeTokens\\n ) external returns (uint);\\n\\n function seizeVerify(\\n address vTokenCollateral,\\n address vTokenBorrowed,\\n address liquidator,\\n address borrower,\\n uint seizeTokens\\n ) external;\\n\\n function transferAllowed(address vToken, address src, address dst, uint transferTokens) external returns (uint);\\n\\n function transferVerify(address vToken, address src, address dst, uint transferTokens) external;\\n\\n /*** Liquidity/Liquidation Calculations ***/\\n\\n function liquidateCalculateSeizeTokens(\\n address vTokenBorrowed,\\n address vTokenCollateral,\\n uint repayAmount\\n ) external view returns (uint, uint);\\n\\n function liquidateCalculateSeizeTokens(\\n address borrower,\\n address vTokenBorrowed,\\n address vTokenCollateral,\\n uint repayAmount\\n ) external view returns (uint, uint);\\n\\n function setMintedVAIOf(address owner, uint amount) external returns (uint);\\n\\n function liquidateVAICalculateSeizeTokens(\\n address vTokenCollateral,\\n uint repayAmount\\n ) external view returns (uint, uint);\\n\\n function getXVSAddress() external view returns (address);\\n\\n function markets(address) external view returns (bool, uint, bool, uint, uint, uint96, bool);\\n\\n function oracle() external view returns (ResilientOracleInterface);\\n\\n function deviationBoundedOracle() external view returns (IDeviationBoundedOracle);\\n\\n function getAccountLiquidity(address) external view returns (uint, uint, uint);\\n\\n function getAssetsIn(address) external view returns (VToken[] memory);\\n\\n function claimVenus(address) external;\\n\\n function venusAccrued(address) external view returns (uint);\\n\\n function venusSupplySpeeds(address) external view returns (uint);\\n\\n function venusBorrowSpeeds(address) external view returns (uint);\\n\\n function getAllMarkets() external view returns (VToken[] memory);\\n\\n function venusSupplierIndex(address, address) external view returns (uint);\\n\\n function venusInitialIndex() external view returns (uint224);\\n\\n function venusBorrowerIndex(address, address) external view returns (uint);\\n\\n function venusBorrowState(address) external view returns (uint224, uint32);\\n\\n function venusSupplyState(address) external view returns (uint224, uint32);\\n\\n function approvedDelegates(address borrower, address delegate) external view returns (bool);\\n\\n function vaiController() external view returns (VAIControllerInterface);\\n\\n function protocolPaused() external view returns (bool);\\n\\n function actionPaused(address market, Action action) external view returns (bool);\\n\\n function mintedVAIs(address user) external view returns (uint);\\n\\n function vaiMintRate() external view returns (uint);\\n\\n function authorizedFlashLoan(address account) external view returns (bool);\\n\\n function userPoolId(address account) external view returns (uint96);\\n\\n function getLiquidationIncentive(address vToken) external view returns (uint256);\\n\\n function getEffectiveLiquidationIncentive(address account, address vToken) external view returns (uint256);\\n\\n function getEffectiveLtvFactor(\\n address account,\\n address vToken,\\n WeightFunction weightingStrategy\\n ) external view returns (uint256);\\n\\n function lastPoolId() external view returns (uint96);\\n\\n function corePoolId() external pure returns (uint96);\\n\\n function pools(\\n uint96 poolId\\n ) external view returns (string memory label, bool isActive, bool allowCorePoolFallback);\\n\\n function getPoolVTokens(uint96 poolId) external view returns (address[] memory);\\n\\n function poolMarkets(\\n uint96 poolId,\\n address vToken\\n )\\n external\\n view\\n returns (\\n bool isListed,\\n uint256 collateralFactorMantissa,\\n bool isVenus,\\n uint256 liquidationThresholdMantissa,\\n uint256 liquidationIncentiveMantissa,\\n uint96 marketPoolId,\\n bool isBorrowAllowed\\n );\\n\\n function isFlashLoanPaused() external view returns (bool);\\n}\\n\\ninterface IVAIVault {\\n function updatePendingRewards() external;\\n}\\n\\ninterface IComptroller {\\n /*** Treasury Data ***/\\n function treasuryAddress() external view returns (address);\\n\\n function treasuryPercent() external view returns (uint);\\n}\\n\",\"keccak256\":\"0x79fb234214cebf97f6e85c2bbe013977a4cd38ff6146a7c405a2fa9521e0cd4a\",\"license\":\"BSD-3-Clause\"},\"contracts/Comptroller/Diamond/interfaces/IFacetBase.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { Action } from \\\"../../../Comptroller/ComptrollerInterface.sol\\\";\\nimport { PoolMarketId } from \\\"../../../Comptroller/Types/PoolMarketId.sol\\\";\\n\\nenum WeightFunction {\\n /// @notice Use the collateral factor of the asset for weighting\\n USE_COLLATERAL_FACTOR,\\n /// @notice Use the liquidation threshold of the asset for weighting\\n USE_LIQUIDATION_THRESHOLD\\n}\\n\\ninterface IFacetBase {\\n /**\\n * @notice The initial XVS rewards index for a market\\n */\\n function venusInitialIndex() external pure returns (uint224);\\n\\n /**\\n * @notice Checks if a certain action is paused on a market\\n * @param action Action id\\n * @param market vToken address\\n */\\n function actionPaused(address market, Action action) external view returns (bool);\\n\\n /**\\n * @notice Returns the XVS address\\n * @return The address of XVS token\\n */\\n function getXVSAddress() external view returns (address);\\n\\n function getPoolMarketIndex(uint96 poolId, address vToken) external pure returns (PoolMarketId);\\n\\n function corePoolId() external pure returns (uint96);\\n}\\n\",\"keccak256\":\"0x454a2e213a5f54fbe7991193b78ae23406234f465c9806f75ee7bf2fe6d1531c\",\"license\":\"BSD-3-Clause\"},\"contracts/Comptroller/Types/PoolMarketId.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\n/// @notice Strongly-typed identifier for pool markets mapping keys\\n/// @dev Underlying storage is bytes32: first 12 bytes (96 bits) = poolId, last 20 bytes = vToken address\\ntype PoolMarketId is bytes32;\\n\\n \",\"keccak256\":\"0xf68bde30ddd6f8bf08194b493991c2a2ebd3814972f93a804beb9b366004cbe3\",\"license\":\"BSD-3-Clause\"},\"contracts/FlashLoan/interfaces/IFlashLoanReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { VToken } from \\\"../../Tokens/VTokens/VToken.sol\\\";\\n\\n/// @title IFlashLoanReceiver\\n/// @notice Interface for flashLoan receiver contract, which executes custom logic with flash-borrowed assets.\\n/// @dev This interface defines the method that must be implemented by any contract wishing to interact with the flashLoan system.\\n/// Contracts must ensure they have the means to repay at least the premium (fee), with any unpaid balance becoming debt.\\ninterface IFlashLoanReceiver {\\n /**\\n * @notice Executes an operation after receiving the flash-borrowed assets.\\n * @dev Implementation of this function must ensure at least the premium (fee) is repaid within the same transaction.\\n * Any unpaid balance (principal + premium - repaid amount) will be added to the onBehalf address's borrow balance.\\n * @param vTokens The vToken contracts corresponding to the flash-borrowed underlying assets.\\n * @param amounts The amounts of each underlying asset that were flash-borrowed.\\n * @param premiums The premiums (fees) associated with each flash-borrowed asset.\\n * @param initiator The address that initiated the flash loan.\\n * @param onBehalf The address of the user whose debt position will be used for any unpaid flash loan balance.\\n * @param param Additional parameters encoded as bytes. These can be used to pass custom data to the receiver contract.\\n * @return success True if the operation succeeds (regardless of repayment amount), false if the operation fails.\\n * @return repayAmounts Array of uint256 representing the amounts to be repaid for each asset. The receiver contract\\n * must approve these amounts to the respective vToken contracts before this function returns.\\n */\\n function executeOperation(\\n VToken[] calldata vTokens,\\n uint256[] calldata amounts,\\n uint256[] calldata premiums,\\n address initiator,\\n address onBehalf,\\n bytes calldata param\\n ) external returns (bool success, uint256[] memory repayAmounts);\\n}\\n\",\"keccak256\":\"0xe8eb540e98551178726f9d2d24e7ae1ab12ab96349c22f7232bacd377a0b33dd\",\"license\":\"BSD-3-Clause\"},\"contracts/InterestRateModels/InterestRateModelV8.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\n/**\\n * @title Venus's InterestRateModelV8 Interface\\n * @author Venus\\n */\\nabstract contract InterestRateModelV8 {\\n /// @notice Indicator that this is an InterestRateModel contract (for inspection)\\n bool public constant isInterestRateModel = true;\\n\\n /**\\n * @notice Calculates the current borrow interest rate per block\\n * @param cash The total amount of cash the market has\\n * @param borrows The total amount of borrows the market has outstanding\\n * @param reserves The total amnount of reserves the market has\\n * @return The borrow rate per block (as a percentage, and scaled by 1e18)\\n */\\n function getBorrowRate(uint256 cash, uint256 borrows, uint256 reserves) external view virtual returns (uint256);\\n\\n /**\\n * @notice Calculates the current supply interest rate per block\\n * @param cash The total amount of cash the market has\\n * @param borrows The total amount of borrows the market has outstanding\\n * @param reserves The total amnount of reserves the market has\\n * @param reserveFactorMantissa The current reserve factor the market has\\n * @return The supply rate per block (as a percentage, and scaled by 1e18)\\n */\\n function getSupplyRate(\\n uint256 cash,\\n uint256 borrows,\\n uint256 reserves,\\n uint256 reserveFactorMantissa\\n ) external view virtual returns (uint256);\\n}\\n\",\"keccak256\":\"0x4d5595e761d50a1431c34b39e72dde6c09b0ebccdbe8c5c4e12c8a2ac7b796e1\",\"license\":\"BSD-3-Clause\"},\"contracts/InterfacesV8.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\nimport { IERC20Upgradeable } from \\\"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\\\";\\nimport { ResilientOracleInterface } from \\\"@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol\\\";\\n\\ninterface IVToken is IERC20Upgradeable {\\n function accrueInterest() external returns (uint256);\\n\\n function redeem(uint256 redeemTokens) external returns (uint256);\\n\\n function redeemUnderlying(uint256 redeemAmount) external returns (uint256);\\n\\n function borrowBalanceCurrent(address borrower) external returns (uint256);\\n\\n function balanceOfUnderlying(address owner) external returns (uint256);\\n\\n function comptroller() external view returns (IComptroller);\\n\\n function borrowBalanceStored(address account) external view returns (uint256);\\n}\\n\\ninterface IVBep20 is IVToken {\\n function borrowBehalf(address borrower, uint256 borrowAmount) external returns (uint256);\\n\\n function repayBorrowBehalf(address borrower, uint256 repayAmount) external returns (uint256);\\n\\n function liquidateBorrow(\\n address borrower,\\n uint256 repayAmount,\\n IVToken vTokenCollateral\\n ) external returns (uint256);\\n\\n function underlying() external view returns (address);\\n}\\n\\ninterface IVBNB is IVToken {\\n function repayBorrowBehalf(address borrower) external payable;\\n\\n function liquidateBorrow(address borrower, IVToken vTokenCollateral) external payable;\\n}\\n\\ninterface IVAIController {\\n function accrueVAIInterest() external;\\n\\n function liquidateVAI(\\n address borrower,\\n uint256 repayAmount,\\n IVToken vTokenCollateral\\n ) external returns (uint256, uint256);\\n\\n function repayVAIBehalf(address borrower, uint256 amount) external returns (uint256, uint256);\\n\\n function getVAIAddress() external view returns (address);\\n\\n function getVAIRepayAmount(address borrower) external view returns (uint256);\\n}\\n\\ninterface IComptroller {\\n enum Action {\\n MINT,\\n REDEEM,\\n BORROW,\\n REPAY,\\n SEIZE,\\n LIQUIDATE,\\n TRANSFER,\\n ENTER_MARKET,\\n EXIT_MARKET\\n }\\n\\n function _setActionsPaused(address[] calldata markets_, Action[] calldata actions_, bool paused_) external;\\n\\n function executeFlashLoan(\\n address payable onBehalf,\\n address payable receiver,\\n IVToken[] calldata vTokens,\\n uint256[] calldata underlyingAmounts,\\n bytes calldata param\\n ) external;\\n\\n function vaiController() external view returns (IVAIController);\\n\\n function liquidatorContract() external view returns (address);\\n\\n function getAccountLiquidity(address account) external view returns (uint256, uint256, uint256);\\n\\n function oracle() external view returns (ResilientOracleInterface);\\n\\n function actionPaused(address market, Action action) external view returns (bool);\\n\\n function markets(address) external view returns (bool, uint256, bool);\\n\\n function isForcedLiquidationEnabled(address) external view returns (bool);\\n\\n function getEffectiveLiquidationIncentive(address account, address vToken) external view returns (uint256);\\n}\\n\\ninterface ILiquidator {\\n function restrictLiquidation(address borrower) external;\\n\\n function unrestrictLiquidation(address borrower) external;\\n\\n function addToAllowlist(address borrower, address liquidator) external;\\n\\n function removeFromAllowlist(address borrower, address liquidator) external;\\n\\n function liquidateBorrow(\\n address vToken,\\n address borrower,\\n uint256 repayAmount,\\n IVToken vTokenCollateral\\n ) external payable;\\n\\n function setTreasuryPercent(uint256 newTreasuryPercentMantissa) external;\\n\\n function treasuryPercentMantissa() external view returns (uint256);\\n}\\n\",\"keccak256\":\"0x02c315f8a158e79d96a4befd4b90473eebb8af75580a27c1eb3b001b421389a0\",\"license\":\"BSD-3-Clause\"},\"contracts/Tokens/VAI/VAIControllerInterface.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { VTokenInterface } from \\\"../VTokens/VTokenInterfaces.sol\\\";\\n\\ninterface VAIControllerInterface {\\n function mintVAI(uint256 mintVAIAmount) external returns (uint256);\\n\\n function repayVAI(uint256 amount) external returns (uint256, uint256);\\n\\n function repayVAIBehalf(address borrower, uint256 amount) external returns (uint256, uint256);\\n\\n function liquidateVAI(\\n address borrower,\\n uint256 repayAmount,\\n VTokenInterface vTokenCollateral\\n ) external returns (uint256, uint256);\\n\\n function getMintableVAI(address minter) external view returns (uint256, uint256);\\n\\n function getVAIAddress() external view returns (address);\\n\\n function getVAIRepayAmount(address account) external view returns (uint256);\\n}\\n\",\"keccak256\":\"0x69d2f9e13b7fbf0a29885048503642372d9ba3d37f2427d4b9cffb87eddd925b\",\"license\":\"BSD-3-Clause\"},\"contracts/Tokens/VTokens/VToken.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { IAccessControlManagerV8 } from \\\"@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol\\\";\\nimport { IProtocolShareReserve } from \\\"../../external/IProtocolShareReserve.sol\\\";\\nimport { ComptrollerInterface, IComptroller } from \\\"../../Comptroller/ComptrollerInterface.sol\\\";\\nimport { TokenErrorReporter } from \\\"../../Utils/ErrorReporter.sol\\\";\\nimport { Exponential } from \\\"../../Utils/Exponential.sol\\\";\\nimport { InterestRateModelV8 } from \\\"../../InterestRateModels/InterestRateModelV8.sol\\\";\\nimport { VTokenInterface } from \\\"./VTokenInterfaces.sol\\\";\\nimport { MANTISSA_ONE } from \\\"@venusprotocol/solidity-utilities/contracts/constants.sol\\\";\\n\\n/**\\n * @title Venus's vToken Contract\\n * @notice Abstract base for vTokens\\n * @author Venus\\n */\\nabstract contract VToken is VTokenInterface, Exponential, TokenErrorReporter {\\n struct MintLocalVars {\\n MathError mathErr;\\n uint exchangeRateMantissa;\\n uint mintTokens;\\n uint totalSupplyNew;\\n uint accountTokensNew;\\n uint actualMintAmount;\\n }\\n\\n struct RedeemLocalVars {\\n MathError mathErr;\\n uint exchangeRateMantissa;\\n uint redeemTokens;\\n uint redeemAmount;\\n uint totalSupplyNew;\\n uint accountTokensNew;\\n }\\n\\n struct BorrowLocalVars {\\n MathError mathErr;\\n uint accountBorrows;\\n uint accountBorrowsNew;\\n uint totalBorrowsNew;\\n }\\n\\n struct RepayBorrowLocalVars {\\n Error err;\\n MathError mathErr;\\n uint repayAmount;\\n uint borrowerIndex;\\n uint accountBorrows;\\n uint accountBorrowsNew;\\n uint totalBorrowsNew;\\n uint actualRepayAmount;\\n }\\n\\n /*** Reentrancy Guard ***/\\n\\n /**\\n * @dev Prevents a contract from calling itself, directly or indirectly.\\n */\\n modifier nonReentrant() {\\n require(_notEntered, \\\"re-entered\\\");\\n _notEntered = false;\\n _;\\n _notEntered = true; // get a gas-refund post-Istanbul\\n }\\n\\n /**\\n * @notice Transfer `amount` tokens from `msg.sender` to `dst`\\n * @param dst The address of the destination account\\n * @param amount The number of tokens to transfer\\n * @return Whether or not the transfer succeeded\\n */\\n // @custom:event Emits Transfer event\\n function transfer(address dst, uint256 amount) external override nonReentrant returns (bool) {\\n return transferTokens(msg.sender, msg.sender, dst, amount) == uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Transfer `amount` tokens from `src` to `dst`\\n * @param src The address of the source account\\n * @param dst The address of the destination account\\n * @param amount The number of tokens to transfer\\n * @return Whether or not the transfer succeeded\\n */\\n // @custom:event Emits Transfer event\\n function transferFrom(address src, address dst, uint256 amount) external override nonReentrant returns (bool) {\\n return transferTokens(msg.sender, src, dst, amount) == uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Approve `spender` to transfer up to `amount` from `src`\\n * @dev This will overwrite the approval amount for `spender`\\n * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)\\n * @param spender The address of the account which may transfer tokens\\n * @param amount The number of tokens that are approved (type(uint256).max means infinite)\\n * @return Whether or not the approval succeeded\\n */\\n // @custom:event Emits Approval event on successful approve\\n function approve(address spender, uint256 amount) external override returns (bool) {\\n transferAllowances[msg.sender][spender] = amount;\\n emit Approval(msg.sender, spender, amount);\\n return true;\\n }\\n\\n /**\\n * @notice Get the underlying balance of the `owner`\\n * @dev This also accrues interest in a transaction\\n * @param owner The address of the account to query\\n * @return The amount of underlying owned by `owner`\\n */\\n function balanceOfUnderlying(address owner) external override returns (uint) {\\n Exp memory exchangeRate = Exp({ mantissa: exchangeRateCurrent() });\\n (MathError mErr, uint balance) = mulScalarTruncate(exchangeRate, accountTokens[owner]);\\n ensureNoMathError(mErr);\\n return balance;\\n }\\n\\n /**\\n * @notice Returns the current total borrows plus accrued interest\\n * @return The total borrows with interest\\n */\\n function totalBorrowsCurrent() external override nonReentrant returns (uint) {\\n require(accrueInterest() == uint(Error.NO_ERROR), \\\"accrue interest failed\\\");\\n return totalBorrows;\\n }\\n\\n /**\\n * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex\\n * @param account The address whose balance should be calculated after updating borrowIndex\\n * @return The calculated balance\\n */\\n function borrowBalanceCurrent(address account) external override nonReentrant returns (uint) {\\n require(accrueInterest() == uint(Error.NO_ERROR), \\\"accrue interest failed\\\");\\n return borrowBalanceStored(account);\\n }\\n\\n /**\\n * @notice Transfers collateral tokens (this market) to the liquidator.\\n * @dev Will fail unless called by another vToken during the process of liquidation.\\n * Its absolutely critical to use msg.sender as the borrowed vToken and not a parameter.\\n * @param liquidator The account receiving seized collateral\\n * @param borrower The account having collateral seized\\n * @param seizeTokens The number of vTokens to seize\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits Transfer event\\n function seize(\\n address liquidator,\\n address borrower,\\n uint seizeTokens\\n ) external override nonReentrant returns (uint) {\\n return seizeInternal(msg.sender, liquidator, borrower, seizeTokens);\\n }\\n\\n /**\\n * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.\\n * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.\\n * @param newPendingAdmin New pending admin.\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits NewPendingAdmin event with old and new admin addresses\\n function _setPendingAdmin(address payable newPendingAdmin) external override returns (uint) {\\n // Check caller = admin\\n ensureAdmin(msg.sender);\\n\\n // Save current value, if any, for inclusion in log\\n address oldPendingAdmin = pendingAdmin;\\n\\n // Store pendingAdmin with value newPendingAdmin\\n pendingAdmin = newPendingAdmin;\\n\\n // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin)\\n emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin\\n * @dev Admin function for pending admin to accept role and update admin\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits NewAdmin event on successful acceptance\\n // @custom:event Emits NewPendingAdmin event with null new pending admin\\n function _acceptAdmin() external override returns (uint) {\\n // Check caller is pendingAdmin\\n if (msg.sender != pendingAdmin) {\\n return fail(Error.UNAUTHORIZED, FailureInfo.ACCEPT_ADMIN_PENDING_ADMIN_CHECK);\\n }\\n\\n // Save current values for inclusion in log\\n address oldAdmin = admin;\\n address oldPendingAdmin = pendingAdmin;\\n\\n // Store admin with value pendingAdmin\\n admin = pendingAdmin;\\n\\n // Clear the pending value\\n pendingAdmin = payable(address(0));\\n\\n emit NewAdmin(oldAdmin, admin);\\n emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice accrues interest and sets a new reserve factor for the protocol using `_setReserveFactorFresh`\\n * @dev Governor function to accrue interest and set a new reserve factor\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits NewReserveFactor event\\n function _setReserveFactor(uint newReserveFactorMantissa_) external override nonReentrant returns (uint) {\\n ensureAllowed(\\\"_setReserveFactor(uint256)\\\");\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reserve factor change failed.\\n return fail(Error(error), FailureInfo.SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // _setReserveFactorFresh emits reserve-factor-specific logs on errors, so we don't need to.\\n return _setReserveFactorFresh(newReserveFactorMantissa_);\\n }\\n\\n /**\\n * @notice Sets the address of the access control manager of this contract\\n * @dev Admin function to set the access control address\\n * @param newAccessControlManagerAddress New address for the access control\\n * @return uint 0=success, otherwise will revert\\n */\\n function setAccessControlManager(address newAccessControlManagerAddress) external returns (uint) {\\n // Check caller is admin\\n ensureAdmin(msg.sender);\\n\\n ensureNonZeroAddress(newAccessControlManagerAddress);\\n\\n emit NewAccessControlManager(accessControlManager, newAccessControlManagerAddress);\\n accessControlManager = newAccessControlManagerAddress;\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Accrues interest and reduces reserves by transferring to protocol share reserve\\n * @param reduceAmount_ Amount of reduction to reserves\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits ReservesReduced event\\n function _reduceReserves(uint reduceAmount_) external virtual override nonReentrant returns (uint) {\\n ensureAllowed(\\\"_reduceReserves(uint256)\\\");\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed.\\n return fail(Error(error), FailureInfo.REDUCE_RESERVES_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // If reserves were reduced in accrueInterest\\n if (reduceReservesBlockNumber == block.number) return (uint(Error.NO_ERROR));\\n // _reduceReservesFresh emits reserve-reduction-specific logs on errors, so we don't need to.\\n return _reduceReservesFresh(reduceAmount_);\\n }\\n\\n /**\\n * @notice Get the current allowance from `owner` for `spender`\\n * @param owner The address of the account which owns the tokens to be spent\\n * @param spender The address of the account which may transfer tokens\\n * @return The number of tokens allowed to be spent (type(uint256).max means infinite)\\n */\\n function allowance(address owner, address spender) external view override returns (uint256) {\\n return transferAllowances[owner][spender];\\n }\\n\\n /**\\n * @notice Get the token balance of the `owner`\\n * @param owner The address of the account to query\\n * @return The number of tokens owned by `owner`\\n */\\n function balanceOf(address owner) external view override returns (uint256) {\\n return accountTokens[owner];\\n }\\n\\n /**\\n * @notice Get a snapshot of the account's balances, and the cached exchange rate\\n * @dev This is used by comptroller to more efficiently perform liquidity checks.\\n * @param account Address of the account to snapshot\\n * @return (possible error, token balance, borrow balance, exchange rate mantissa)\\n */\\n function getAccountSnapshot(address account) external view override returns (uint, uint, uint, uint) {\\n uint borrowBalance;\\n uint exchangeRateMantissa;\\n\\n MathError mErr;\\n\\n (mErr, borrowBalance) = borrowBalanceStoredInternal(account);\\n if (mErr != MathError.NO_ERROR) {\\n return (uint(Error.MATH_ERROR), 0, 0, 0);\\n }\\n\\n (mErr, exchangeRateMantissa) = exchangeRateStoredInternal();\\n if (mErr != MathError.NO_ERROR) {\\n return (uint(Error.MATH_ERROR), 0, 0, 0);\\n }\\n\\n return (uint(Error.NO_ERROR), accountTokens[account], borrowBalance, exchangeRateMantissa);\\n }\\n\\n /**\\n * @notice Returns the current per-block supply interest rate for this vToken\\n * @dev The calculation includes `flashLoanAmount` in the cash balance to account for active flash loans.\\n * @return The supply interest rate per block, scaled by 1e18\\n */\\n function supplyRatePerBlock() external view override returns (uint) {\\n return\\n interestRateModel.getSupplyRate(\\n _getCashPriorWithFlashLoan(),\\n totalBorrows,\\n totalReserves,\\n reserveFactorMantissa\\n );\\n }\\n\\n /**\\n * @notice Returns the current per-block borrow interest rate for this vToken\\n * @dev The calculation includes `flashLoanAmount` in the cash balance to account for active flash loans.\\n * @return The borrow interest rate per block, scaled by 1e18\\n */\\n function borrowRatePerBlock() external view override returns (uint) {\\n return interestRateModel.getBorrowRate(_getCashPriorWithFlashLoan(), totalBorrows, totalReserves);\\n }\\n\\n /**\\n * @notice Get cash balance of this vToken in the underlying asset\\n * @return The quantity of underlying asset owned by this contract\\n */\\n function getCash() external view override returns (uint) {\\n return getCashPrior();\\n }\\n\\n /**\\n * @notice Governance function to set new threshold of block difference after which funds will be sent to the protocol share reserve\\n * @param newReduceReservesBlockDelta_ block difference value\\n */\\n function setReduceReservesBlockDelta(uint256 newReduceReservesBlockDelta_) external {\\n require(newReduceReservesBlockDelta_ > 0, \\\"Invalid Input\\\");\\n ensureAllowed(\\\"setReduceReservesBlockDelta(uint256)\\\");\\n emit NewReduceReservesBlockDelta(reduceReservesBlockDelta, newReduceReservesBlockDelta_);\\n reduceReservesBlockDelta = newReduceReservesBlockDelta_;\\n }\\n\\n /**\\n * @notice Sets protocol share reserve contract address\\n * @param protcolShareReserve_ The address of protocol share reserve contract\\n */\\n function setProtocolShareReserve(address payable protcolShareReserve_) external {\\n // Check caller is admin\\n ensureAdmin(msg.sender);\\n ensureNonZeroAddress(protcolShareReserve_);\\n emit NewProtocolShareReserve(protocolShareReserve, protcolShareReserve_);\\n protocolShareReserve = protcolShareReserve_;\\n }\\n\\n /**\\n * @notice Transfers the underlying asset to the specified address for flash loan purposes.\\n * @dev Can only be called by the Comptroller contract. This function performs the actual transfer of the underlying\\n * asset by calling the `doTransferOut` internal function.\\n * - The caller must be the Comptroller contract.\\n * - Sets the flashLoanAmount to track the borrowed amount during the flash loan process.\\n * @param to The address to which the underlying asset is to be transferred.\\n * @param amount The amount of the underlying asset to transfer.\\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\\n * @custom:error FlashLoanAlreadyActive is thrown if there is already an active flash loan.\\n * @custom:error InsufficientCash is thrown when the vToken does not have enough cash to lend\\n * @custom:event Emits TransferOutUnderlyingFlashLoan event on successful transfer of amount to receiver\\n */\\n function transferOutUnderlyingFlashLoan(address payable to, uint256 amount) external nonReentrant {\\n if (msg.sender != address(comptroller)) {\\n revert InvalidComptroller();\\n }\\n\\n if (flashLoanAmount > 0) {\\n revert FlashLoanAlreadyActive();\\n }\\n\\n if (getCashPrior() < amount) {\\n revert InsufficientCash();\\n }\\n flashLoanAmount = amount;\\n doTransferOut(to, amount);\\n emit TransferOutUnderlyingFlashLoan(underlying, to, amount);\\n }\\n\\n /**\\n * @notice Transfers the underlying asset from the specified address during flash loan repayment.\\n * @dev Can only be called by the Comptroller contract. This function performs the actual transfer of the underlying\\n * asset by calling the `doTransferIn` internal function and handles protocol fee distribution.\\n * - The caller must be the Comptroller contract.\\n * - Transfers the protocol fee to the protocol share reserve.\\n * - Resets the flashLoanAmount to 0 to complete the flash loan cycle.\\n * @param from The address from which the underlying asset is to be transferred.\\n * @param repaymentAmount The amount of the underlying asset being repaid by the receiver.\\n * @param totalFee The total fee amount for the flash loan.\\n * @param protocolFee The protocol fee amount to be transferred to the protocol share reserve.\\n * @return actualAmountTransferred The actual amount transferred in from the receiver.\\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\\n * @custom:error InsufficientRepayment is thrown when the repayment amount is insufficient to cover the total fee\\n * @custom:event Emits TransferInUnderlyingFlashLoan event on successful transfer of amount from the receiver to the vToken\\n */\\n function transferInUnderlyingFlashLoan(\\n address payable from,\\n uint256 repaymentAmount,\\n uint256 totalFee,\\n uint256 protocolFee\\n ) external nonReentrant returns (uint256) {\\n if (msg.sender != address(comptroller)) {\\n revert InvalidComptroller();\\n }\\n\\n uint256 actualAmountTransferred = doTransferIn(from, repaymentAmount);\\n\\n if (actualAmountTransferred < totalFee) {\\n revert InsufficientRepayment(actualAmountTransferred, totalFee);\\n }\\n\\n // Transfer protocol fee to protocol share reserve\\n doTransferOut(protocolShareReserve, protocolFee);\\n\\n IProtocolShareReserve(protocolShareReserve).updateAssetsState(\\n address(comptroller),\\n underlying,\\n IProtocolShareReserve.IncomeType.FLASHLOAN\\n );\\n\\n // Reset flashLoanAmount to complete the flash loan cycle\\n flashLoanAmount = 0;\\n\\n emit TransferInUnderlyingFlashLoan(underlying, from, actualAmountTransferred, totalFee, protocolFee);\\n return actualAmountTransferred;\\n }\\n\\n /**\\n * @notice Sets flash loan status for the market\\n * @param enabled True to enable flash loans, false to disable\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n * @custom:access Only Governance\\n * @custom:event Emits FlashLoanStatusChanged event on success\\n */\\n function setFlashLoanEnabled(bool enabled) external returns (uint256) {\\n ensureAllowed(\\\"setFlashLoanEnabled(bool)\\\");\\n\\n if (isFlashLoanEnabled != enabled) {\\n emit FlashLoanStatusChanged(isFlashLoanEnabled, enabled);\\n isFlashLoanEnabled = enabled;\\n }\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Update flashLoan fee mantissa\\n * @param flashLoanFeeMantissa_ FlashLoan fee, scaled by 1e18\\n * @param flashLoanProtocolShare_ FlashLoan protocol fee share, transferred to protocol share reserve\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n * @custom:access Only Governance\\n * @custom:error FlashLoanFeeTooHigh is thrown when flash loan fee exceeds maximum allowed\\n * @custom:error FlashLoanProtocolShareTooHigh is thrown when flash loan fee protocol share exceeds maximum allowed\\n * @custom:event Emits FlashLoanFeeUpdated event on success\\n */\\n function setFlashLoanFeeMantissa(\\n uint256 flashLoanFeeMantissa_,\\n uint256 flashLoanProtocolShare_\\n ) external returns (uint256) {\\n ensureAllowed(\\\"setFlashLoanFeeMantissa(uint256,uint256)\\\");\\n\\n if (flashLoanFeeMantissa_ > MANTISSA_ONE) {\\n revert FlashLoanFeeTooHigh(flashLoanFeeMantissa_, MANTISSA_ONE);\\n }\\n\\n if (flashLoanProtocolShare_ > MANTISSA_ONE) {\\n revert FlashLoanProtocolShareTooHigh(flashLoanProtocolShare_, MANTISSA_ONE);\\n }\\n\\n // Only proceed if values are changing\\n if (\\n flashLoanFeeMantissa != flashLoanFeeMantissa_ || flashLoanProtocolShareMantissa != flashLoanProtocolShare_\\n ) {\\n emit FlashLoanFeeUpdated(\\n flashLoanFeeMantissa,\\n flashLoanFeeMantissa_,\\n flashLoanProtocolShareMantissa,\\n flashLoanProtocolShare_\\n );\\n\\n flashLoanFeeMantissa = flashLoanFeeMantissa_;\\n flashLoanProtocolShareMantissa = flashLoanProtocolShare_;\\n }\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Initialize the money market\\n * @param comptroller_ The address of the Comptroller\\n * @param interestRateModel_ The address of the interest rate model\\n * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18\\n * @param name_ EIP-20 name of this token\\n * @param symbol_ EIP-20 symbol of this token\\n * @param decimals_ EIP-20 decimal precision of this token\\n */\\n function initialize(\\n ComptrollerInterface comptroller_,\\n InterestRateModelV8 interestRateModel_,\\n uint initialExchangeRateMantissa_,\\n string memory name_,\\n string memory symbol_,\\n uint8 decimals_\\n ) public {\\n ensureAdmin(msg.sender);\\n require(accrualBlockNumber == 0 && borrowIndex == 0, \\\"market may only be initialized once\\\");\\n\\n // Set initial exchange rate\\n initialExchangeRateMantissa = initialExchangeRateMantissa_;\\n require(initialExchangeRateMantissa > 0, \\\"initial exchange rate must be greater than zero.\\\");\\n\\n // Set the comptroller\\n uint err = _setComptroller(comptroller_);\\n require(err == uint(Error.NO_ERROR), \\\"setting comptroller failed\\\");\\n\\n // Initialize block number and borrow index (block number mocks depend on comptroller being set)\\n accrualBlockNumber = block.number;\\n borrowIndex = mantissaOne;\\n\\n // Set the interest rate model (depends on block number / borrow index)\\n err = _setInterestRateModelFresh(interestRateModel_);\\n require(err == uint(Error.NO_ERROR), \\\"setting interest rate model failed\\\");\\n\\n name = name_;\\n symbol = symbol_;\\n decimals = decimals_;\\n\\n // The counter starts true to prevent changing it from zero to non-zero (i.e. smaller cost/refund)\\n _notEntered = true;\\n }\\n\\n /**\\n * @notice Accrue interest then return the up-to-date exchange rate\\n * @return Calculated exchange rate scaled by 1e18\\n */\\n function exchangeRateCurrent() public override nonReentrant returns (uint) {\\n require(accrueInterest() == uint(Error.NO_ERROR), \\\"accrue interest failed\\\");\\n return exchangeRateStored();\\n }\\n\\n /**\\n * @notice Applies accrued interest to total borrows and reserves\\n * @dev This calculates interest accrued from the last checkpointed block\\n * up to the current block and writes new checkpoint to storage and\\n * reduce spread reserves to protocol share reserve\\n * if currentBlock - reduceReservesBlockNumber >= blockDelta\\n */\\n // @custom:event Emits AccrueInterest event\\n function accrueInterest() public virtual override returns (uint) {\\n /* Remember the initial block number */\\n uint currentBlockNumber = block.number;\\n uint accrualBlockNumberPrior = accrualBlockNumber;\\n\\n /* Short-circuit accumulating 0 interest */\\n if (accrualBlockNumberPrior == currentBlockNumber) {\\n return uint(Error.NO_ERROR);\\n }\\n\\n /* Read the previous values out of storage */\\n uint cashPrior = _getCashPriorWithFlashLoan();\\n uint borrowsPrior = totalBorrows;\\n uint reservesPrior = totalReserves;\\n uint borrowIndexPrior = borrowIndex;\\n\\n /* Calculate the current borrow interest rate */\\n uint borrowRateMantissa = interestRateModel.getBorrowRate(cashPrior, borrowsPrior, reservesPrior);\\n require(borrowRateMantissa <= borrowRateMaxMantissa, \\\"borrow rate is absurdly high\\\");\\n\\n /* Calculate the number of blocks elapsed since the last accrual */\\n (MathError mathErr, uint blockDelta) = subUInt(currentBlockNumber, accrualBlockNumberPrior);\\n ensureNoMathError(mathErr);\\n\\n /*\\n * Calculate the interest accumulated into borrows and reserves and the new index:\\n * simpleInterestFactor = borrowRate * blockDelta\\n * interestAccumulated = simpleInterestFactor * totalBorrows\\n * totalBorrowsNew = interestAccumulated + totalBorrows\\n * totalReservesNew = interestAccumulated * reserveFactor + totalReserves\\n * borrowIndexNew = simpleInterestFactor * borrowIndex + borrowIndex\\n */\\n\\n Exp memory simpleInterestFactor;\\n uint interestAccumulated;\\n uint totalBorrowsNew;\\n uint totalReservesNew;\\n uint borrowIndexNew;\\n\\n (mathErr, simpleInterestFactor) = mulScalar(Exp({ mantissa: borrowRateMantissa }), blockDelta);\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n (mathErr, interestAccumulated) = mulScalarTruncate(simpleInterestFactor, borrowsPrior);\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n (mathErr, totalBorrowsNew) = addUInt(interestAccumulated, borrowsPrior);\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n (mathErr, totalReservesNew) = mulScalarTruncateAddUInt(\\n Exp({ mantissa: reserveFactorMantissa }),\\n interestAccumulated,\\n reservesPrior\\n );\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n (mathErr, borrowIndexNew) = mulScalarTruncateAddUInt(simpleInterestFactor, borrowIndexPrior, borrowIndexPrior);\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We write the previously calculated values into storage */\\n accrualBlockNumber = currentBlockNumber;\\n borrowIndex = borrowIndexNew;\\n totalBorrows = totalBorrowsNew;\\n totalReserves = totalReservesNew;\\n\\n (mathErr, blockDelta) = subUInt(currentBlockNumber, reduceReservesBlockNumber);\\n ensureNoMathError(mathErr);\\n if (blockDelta >= reduceReservesBlockDelta) {\\n reduceReservesBlockNumber = currentBlockNumber;\\n uint actualCash = getCashPrior();\\n if (actualCash < totalReservesNew) {\\n _reduceReservesFresh(actualCash);\\n } else {\\n _reduceReservesFresh(totalReservesNew);\\n }\\n }\\n\\n /* We emit an AccrueInterest event */\\n emit AccrueInterest(cashPrior, interestAccumulated, borrowIndexNew, totalBorrowsNew);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Sets a new comptroller for the market\\n * @dev Admin function to set a new comptroller\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits NewComptroller event\\n function _setComptroller(ComptrollerInterface newComptroller) public override returns (uint) {\\n // Check caller is admin\\n ensureAdmin(msg.sender);\\n\\n // Ensure invoke comptroller.isComptroller() returns true\\n require(newComptroller.isComptroller(), \\\"marker method returned false\\\");\\n\\n // Emit NewComptroller(oldComptroller, newComptroller)\\n emit NewComptroller(comptroller, newComptroller);\\n\\n // Set market's comptroller to newComptroller\\n comptroller = newComptroller;\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Accrues interest and updates the interest rate model using _setInterestRateModelFresh\\n * @dev Governance function to accrue interest and update the interest rate model\\n * @param newInterestRateModel_ The new interest rate model to use\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _setInterestRateModel(InterestRateModelV8 newInterestRateModel_) public override returns (uint) {\\n ensureAllowed(\\\"_setInterestRateModel(address)\\\");\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted change of interest rate model failed\\n return fail(Error(error), FailureInfo.SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // _setInterestRateModelFresh emits interest-rate-model-update-specific logs on errors, so we don't need to.\\n return _setInterestRateModelFresh(newInterestRateModel_);\\n }\\n\\n /**\\n * @notice Calculates the exchange rate from the underlying to the VToken\\n * @dev This function does not accrue interest before calculating the exchange rate\\n * @return Calculated exchange rate scaled by 1e18\\n */\\n function exchangeRateStored() public view override returns (uint) {\\n (MathError err, uint result) = exchangeRateStoredInternal();\\n ensureNoMathError(err);\\n return result;\\n }\\n\\n /**\\n * @notice Return the borrow balance of account based on stored data\\n * @param account The address whose balance should be calculated\\n * @return The calculated balance\\n */\\n function borrowBalanceStored(address account) public view override returns (uint) {\\n (MathError err, uint result) = borrowBalanceStoredInternal(account);\\n ensureNoMathError(err);\\n return result;\\n }\\n\\n /**\\n * @notice Opens a debt position for the borrower as part of flash loan repayment\\n * @dev This function is specifically called during flash loan operations when the repayment\\n * is insufficient to cover the full borrowed amount plus fees. It creates a debt position\\n * for the unpaid balance. The function checks if the borrow is allowed, accrues interest,\\n * and updates the borrower's balance. It also emits a Borrow event and calls the\\n * comptroller's borrowVerify function. It reverts if the borrow is not allowed or\\n * if the market's block number is not current.\\n * @param borrower The address of the borrower who will have the debt position created\\n * @param borrowAmount The amount of underlying asset that becomes debt (unpaid flash loan balance)\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\\n */\\n function flashLoanDebtPosition(address borrower, uint borrowAmount) external nonReentrant returns (uint256) {\\n // Reverts if the caller is not the comptroller\\n if (msg.sender != address(comptroller)) {\\n revert InvalidComptroller();\\n }\\n\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors.\\n return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // borrowFresh emits borrow-specific logs on errors, so we don't need to\\n return borrowFresh(borrower, payable(address(0)), borrowAmount, false);\\n }\\n\\n /**\\n * @notice Calculates the total fee and protocol fee for a flash loan..\\n * @param amount The amount of the flash loan.\\n * @return totalFee The total fee for the flash loan.\\n * @return protocolFee The portion of the total fee allocated to the protocol.\\n */\\n function calculateFlashLoanFee(uint256 amount) public view returns (uint256, uint256) {\\n MathError mErr;\\n uint256 totalFee;\\n uint256 protocolFee;\\n\\n (mErr, totalFee) = mulScalarTruncate(Exp({ mantissa: amount }), flashLoanFeeMantissa);\\n ensureNoMathError(mErr);\\n\\n (mErr, protocolFee) = mulScalarTruncate(Exp({ mantissa: totalFee }), flashLoanProtocolShareMantissa);\\n ensureNoMathError(mErr);\\n\\n return (totalFee, protocolFee);\\n }\\n\\n /**\\n * @notice Transfers `tokens` tokens from `src` to `dst` by `spender`\\n * @dev Called by both `transfer` and `transferFrom` internally\\n * @param spender The address of the account performing the transfer\\n * @param src The address of the source account\\n * @param dst The address of the destination account\\n * @param tokens The number of tokens to transfer\\n * @return Whether or not the transfer succeeded\\n */\\n function transferTokens(address spender, address src, address dst, uint tokens) internal returns (uint) {\\n /* Fail if transfer not allowed */\\n uint allowed = comptroller.transferAllowed(address(this), src, dst, tokens);\\n if (allowed != 0) {\\n return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.TRANSFER_COMPTROLLER_REJECTION, allowed);\\n }\\n\\n /* Do not allow self-transfers */\\n if (src == dst) {\\n return fail(Error.BAD_INPUT, FailureInfo.TRANSFER_NOT_ALLOWED);\\n }\\n\\n /* Get the allowance, infinite for the account owner */\\n uint startingAllowance = 0;\\n if (spender == src) {\\n startingAllowance = type(uint256).max;\\n } else {\\n startingAllowance = transferAllowances[src][spender];\\n }\\n\\n /* Do the calculations, checking for {under,over}flow */\\n MathError mathErr;\\n uint allowanceNew;\\n uint srvTokensNew;\\n uint dstTokensNew;\\n\\n (mathErr, allowanceNew) = subUInt(startingAllowance, tokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ALLOWED);\\n }\\n\\n (mathErr, srvTokensNew) = subUInt(accountTokens[src], tokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ENOUGH);\\n }\\n\\n (mathErr, dstTokensNew) = addUInt(accountTokens[dst], tokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_TOO_MUCH);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n accountTokens[src] = srvTokensNew;\\n accountTokens[dst] = dstTokensNew;\\n\\n /* Eat some of the allowance (if necessary) */\\n if (startingAllowance != type(uint256).max) {\\n transferAllowances[src][spender] = allowanceNew;\\n }\\n\\n /* We emit a Transfer event */\\n emit Transfer(src, dst, tokens);\\n\\n comptroller.transferVerify(address(this), src, dst, tokens);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Sender supplies assets into the market and receives vTokens in exchange\\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\\n * @param mintAmount The amount of the underlying asset to supply\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\\n */\\n function mintInternal(uint mintAmount) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted mint failed\\n return (fail(Error(error), FailureInfo.MINT_ACCRUE_INTEREST_FAILED), 0);\\n }\\n\\n // mintFresh emits the actual Mint event if successful and logs on errors, so we don't need to\\n return mintFresh(msg.sender, mintAmount);\\n }\\n\\n /**\\n * @notice User supplies assets into the market and receives vTokens in exchange\\n * @dev Assumes interest has already been accrued up to the current block\\n * @param minter The address of the account which is supplying the assets\\n * @param mintAmount The amount of the underlying asset to supply\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\\n */\\n function mintFresh(address minter, uint mintAmount) internal returns (uint, uint) {\\n /* Fail if mint not allowed */\\n uint allowed = comptroller.mintAllowed(address(this), minter, mintAmount);\\n if (allowed != 0) {\\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed), 0);\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.MINT_FRESHNESS_CHECK), 0);\\n }\\n\\n MintLocalVars memory vars;\\n\\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\\n if (vars.mathErr != MathError.NO_ERROR) {\\n return (failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)), 0);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /*\\n * We call `doTransferIn` for the minter and the mintAmount.\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * `doTransferIn` reverts if anything goes wrong, since we can't be sure if\\n * side-effects occurred. The function returns the amount actually transferred,\\n * in case of a fee. On success, the vToken holds an additional `actualMintAmount`\\n * of cash.\\n */\\n vars.actualMintAmount = doTransferIn(minter, mintAmount);\\n\\n /*\\n * We get the current exchange rate and calculate the number of vTokens to be minted:\\n * mintTokens = actualMintAmount / exchangeRate\\n */\\n\\n (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(\\n vars.actualMintAmount,\\n Exp({ mantissa: vars.exchangeRateMantissa })\\n );\\n ensureNoMathError(vars.mathErr);\\n\\n /*\\n * We calculate the new total supply of vTokens and minter token balance, checking for overflow:\\n * totalSupplyNew = totalSupply + mintTokens\\n * accountTokensNew = accountTokens[minter] + mintTokens\\n */\\n (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens);\\n ensureNoMathError(vars.mathErr);\\n (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[minter], vars.mintTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n /* We write previously calculated values into storage */\\n totalSupply = vars.totalSupplyNew;\\n accountTokens[minter] = vars.accountTokensNew;\\n\\n /* We emit a Mint event, and a Transfer event */\\n emit Mint(minter, vars.actualMintAmount, vars.mintTokens, vars.accountTokensNew);\\n emit Transfer(address(this), minter, vars.mintTokens);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.mintVerify(address(this), minter, vars.actualMintAmount, vars.mintTokens);\\n\\n return (uint(Error.NO_ERROR), vars.actualMintAmount);\\n }\\n\\n /**\\n * @notice Sender supplies assets into the market and receiver receives vTokens in exchange\\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\\n * @param receiver The address of the account which is receiving the vTokens\\n * @param mintAmount The amount of the underlying asset to supply\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\\n */\\n function mintBehalfInternal(address receiver, uint mintAmount) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted mintBehalf failed\\n return (fail(Error(error), FailureInfo.MINT_ACCRUE_INTEREST_FAILED), 0);\\n }\\n\\n // mintBehalfFresh emits the actual Mint event if successful and logs on errors, so we don't need to\\n return mintBehalfFresh(msg.sender, receiver, mintAmount);\\n }\\n\\n /**\\n * @notice Payer supplies assets into the market and receiver receives vTokens in exchange\\n * @dev Assumes interest has already been accrued up to the current block\\n * @param payer The address of the account which is paying the underlying token\\n * @param receiver The address of the account which is receiving vToken\\n * @param mintAmount The amount of the underlying asset to supply\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\\n */\\n function mintBehalfFresh(address payer, address receiver, uint mintAmount) internal returns (uint, uint) {\\n ensureNonZeroAddress(receiver);\\n /* Fail if mint not allowed */\\n uint allowed = comptroller.mintAllowed(address(this), receiver, mintAmount);\\n if (allowed != 0) {\\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed), 0);\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.MINT_FRESHNESS_CHECK), 0);\\n }\\n\\n MintLocalVars memory vars;\\n\\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\\n if (vars.mathErr != MathError.NO_ERROR) {\\n return (failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)), 0);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /*\\n * We call `doTransferIn` for the payer and the mintAmount.\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * `doTransferIn` reverts if anything goes wrong, since we can't be sure if\\n * side-effects occurred. The function returns the amount actually transferred,\\n * in case of a fee. On success, the vToken holds an additional `actualMintAmount`\\n * of cash.\\n */\\n vars.actualMintAmount = doTransferIn(payer, mintAmount);\\n\\n /*\\n * We get the current exchange rate and calculate the number of vTokens to be minted:\\n * mintTokens = actualMintAmount / exchangeRate\\n */\\n\\n (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(\\n vars.actualMintAmount,\\n Exp({ mantissa: vars.exchangeRateMantissa })\\n );\\n ensureNoMathError(vars.mathErr);\\n\\n /*\\n * We calculate the new total supply of vTokens and receiver token balance, checking for overflow:\\n * totalSupplyNew = totalSupply + mintTokens\\n * accountTokensNew = accountTokens[receiver] + mintTokens\\n */\\n (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[receiver], vars.mintTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n /* We write previously calculated values into storage */\\n totalSupply = vars.totalSupplyNew;\\n accountTokens[receiver] = vars.accountTokensNew;\\n\\n /* We emit a MintBehalf event, and a Transfer event */\\n emit MintBehalf(payer, receiver, vars.actualMintAmount, vars.mintTokens, vars.accountTokensNew);\\n emit Transfer(address(this), receiver, vars.mintTokens);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.mintVerify(address(this), receiver, vars.actualMintAmount, vars.mintTokens);\\n\\n return (uint(Error.NO_ERROR), vars.actualMintAmount);\\n }\\n\\n /**\\n * @notice Redeemer redeems vTokens in exchange for the underlying assets, transferred to the receiver. Redeemer and receiver can be the same\\n * address, or different addresses if the receiver was previously approved by the redeemer as a valid delegate (see MarketFacet.updateDelegate)\\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\\n * @param redeemer The address of the account which is redeeming the tokens\\n * @param receiver The receiver of the tokens\\n * @param redeemTokens The number of vTokens to redeem into underlying\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function redeemInternal(\\n address redeemer,\\n address payable receiver,\\n uint redeemTokens\\n ) internal nonReentrant returns (uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed\\n return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // redeemFresh emits redeem-specific logs on errors, so we don't need to\\n return redeemFresh(redeemer, receiver, redeemTokens, 0);\\n }\\n\\n /**\\n * @notice Sender redeems underlying assets on behalf of some other address. This function is only available\\n * for senders, explicitly marked as delegates of the supplier using `comptroller.updateDelegate`\\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\\n * @param redeemer The address of the account which is redeeming the tokens\\n * @param receiver The receiver of the tokens, if called by a delegate\\n * @param redeemAmount The amount of underlying to receive from redeeming vTokens\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function redeemUnderlyingInternal(\\n address redeemer,\\n address payable receiver,\\n uint redeemAmount\\n ) internal nonReentrant returns (uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed\\n return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // redeemFresh emits redeem-specific logs on errors, so we don't need to\\n return redeemFresh(redeemer, receiver, 0, redeemAmount);\\n }\\n\\n /**\\n * @notice Redeemer redeems vTokens in exchange for the underlying assets, transferred to the receiver. Redeemer and receiver can be the same\\n * address, or different addresses if the receiver was previously approved by the redeemer as a valid delegate (see MarketFacet.updateDelegate)\\n * @dev Assumes interest has already been accrued up to the current block\\n * @param redeemer The address of the account which is redeeming the tokens\\n * @param receiver The receiver of the tokens\\n * @param redeemTokensIn The number of vTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be non-zero)\\n * @param redeemAmountIn The number of underlying tokens to receive from redeeming vTokens (only one of redeemTokensIn or redeemAmountIn may be non-zero)\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // solhint-disable-next-line code-complexity\\n function redeemFresh(\\n address redeemer,\\n address payable receiver,\\n uint redeemTokensIn,\\n uint redeemAmountIn\\n ) internal returns (uint) {\\n require(redeemTokensIn == 0 || redeemAmountIn == 0, \\\"one of redeemTokensIn or redeemAmountIn must be zero\\\");\\n\\n RedeemLocalVars memory vars;\\n\\n /* exchangeRate = invoke Exchange Rate Stored() */\\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\\n ensureNoMathError(vars.mathErr);\\n\\n /* If redeemTokensIn > 0: */\\n if (redeemTokensIn > 0) {\\n /*\\n * We calculate the exchange rate and the amount of underlying to be redeemed:\\n * redeemTokens = redeemTokensIn\\n * redeemAmount = redeemTokensIn x exchangeRateCurrent\\n */\\n vars.redeemTokens = redeemTokensIn;\\n\\n (vars.mathErr, vars.redeemAmount) = mulScalarTruncate(\\n Exp({ mantissa: vars.exchangeRateMantissa }),\\n redeemTokensIn\\n );\\n ensureNoMathError(vars.mathErr);\\n } else {\\n /*\\n * We get the current exchange rate and calculate the amount to be redeemed:\\n * redeemTokens = redeemAmountIn / exchangeRate\\n * redeemAmount = redeemAmountIn\\n */\\n\\n (vars.mathErr, vars.redeemTokens) = divScalarByExpTruncate(\\n redeemAmountIn,\\n Exp({ mantissa: vars.exchangeRateMantissa })\\n );\\n ensureNoMathError(vars.mathErr);\\n\\n vars.redeemAmount = redeemAmountIn;\\n }\\n\\n /* Fail if redeem not allowed */\\n uint allowed = comptroller.redeemAllowed(address(this), redeemer, vars.redeemTokens);\\n if (allowed != 0) {\\n revert(\\\"math error\\\");\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n revert(\\\"math error\\\");\\n }\\n\\n /*\\n * We calculate the new total supply and redeemer balance, checking for underflow:\\n * totalSupplyNew = totalSupply - redeemTokens\\n * accountTokensNew = accountTokens[redeemer] - redeemTokens\\n */\\n (vars.mathErr, vars.totalSupplyNew) = subUInt(totalSupply, vars.redeemTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.accountTokensNew) = subUInt(accountTokens[redeemer], vars.redeemTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n /* Fail gracefully if protocol has insufficient cash */\\n if (getCashPrior() < vars.redeemAmount) {\\n revert(\\\"math error\\\");\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We write previously calculated values into storage */\\n totalSupply = vars.totalSupplyNew;\\n accountTokens[redeemer] = vars.accountTokensNew;\\n\\n /*\\n * We invoke doTransferOut for the receiver and the redeemAmount.\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * On success, the vToken has redeemAmount less of cash.\\n * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n */\\n\\n uint feeAmount;\\n uint remainedAmount;\\n if (IComptroller(address(comptroller)).treasuryPercent() != 0) {\\n (vars.mathErr, feeAmount) = mulUInt(\\n vars.redeemAmount,\\n IComptroller(address(comptroller)).treasuryPercent()\\n );\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, feeAmount) = divUInt(feeAmount, MANTISSA_ONE);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, remainedAmount) = subUInt(vars.redeemAmount, feeAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n address payable treasuryAddress = payable(IComptroller(address(comptroller)).treasuryAddress());\\n doTransferOut(treasuryAddress, feeAmount);\\n\\n emit RedeemFee(redeemer, feeAmount, vars.redeemTokens);\\n } else {\\n remainedAmount = vars.redeemAmount;\\n }\\n\\n doTransferOut(receiver, remainedAmount);\\n\\n /* We emit a Transfer event, and a Redeem event */\\n emit Transfer(redeemer, address(this), vars.redeemTokens);\\n emit Redeem(redeemer, remainedAmount, vars.redeemTokens, vars.accountTokensNew);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.redeemVerify(address(this), redeemer, vars.redeemAmount, vars.redeemTokens);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Receiver gets the borrow on behalf of the borrower address\\n * @param borrower The borrower, on behalf of whom to borrow\\n * @param receiver The account that would receive the funds (can be the same as the borrower)\\n * @param borrowAmount The amount of the underlying asset to borrow\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function borrowInternal(\\n address borrower,\\n address payable receiver,\\n uint borrowAmount\\n ) internal nonReentrant returns (uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\\n return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // borrowFresh emits borrow-specific logs on errors, so we don't need to\\n return borrowFresh(borrower, receiver, borrowAmount, true);\\n }\\n\\n /**\\n * @notice Receiver gets the borrow on behalf of the borrower address, controls whether to do the transfer\\n * @dev Before calling this function, ensure that the interest has been accrued\\n * @param borrower The borrower, on behalf of whom to borrow\\n * @param receiver The account that would receive the funds (can be the same as the borrower)\\n * @param borrowAmount The amount of the underlying asset to borrow\\n * @param shouldTransfer Whether to call doTransferOut for the receiver\\n * @return uint Returns 0 on success, otherwise revert (see ErrorReporter.sol for details).\\n */\\n function borrowFresh(\\n address borrower,\\n address payable receiver,\\n uint borrowAmount,\\n bool shouldTransfer\\n ) internal returns (uint) {\\n /* Revert if borrow not allowed */\\n uint allowed = comptroller.borrowAllowed(address(this), borrower, borrowAmount);\\n\\n if (allowed != 0) {\\n revert(\\\"math error\\\");\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n revert(\\\"math error\\\");\\n }\\n\\n /* Revert if protocol has insufficient underlying cash */\\n if (shouldTransfer && getCashPrior() < borrowAmount) {\\n revert(\\\"math error\\\");\\n }\\n\\n BorrowLocalVars memory vars;\\n\\n /*\\n * We calculate the new borrower and total borrow balances, failing on overflow:\\n * accountBorrowsNew = accountBorrows + borrowAmount\\n * totalBorrowsNew = totalBorrows + borrowAmount\\n */\\n (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.accountBorrowsNew) = addUInt(vars.accountBorrows, borrowAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.totalBorrowsNew) = addUInt(totalBorrows, borrowAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We write the previously calculated values into storage */\\n accountBorrows[borrower].principal = vars.accountBorrowsNew;\\n accountBorrows[borrower].interestIndex = borrowIndex;\\n totalBorrows = vars.totalBorrowsNew;\\n\\n if (shouldTransfer) {\\n /*\\n * We invoke doTransferOut for the receiver and the borrowAmount.\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * On success, the vToken borrowAmount less of cash.\\n * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n */\\n doTransferOut(receiver, borrowAmount);\\n }\\n\\n /* We emit a Borrow event */\\n emit Borrow(borrower, borrowAmount, vars.accountBorrowsNew, vars.totalBorrowsNew);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.borrowVerify(address(this), borrower, borrowAmount);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Sender repays their own borrow\\n * @param repayAmount The amount to repay\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n function repayBorrowInternal(uint repayAmount) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\\n return (fail(Error(error), FailureInfo.REPAY_BORROW_ACCRUE_INTEREST_FAILED), 0);\\n }\\n\\n // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to\\n return repayBorrowFresh(msg.sender, msg.sender, repayAmount);\\n }\\n\\n /**\\n * @notice Sender repays a borrow belonging to another borrowing account\\n * @param borrower The account with the debt being payed off\\n * @param repayAmount The amount to repay\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n function repayBorrowBehalfInternal(address borrower, uint repayAmount) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\\n return (fail(Error(error), FailureInfo.REPAY_BEHALF_ACCRUE_INTEREST_FAILED), 0);\\n }\\n\\n // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to\\n return repayBorrowFresh(msg.sender, borrower, repayAmount);\\n }\\n\\n /**\\n * @notice Borrows are repaid by another user (possibly the borrower).\\n * @param payer The account paying off the borrow\\n * @param borrower The account with the debt being payed off\\n * @param repayAmount The amount of undelrying tokens being returned\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n function repayBorrowFresh(address payer, address borrower, uint repayAmount) internal returns (uint, uint) {\\n /* Fail if repayBorrow not allowed */\\n uint allowed = comptroller.repayBorrowAllowed(address(this), payer, borrower, repayAmount);\\n if (allowed != 0) {\\n return (\\n failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REPAY_BORROW_COMPTROLLER_REJECTION, allowed),\\n 0\\n );\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.REPAY_BORROW_FRESHNESS_CHECK), 0);\\n }\\n\\n RepayBorrowLocalVars memory vars;\\n\\n /* We remember the original borrowerIndex for verification purposes */\\n vars.borrowerIndex = accountBorrows[borrower].interestIndex;\\n\\n /* We fetch the amount the borrower owes, with accumulated interest */\\n (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower);\\n if (vars.mathErr != MathError.NO_ERROR) {\\n return (\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\\n uint(vars.mathErr)\\n ),\\n 0\\n );\\n }\\n\\n // caps the repayAmount to the actual owed amount\\n vars.repayAmount = repayAmount >= vars.accountBorrows ? vars.accountBorrows : repayAmount;\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /*\\n * We call doTransferIn for the payer and the repayAmount\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * On success, the vToken holds an additional repayAmount of cash.\\n * doTransferIn reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n * it returns the amount actually transferred, in case of a fee.\\n */\\n vars.actualRepayAmount = doTransferIn(payer, vars.repayAmount);\\n\\n /*\\n * We calculate the new borrower and total borrow balances, failing on underflow:\\n * accountBorrowsNew = accountBorrows - actualRepayAmount\\n * totalBorrowsNew = totalBorrows - actualRepayAmount\\n */\\n (vars.mathErr, vars.accountBorrowsNew) = subUInt(vars.accountBorrows, vars.actualRepayAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.totalBorrowsNew) = subUInt(totalBorrows, vars.actualRepayAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n /* We write the previously calculated values into storage */\\n accountBorrows[borrower].principal = vars.accountBorrowsNew;\\n accountBorrows[borrower].interestIndex = borrowIndex;\\n totalBorrows = vars.totalBorrowsNew;\\n\\n /* We emit a RepayBorrow event */\\n emit RepayBorrow(payer, borrower, vars.actualRepayAmount, vars.accountBorrowsNew, vars.totalBorrowsNew);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.repayBorrowVerify(address(this), payer, borrower, vars.actualRepayAmount, vars.borrowerIndex);\\n\\n return (uint(Error.NO_ERROR), vars.actualRepayAmount);\\n }\\n\\n /**\\n * @notice The sender liquidates the borrowers collateral.\\n * The collateral seized is transferred to the liquidator.\\n * @param borrower The borrower of this vToken to be liquidated\\n * @param vTokenCollateral The market in which to seize collateral from the borrower\\n * @param repayAmount The amount of the underlying borrowed asset to repay\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n function liquidateBorrowInternal(\\n address borrower,\\n uint repayAmount,\\n VTokenInterface vTokenCollateral\\n ) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed\\n return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED), 0);\\n }\\n\\n error = vTokenCollateral.accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed\\n return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED), 0);\\n }\\n\\n // liquidateBorrowFresh emits borrow-specific logs on errors, so we don't need to\\n return liquidateBorrowFresh(msg.sender, borrower, repayAmount, vTokenCollateral);\\n }\\n\\n /**\\n * @notice The liquidator liquidates the borrowers collateral.\\n * The collateral seized is transferred to the liquidator.\\n * @param borrower The borrower of this vToken to be liquidated\\n * @param liquidator The address repaying the borrow and seizing collateral\\n * @param vTokenCollateral The market in which to seize collateral from the borrower\\n * @param repayAmount The amount of the underlying borrowed asset to repay\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n // solhint-disable-next-line code-complexity\\n function liquidateBorrowFresh(\\n address liquidator,\\n address borrower,\\n uint repayAmount,\\n VTokenInterface vTokenCollateral\\n ) internal returns (uint, uint) {\\n /* Fail if liquidate not allowed */\\n uint allowed = comptroller.liquidateBorrowAllowed(\\n address(this),\\n address(vTokenCollateral),\\n liquidator,\\n borrower,\\n repayAmount\\n );\\n if (allowed != 0) {\\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_COMPTROLLER_REJECTION, allowed), 0);\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_FRESHNESS_CHECK), 0);\\n }\\n\\n /* Verify vTokenCollateral market's block number equals current block number */\\n if (vTokenCollateral.accrualBlockNumber() != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_COLLATERAL_FRESHNESS_CHECK), 0);\\n }\\n\\n /* Fail if borrower = liquidator */\\n if (borrower == liquidator) {\\n return (fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_LIQUIDATOR_IS_BORROWER), 0);\\n }\\n\\n /* Fail if repayAmount = 0 */\\n if (repayAmount == 0) {\\n return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_ZERO), 0);\\n }\\n\\n /* Fail if repayAmount = type(uint256).max */\\n if (repayAmount == type(uint256).max) {\\n return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX), 0);\\n }\\n\\n /* Fail if repayBorrow fails */\\n (uint repayBorrowError, uint actualRepayAmount) = repayBorrowFresh(liquidator, borrower, repayAmount);\\n if (repayBorrowError != uint(Error.NO_ERROR)) {\\n return (fail(Error(repayBorrowError), FailureInfo.LIQUIDATE_REPAY_BORROW_FRESH_FAILED), 0);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We calculate the number of collateral tokens that will be seized */\\n (uint amountSeizeError, uint seizeTokens) = comptroller.liquidateCalculateSeizeTokens(\\n borrower,\\n address(this),\\n address(vTokenCollateral),\\n actualRepayAmount\\n );\\n\\n require(amountSeizeError == uint(Error.NO_ERROR), \\\"LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED\\\");\\n\\n /* Revert if borrower collateral token balance < seizeTokens */\\n require(vTokenCollateral.balanceOf(borrower) >= seizeTokens, \\\"LIQUIDATE_SEIZE_TOO_MUCH\\\");\\n\\n // If this is also the collateral, run seizeInternal to avoid re-entrancy, otherwise make an external call\\n uint seizeError;\\n if (address(vTokenCollateral) == address(this)) {\\n seizeError = seizeInternal(address(this), liquidator, borrower, seizeTokens);\\n } else {\\n seizeError = vTokenCollateral.seize(liquidator, borrower, seizeTokens);\\n }\\n\\n /* Revert if seize tokens fails (since we cannot be sure of side effects) */\\n require(seizeError == uint(Error.NO_ERROR), \\\"token seizure failed\\\");\\n\\n /* We emit a LiquidateBorrow event */\\n emit LiquidateBorrow(liquidator, borrower, actualRepayAmount, address(vTokenCollateral), seizeTokens);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.liquidateBorrowVerify(\\n address(this),\\n address(vTokenCollateral),\\n liquidator,\\n borrower,\\n actualRepayAmount,\\n seizeTokens\\n );\\n\\n return (uint(Error.NO_ERROR), actualRepayAmount);\\n }\\n\\n /**\\n * @notice Transfers collateral tokens (this market) to the liquidator.\\n * @dev Called only during an in-kind liquidation, or by liquidateBorrow during the liquidation of another vToken.\\n * Its absolutely critical to use msg.sender as the seizer vToken and not a parameter.\\n * @param seizerToken The contract seizing the collateral (i.e. borrowed vToken)\\n * @param liquidator The account receiving seized collateral\\n * @param borrower The account having collateral seized\\n * @param seizeTokens The number of vTokens to seize\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function seizeInternal(\\n address seizerToken,\\n address liquidator,\\n address borrower,\\n uint seizeTokens\\n ) internal returns (uint) {\\n /* Fail if seize not allowed */\\n uint allowed = comptroller.seizeAllowed(address(this), seizerToken, liquidator, borrower, seizeTokens);\\n if (allowed != 0) {\\n return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_SEIZE_COMPTROLLER_REJECTION, allowed);\\n }\\n\\n /* Fail if borrower = liquidator */\\n if (borrower == liquidator) {\\n return fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER);\\n }\\n\\n MathError mathErr;\\n uint borrowerTokensNew;\\n uint liquidatorTokensNew;\\n\\n /*\\n * We calculate the new borrower and liquidator token balances, failing on underflow/overflow:\\n * borrowerTokensNew = accountTokens[borrower] - seizeTokens\\n * liquidatorTokensNew = accountTokens[liquidator] + seizeTokens\\n */\\n (mathErr, borrowerTokensNew) = subUInt(accountTokens[borrower], seizeTokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED, uint(mathErr));\\n }\\n\\n (mathErr, liquidatorTokensNew) = addUInt(accountTokens[liquidator], seizeTokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED, uint(mathErr));\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We write the previously calculated values into storage */\\n accountTokens[borrower] = borrowerTokensNew;\\n accountTokens[liquidator] = liquidatorTokensNew;\\n\\n /* Emit a Transfer event */\\n emit Transfer(borrower, liquidator, seizeTokens);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.seizeVerify(address(this), seizerToken, liquidator, borrower, seizeTokens);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Sets a new reserve factor for the protocol (requires fresh interest accrual)\\n * @dev Governance function to set a new reserve factor\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _setReserveFactorFresh(uint newReserveFactorMantissa) internal returns (uint) {\\n // Verify market's block number equals current block number\\n if (accrualBlockNumber != block.number) {\\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_RESERVE_FACTOR_FRESH_CHECK);\\n }\\n\\n // Check newReserveFactor \\u2264 maxReserveFactor\\n if (newReserveFactorMantissa > reserveFactorMaxMantissa) {\\n return fail(Error.BAD_INPUT, FailureInfo.SET_RESERVE_FACTOR_BOUNDS_CHECK);\\n }\\n\\n uint oldReserveFactorMantissa = reserveFactorMantissa;\\n reserveFactorMantissa = newReserveFactorMantissa;\\n\\n emit NewReserveFactor(oldReserveFactorMantissa, newReserveFactorMantissa);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Accrues interest and adds reserves by transferring from `msg.sender`\\n * @param addAmount Amount of addition to reserves\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _addReservesInternal(uint addAmount) internal nonReentrant returns (uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed.\\n return fail(Error(error), FailureInfo.ADD_RESERVES_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // _addReservesFresh emits reserve-addition-specific logs on errors, so we don't need to.\\n (error, ) = _addReservesFresh(addAmount);\\n return error;\\n }\\n\\n /**\\n * @notice Add reserves by transferring from caller\\n * @dev Requires fresh interest accrual\\n * @param addAmount Amount of addition to reserves\\n * @return (uint, uint) An error code (0=success, otherwise a failure (see ErrorReporter.sol for details)) and the actual amount added, net token fees\\n */\\n function _addReservesFresh(uint addAmount) internal returns (uint, uint) {\\n // totalReserves + actualAddAmount\\n uint totalReservesNew;\\n uint actualAddAmount;\\n\\n // We fail gracefully unless market's block number equals current block number\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.ADD_RESERVES_FRESH_CHECK), actualAddAmount);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /*\\n * We call doTransferIn for the caller and the addAmount\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * On success, the vToken holds an additional addAmount of cash.\\n * doTransferIn reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n * it returns the amount actually transferred, in case of a fee.\\n */\\n\\n actualAddAmount = doTransferIn(msg.sender, addAmount);\\n\\n totalReservesNew = totalReserves + actualAddAmount;\\n\\n /* Revert on overflow */\\n require(totalReservesNew >= totalReserves, \\\"add reserves unexpected overflow\\\");\\n\\n // Store reserves[n+1] = reserves[n] + actualAddAmount\\n totalReserves = totalReservesNew;\\n\\n /* Emit NewReserves(admin, actualAddAmount, reserves[n+1]) */\\n emit ReservesAdded(msg.sender, actualAddAmount, totalReservesNew);\\n\\n /* Return (NO_ERROR, actualAddAmount) */\\n return (uint(Error.NO_ERROR), actualAddAmount);\\n }\\n\\n /**\\n * @notice Reduces reserves by transferring to protocol share reserve contract\\n * @dev Requires fresh interest accrual\\n * @param reduceAmount Amount of reduction to reserves\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _reduceReservesFresh(uint reduceAmount) internal virtual returns (uint) {\\n if (reduceAmount == 0) {\\n return uint(Error.NO_ERROR);\\n }\\n\\n // We fail gracefully unless market's block number equals current block number\\n if (accrualBlockNumber != block.number) {\\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDUCE_RESERVES_FRESH_CHECK);\\n }\\n\\n // Fail gracefully if protocol has insufficient underlying cash\\n if (getCashPrior() < reduceAmount) {\\n return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDUCE_RESERVES_CASH_NOT_AVAILABLE);\\n }\\n\\n // Check reduceAmount \\u2264 reserves[n] (totalReserves)\\n if (reduceAmount > totalReserves) {\\n return fail(Error.BAD_INPUT, FailureInfo.REDUCE_RESERVES_VALIDATION);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n // Store reserves[n+1] = reserves[n] - reduceAmount\\n totalReserves = totalReserves - reduceAmount;\\n\\n // doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n doTransferOut(protocolShareReserve, reduceAmount);\\n\\n IProtocolShareReserve(protocolShareReserve).updateAssetsState(\\n address(comptroller),\\n underlying,\\n IProtocolShareReserve.IncomeType.SPREAD\\n );\\n\\n emit ReservesReduced(protocolShareReserve, reduceAmount, totalReserves);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice updates the interest rate model (requires fresh interest accrual)\\n * @dev Governance function to update the interest rate model\\n * @param newInterestRateModel the new interest rate model to use\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _setInterestRateModelFresh(InterestRateModelV8 newInterestRateModel) internal returns (uint) {\\n // We fail gracefully unless market's block number equals current block number\\n if (accrualBlockNumber != block.number) {\\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_INTEREST_RATE_MODEL_FRESH_CHECK);\\n }\\n\\n // Ensure invoke newInterestRateModel.isInterestRateModel() returns true\\n require(newInterestRateModel.isInterestRateModel(), \\\"marker method returned false\\\");\\n\\n // Emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel)\\n emit NewMarketInterestRateModel(interestRateModel, newInterestRateModel);\\n\\n // Set the interest rate model to newInterestRateModel\\n interestRateModel = newInterestRateModel;\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /*** Safe Token ***/\\n\\n /**\\n * @dev Performs a transfer in, reverting upon failure. Returns the amount actually transferred to the protocol, in case of a fee.\\n * This may revert due to insufficient balance or insufficient allowance.\\n */\\n function doTransferIn(address from, uint amount) internal virtual returns (uint);\\n\\n /**\\n * @dev Performs a transfer out, ideally returning an explanatory error code upon failure rather than reverting.\\n * If caller has not called checked protocol's balance, may revert due to insufficient cash held in the contract.\\n * If caller has checked protocol's balance, and verified it is >= amount, this should not revert in normal conditions.\\n */\\n function doTransferOut(address payable to, uint amount) internal virtual;\\n\\n /**\\n * @notice Return the borrow balance of account based on stored data\\n * @param account The address whose balance should be calculated\\n * @return Tuple of error code and the calculated balance or 0 if error code is non-zero\\n */\\n function borrowBalanceStoredInternal(address account) internal view returns (MathError, uint) {\\n /* Note: we do not assert that the market is up to date */\\n MathError mathErr;\\n uint principalTimesIndex;\\n uint result;\\n\\n /* Get borrowBalance and borrowIndex */\\n BorrowSnapshot storage borrowSnapshot = accountBorrows[account];\\n\\n /* If borrowBalance = 0 then borrowIndex is likely also 0.\\n * Rather than failing the calculation with a division by 0, we immediately return 0 in this case.\\n */\\n if (borrowSnapshot.principal == 0) {\\n return (MathError.NO_ERROR, 0);\\n }\\n\\n /* Calculate new borrow balance using the interest index:\\n * recentBorrowBalance = borrower.borrowBalance * market.borrowIndex / borrower.borrowIndex\\n */\\n (mathErr, principalTimesIndex) = mulUInt(borrowSnapshot.principal, borrowIndex);\\n if (mathErr != MathError.NO_ERROR) {\\n return (mathErr, 0);\\n }\\n\\n (mathErr, result) = divUInt(principalTimesIndex, borrowSnapshot.interestIndex);\\n if (mathErr != MathError.NO_ERROR) {\\n return (mathErr, 0);\\n }\\n\\n return (MathError.NO_ERROR, result);\\n }\\n\\n /**\\n * @notice Calculates the exchange rate from the underlying to the vToken\\n * @dev This function does not accrue interest before calculating the exchange rate\\n * @return Tuple of error code and calculated exchange rate scaled by 1e18\\n */\\n function exchangeRateStoredInternal() internal view virtual returns (MathError, uint) {\\n uint _totalSupply = totalSupply;\\n if (_totalSupply == 0) {\\n /*\\n * If there are no tokens minted:\\n * exchangeRate = initialExchangeRate\\n */\\n return (MathError.NO_ERROR, initialExchangeRateMantissa);\\n } else {\\n /*\\n * Otherwise:\\n * exchangeRate = (totalCash + totalBorrows + flashLoanAmount - totalReserves) / totalSupply\\n */\\n uint totalCash = _getCashPriorWithFlashLoan();\\n uint cashPlusBorrowsMinusReserves;\\n Exp memory exchangeRate;\\n MathError mathErr;\\n\\n (mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(totalCash, totalBorrows, totalReserves);\\n if (mathErr != MathError.NO_ERROR) {\\n return (mathErr, 0);\\n }\\n\\n (mathErr, exchangeRate) = getExp(cashPlusBorrowsMinusReserves, _totalSupply);\\n if (mathErr != MathError.NO_ERROR) {\\n return (mathErr, 0);\\n }\\n\\n return (MathError.NO_ERROR, exchangeRate.mantissa);\\n }\\n }\\n\\n /**\\n * @notice Gets balance of this contract including active flash loans\\n * @return The quantity of underlying owned by this contract plus active flash loan amount\\n */\\n function _getCashPriorWithFlashLoan() internal view returns (uint) {\\n return getCashPrior() + flashLoanAmount;\\n }\\n\\n function ensureAllowed(string memory functionSig) private view {\\n require(\\n IAccessControlManagerV8(accessControlManager).isAllowedToCall(msg.sender, functionSig),\\n \\\"access denied\\\"\\n );\\n }\\n\\n function ensureAdmin(address caller_) private view {\\n require(caller_ == admin, \\\"Unauthorized\\\");\\n }\\n\\n function ensureNoMathError(MathError mErr) private pure {\\n require(mErr == MathError.NO_ERROR, \\\"math error\\\");\\n }\\n\\n function ensureNonZeroAddress(address address_) private pure {\\n require(address_ != address(0), \\\"zero address\\\");\\n }\\n\\n /*** Safe Token ***/\\n\\n /**\\n * @notice Gets balance of this contract in terms of the underlying\\n * @dev This excludes the value of the current message, if any\\n * @return The quantity of underlying owned by this contract\\n */\\n function getCashPrior() internal view virtual returns (uint);\\n}\\n\",\"keccak256\":\"0xa71b46dc3b3133980a99068c0e65a547317208f666196e28d38cc66a3808869b\",\"license\":\"BSD-3-Clause\"},\"contracts/Tokens/VTokens/VTokenInterfaces.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { ComptrollerInterface } from \\\"../../Comptroller/ComptrollerInterface.sol\\\";\\nimport { InterestRateModelV8 } from \\\"../../InterestRateModels/InterestRateModelV8.sol\\\";\\n\\ncontract VTokenStorageBase {\\n /**\\n * @notice Container for borrow balance information\\n * @member principal Total balance (with accrued interest), after applying the most recent balance-changing action\\n * @member interestIndex Global borrowIndex as of the most recent balance-changing action\\n */\\n struct BorrowSnapshot {\\n uint principal;\\n uint interestIndex;\\n }\\n\\n /**\\n * @dev Guard variable for re-entrancy checks\\n */\\n bool internal _notEntered;\\n\\n /**\\n * @notice EIP-20 token name for this token\\n */\\n string public name;\\n\\n /**\\n * @notice EIP-20 token symbol for this token\\n */\\n string public symbol;\\n\\n /**\\n * @notice EIP-20 token decimals for this token\\n */\\n uint8 public decimals;\\n\\n /**\\n * @notice Maximum borrow rate that can ever be applied (.0005% / block)\\n */\\n\\n uint internal constant borrowRateMaxMantissa = 0.0005e16;\\n\\n /**\\n * @notice Maximum fraction of interest that can be set aside for reserves\\n */\\n uint internal constant reserveFactorMaxMantissa = 1e18;\\n\\n /**\\n * @notice Administrator for this contract\\n */\\n address payable public admin;\\n\\n /**\\n * @notice Pending administrator for this contract\\n */\\n address payable public pendingAdmin;\\n\\n /**\\n * @notice Contract which oversees inter-vToken operations\\n */\\n ComptrollerInterface public comptroller;\\n\\n /**\\n * @notice Model which tells what the current interest rate should be\\n */\\n InterestRateModelV8 public interestRateModel;\\n\\n /**\\n * @notice Initial exchange rate used when minting the first VTokens (used when totalSupply = 0)\\n */\\n uint internal initialExchangeRateMantissa;\\n\\n /**\\n * @notice Fraction of interest currently set aside for reserves\\n */\\n uint public reserveFactorMantissa;\\n\\n /**\\n * @notice Block number that interest was last accrued at\\n */\\n uint public accrualBlockNumber;\\n\\n /**\\n * @notice Accumulator of the total earned interest rate since the opening of the market\\n */\\n uint public borrowIndex;\\n\\n /**\\n * @notice Total amount of outstanding borrows of the underlying in this market\\n */\\n uint public totalBorrows;\\n\\n /**\\n * @notice Total amount of reserves of the underlying held in this market\\n */\\n uint public totalReserves;\\n\\n /**\\n * @notice Total number of tokens in circulation\\n */\\n uint public totalSupply;\\n\\n /**\\n * @notice Official record of token balances for each account\\n */\\n mapping(address => uint) internal accountTokens;\\n\\n /**\\n * @notice Approved token transfer amounts on behalf of others\\n */\\n mapping(address => mapping(address => uint)) internal transferAllowances;\\n\\n /**\\n * @notice Mapping of account addresses to outstanding borrow balances\\n */\\n mapping(address => BorrowSnapshot) internal accountBorrows;\\n\\n /**\\n * @notice Underlying asset for this VToken\\n */\\n address public underlying;\\n\\n /**\\n * @notice Implementation address for this contract\\n */\\n address public implementation;\\n\\n /**\\n * @notice delta block after which reserves will be reduced\\n */\\n uint public reduceReservesBlockDelta;\\n\\n /**\\n * @notice last block number at which reserves were reduced\\n */\\n uint public reduceReservesBlockNumber;\\n\\n /**\\n * @notice address of protocol share reserve contract\\n */\\n address payable public protocolShareReserve;\\n\\n /**\\n * @notice address of accessControlManager\\n */\\n\\n address public accessControlManager;\\n}\\n\\ncontract VTokenStorage is VTokenStorageBase {\\n /**\\n * @notice flashLoan is enabled for this market or not\\n */\\n bool public isFlashLoanEnabled;\\n\\n /**\\n * @notice total fee percentage collected on flashLoan (scaled by 1e18)\\n */\\n uint256 public flashLoanFeeMantissa;\\n\\n /**\\n * @notice fee percentage of flashLoan that goes to protocol (scaled by 1e18)\\n */\\n uint256 public flashLoanProtocolShareMantissa;\\n\\n /**\\n * @notice Amount of flashLoan taken by the receiver\\n * @dev This is used to track the amount of flashLoan taken to correctly calculate the exchange rate\\n * during the flashLoan process. It is added to the total cash when calculating the exchange rate.\\n */\\n uint256 public flashLoanAmount;\\n\\n /**\\n * @notice Tracked internal cash balance, immune to direct token transfers (donation attacks)\\n * @dev Updated only via doTransferIn/doTransferOut. Must be initialized via sweepTokenAndSync() after upgrade.\\n */\\n uint256 public internalCash;\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[45] private __gap;\\n}\\n\\nabstract contract VTokenInterface is VTokenStorage {\\n /**\\n * @notice Indicator that this is a vToken contract (for inspection)\\n */\\n bool public constant isVToken = true;\\n\\n /*** Market Events ***/\\n\\n /**\\n * @notice Event emitted when interest is accrued\\n */\\n event AccrueInterest(uint cashPrior, uint interestAccumulated, uint borrowIndex, uint totalBorrows);\\n\\n /**\\n * @notice Event emitted when tokens are minted\\n */\\n event Mint(address minter, uint mintAmount, uint mintTokens, uint256 totalSupply);\\n\\n /**\\n * @notice Event emitted when tokens are minted behalf by payer to receiver\\n */\\n event MintBehalf(address payer, address receiver, uint mintAmount, uint mintTokens, uint256 totalSupply);\\n\\n /**\\n * @notice Event emitted when tokens are redeemed\\n */\\n event Redeem(address redeemer, uint redeemAmount, uint redeemTokens, uint256 totalSupply);\\n\\n /**\\n * @notice Event emitted when tokens are redeemed and fee is transferred\\n */\\n event RedeemFee(address redeemer, uint feeAmount, uint redeemTokens);\\n\\n /**\\n * @notice Event emitted when underlying is borrowed\\n */\\n event Borrow(address borrower, uint borrowAmount, uint accountBorrows, uint totalBorrows);\\n\\n /**\\n * @notice Event emitted when a borrow is repaid\\n */\\n event RepayBorrow(address payer, address borrower, uint repayAmount, uint accountBorrows, uint totalBorrows);\\n\\n /**\\n * @notice Event emitted when a borrow is liquidated\\n */\\n event LiquidateBorrow(\\n address liquidator,\\n address borrower,\\n uint repayAmount,\\n address vTokenCollateral,\\n uint seizeTokens\\n );\\n\\n /*** Admin Events ***/\\n\\n /**\\n * @notice Event emitted when pendingAdmin is changed\\n */\\n event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);\\n\\n /**\\n * @notice Event emitted when pendingAdmin is accepted, which means admin has been updated\\n */\\n event NewAdmin(address oldAdmin, address newAdmin);\\n\\n /**\\n * @notice Event emitted when comptroller is changed\\n */\\n event NewComptroller(ComptrollerInterface oldComptroller, ComptrollerInterface newComptroller);\\n\\n /**\\n * @notice Event emitted when interestRateModel is changed\\n */\\n event NewMarketInterestRateModel(\\n InterestRateModelV8 oldInterestRateModel,\\n InterestRateModelV8 newInterestRateModel\\n );\\n\\n /**\\n * @notice Event emitted when the reserve factor is changed\\n */\\n event NewReserveFactor(uint oldReserveFactorMantissa, uint newReserveFactorMantissa);\\n\\n /**\\n * @notice Event emitted when the reserves are added\\n */\\n event ReservesAdded(address benefactor, uint addAmount, uint newTotalReserves);\\n\\n /**\\n * @notice Event emitted when the reserves are reduced\\n */\\n event ReservesReduced(address protocolShareReserve, uint reduceAmount, uint newTotalReserves);\\n\\n /**\\n * @notice EIP20 Transfer event\\n */\\n event Transfer(address indexed from, address indexed to, uint amount);\\n\\n /**\\n * @notice EIP20 Approval event\\n */\\n event Approval(address indexed owner, address indexed spender, uint amount);\\n\\n /**\\n * @notice Event emitted when block delta for reduce reserves get updated\\n */\\n event NewReduceReservesBlockDelta(uint256 oldReduceReservesBlockDelta, uint256 newReduceReservesBlockDelta);\\n\\n /**\\n * @notice Event emitted when address of ProtocolShareReserve contract get updated\\n */\\n event NewProtocolShareReserve(address indexed oldProtocolShareReserve, address indexed newProtocolShareReserve);\\n\\n /**\\n * @notice Emitted when access control address is changed by admin\\n */\\n event NewAccessControlManager(address oldAccessControlAddress, address newAccessControlAddress);\\n\\n /**\\n * @notice Event emitted when flashLoanEnabled status is changed\\n */\\n event FlashLoanStatusChanged(bool previousStatus, bool newStatus);\\n\\n /**\\n * @notice Event emitted when asset is transferred to receiver\\n */\\n event TransferOutUnderlyingFlashLoan(address asset, address receiver, uint256 amount);\\n\\n /**\\n * @notice Event emitted when asset is transferred from sender and verified\\n */\\n event TransferInUnderlyingFlashLoan(\\n address indexed asset,\\n address indexed sender,\\n uint256 amount,\\n uint256 totalFee,\\n uint256 protocolFee\\n );\\n\\n /**\\n * @notice Event emitted when internalCash is synced with actual token balance\\n */\\n event CashSynced(uint256 oldInternalCash, uint256 newInternalCash);\\n\\n /**\\n * @notice Event emitted when excess tokens are swept by admin\\n */\\n event TokenSwept(address indexed recipient, uint256 amount);\\n\\n /**\\n * @notice Event emitted when flashLoan fee mantissa is updated\\n */\\n event FlashLoanFeeUpdated(\\n uint256 oldFlashLoanFeeMantissa,\\n uint256 newFlashLoanFeeMantissa,\\n uint256 oldFlashLoanProtocolShare,\\n uint256 newFlashLoanProtocolShare\\n );\\n\\n // @notice Thrown when comptroller is not valid\\n error InvalidComptroller();\\n\\n // @notice Thrown when there is already an active flashLoan\\n error FlashLoanAlreadyActive();\\n\\n /// @notice Thrown when flash loan fee exceeds maximum allowed\\n error FlashLoanFeeTooHigh(uint256 fee, uint256 maxFee);\\n\\n /// @notice Thrown when flash loan fee protocol share exceeds maximum allowed\\n error FlashLoanProtocolShareTooHigh(uint256 fee, uint256 maxFee);\\n\\n // @notice Thrown when the vToken does not have enough cash to lend\\n error InsufficientCash();\\n\\n /// @notice Thrown when the repayment amount is insufficient to cover the total fee\\n error InsufficientRepayment(uint256 actualAmount, uint256 requiredTotalFee);\\n\\n /*** User Interface ***/\\n\\n function transfer(address dst, uint amount) external virtual returns (bool);\\n\\n function transferFrom(address src, address dst, uint amount) external virtual returns (bool);\\n\\n function approve(address spender, uint amount) external virtual returns (bool);\\n\\n function balanceOfUnderlying(address owner) external virtual returns (uint);\\n\\n function totalBorrowsCurrent() external virtual returns (uint);\\n\\n function borrowBalanceCurrent(address account) external virtual returns (uint);\\n\\n function seize(address liquidator, address borrower, uint seizeTokens) external virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _setPendingAdmin(address payable newPendingAdmin) external virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _acceptAdmin() external virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _setReserveFactor(uint newReserveFactorMantissa) external virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _reduceReserves(uint reduceAmount) external virtual returns (uint);\\n\\n function balanceOf(address owner) external view virtual returns (uint);\\n\\n function allowance(address owner, address spender) external view virtual returns (uint);\\n\\n function getAccountSnapshot(address account) external view virtual returns (uint, uint, uint, uint);\\n\\n function borrowRatePerBlock() external view virtual returns (uint);\\n\\n function supplyRatePerBlock() external view virtual returns (uint);\\n\\n function getCash() external view virtual returns (uint);\\n\\n function exchangeRateCurrent() public virtual returns (uint);\\n\\n function accrueInterest() public virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _setComptroller(ComptrollerInterface newComptroller) public virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _setInterestRateModel(InterestRateModelV8 newInterestRateModel) public virtual returns (uint);\\n\\n function borrowBalanceStored(address account) public view virtual returns (uint);\\n\\n function exchangeRateStored() public view virtual returns (uint);\\n}\\n\\ninterface VBep20Interface {\\n /*** User Interface ***/\\n\\n function mint(uint mintAmount) external returns (uint);\\n\\n function mintBehalf(address receiver, uint mintAmount) external returns (uint);\\n\\n function redeem(uint redeemTokens) external returns (uint);\\n\\n function redeemUnderlying(uint redeemAmount) external returns (uint);\\n\\n function borrow(uint borrowAmount) external returns (uint);\\n\\n function repayBorrow(uint repayAmount) external returns (uint);\\n\\n function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint);\\n\\n function liquidateBorrow(\\n address borrower,\\n uint repayAmount,\\n VTokenInterface vTokenCollateral\\n ) external returns (uint);\\n\\n /*** Admin Functions ***/\\n\\n function _addReserves(uint addAmount) external returns (uint);\\n}\\n\\ninterface VDelegatorInterface {\\n /**\\n * @notice Emitted when implementation is changed\\n */\\n event NewImplementation(address oldImplementation, address newImplementation);\\n\\n /**\\n * @notice Called by the admin to update the implementation of the delegator\\n * @param implementation_ The address of the new implementation for delegation\\n * @param allowResign Flag to indicate whether to call _resignImplementation on the old implementation\\n * @param becomeImplementationData The encoded bytes data to be passed to _becomeImplementation\\n */\\n function _setImplementation(\\n address implementation_,\\n bool allowResign,\\n bytes memory becomeImplementationData\\n ) external;\\n}\\n\\ninterface VDelegateInterface {\\n /**\\n * @notice Called by the delegator on a delegate to initialize it for duty\\n * @dev Should revert if any issues arise which make it unfit for delegation\\n * @param data The encoded bytes data for any initialization\\n */\\n function _becomeImplementation(bytes memory data) external;\\n\\n /**\\n * @notice Called by the delegator on a delegate to forfeit its responsibility\\n */\\n function _resignImplementation() external;\\n}\\n\",\"keccak256\":\"0xa892e7d531228f2bdc92ddf37be5c43fc29a7d952335397dede149d3ac182017\",\"license\":\"BSD-3-Clause\"},\"contracts/Utils/CarefulMath.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\n/**\\n * @title Careful Math\\n * @author Venus\\n * @notice Derived from OpenZeppelin's SafeMath library\\n * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol\\n */\\ncontract CarefulMath {\\n /**\\n * @dev Possible error codes that we can return\\n */\\n enum MathError {\\n NO_ERROR,\\n DIVISION_BY_ZERO,\\n INTEGER_OVERFLOW,\\n INTEGER_UNDERFLOW\\n }\\n\\n /**\\n * @dev Multiplies two numbers, returns an error on overflow.\\n */\\n function mulUInt(uint a, uint b) internal pure returns (MathError, uint) {\\n if (a == 0) {\\n return (MathError.NO_ERROR, 0);\\n }\\n\\n uint c;\\n unchecked {\\n c = a * b;\\n }\\n\\n if (c / a != b) {\\n return (MathError.INTEGER_OVERFLOW, 0);\\n } else {\\n return (MathError.NO_ERROR, c);\\n }\\n }\\n\\n /**\\n * @dev Integer division of two numbers, truncating the quotient.\\n */\\n function divUInt(uint a, uint b) internal pure returns (MathError, uint) {\\n if (b == 0) {\\n return (MathError.DIVISION_BY_ZERO, 0);\\n }\\n\\n return (MathError.NO_ERROR, a / b);\\n }\\n\\n /**\\n * @dev Subtracts two numbers, returns an error on overflow (i.e. if subtrahend is greater than minuend).\\n */\\n function subUInt(uint a, uint b) internal pure returns (MathError, uint) {\\n if (b <= a) {\\n unchecked {\\n return (MathError.NO_ERROR, a - b);\\n }\\n } else {\\n return (MathError.INTEGER_UNDERFLOW, 0);\\n }\\n }\\n\\n /**\\n * @dev Adds two numbers, returns an error on overflow.\\n */\\n function addUInt(uint a, uint b) internal pure returns (MathError, uint) {\\n uint c;\\n unchecked {\\n c = a + b;\\n }\\n\\n if (c >= a) {\\n return (MathError.NO_ERROR, c);\\n } else {\\n return (MathError.INTEGER_OVERFLOW, 0);\\n }\\n }\\n\\n /**\\n * @dev add a and b and then subtract c\\n */\\n function addThenSubUInt(uint a, uint b, uint c) internal pure returns (MathError, uint) {\\n (MathError err0, uint sum) = addUInt(a, b);\\n\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, 0);\\n }\\n\\n return subUInt(sum, c);\\n }\\n}\\n\",\"keccak256\":\"0xb7ca049dc6f4a31c8994ad5fd2093b9f7f60c21495ff8386c7802ef13d9858a7\",\"license\":\"BSD-3-Clause\"},\"contracts/Utils/ErrorReporter.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { WeightFunction } from \\\"../Comptroller/Diamond/interfaces/IFacetBase.sol\\\";\\n\\ncontract ComptrollerErrorReporter {\\n /// @notice Thrown when You are already in the selected pool.\\n error AlreadyInSelectedPool();\\n\\n /// @notice Thrown when One or more of your assets are not compatible with the selected pool.\\n error IncompatibleBorrowedAssets();\\n\\n /// @notice Thrown when Switching to this pool would fail the liquidity check or lead to liquidation.\\n error LiquidityCheckFailed(uint256 errorCode, uint256 shortfall);\\n\\n /// @notice Thrown when trying to call pool-specific methods on the Core Pool\\n error InvalidOperationForCorePool();\\n\\n /// @notice Thrown when input array lengths do not match\\n error ArrayLengthMismatch();\\n\\n /// @notice Thrown when market trying to add in a pool is not listed in the core pool\\n error MarketNotListedInCorePool();\\n\\n /// @notice Thrown when market is not set in the _poolMarkets mapping\\n error MarketConfigNotFound();\\n\\n /// @notice Thrown when borrowing is not allowed in the selected pool for a given market.\\n error BorrowNotAllowedInPool();\\n\\n /// @notice Thrown when trying to remove a market that is not listed in the given pool.\\n error PoolMarketNotFound(uint96 poolId, address vToken);\\n\\n /// @notice Thrown when a given pool ID does not exist\\n error PoolDoesNotExist(uint96 poolId);\\n\\n /// @notice Thrown when the pool label is empty\\n error EmptyPoolLabel();\\n\\n /// @notice Thrown when a vToken is already listed in the specified pool\\n error MarketAlreadyListed(uint96 poolId, address vToken);\\n\\n /// @notice Thrown when an invalid weighting strategy is provided\\n error InvalidWeightingStrategy(WeightFunction strategy);\\n\\n // @notice Thrown when no assets are requested for flash loan\\n error NoAssetsRequested();\\n\\n // @notice Thrown when invalid flash loan parameters are provided\\n error InvalidFlashLoanParams();\\n\\n // @notice Thrown when flash loan is not enabled on the vToken\\n error FlashLoanNotEnabled();\\n\\n // @notice Thrown when the sender is not authorized to use flashloan onBehalfOf\\n error SenderNotAuthorizedForFlashLoan(address sender);\\n\\n // @notice Thrown when the onBehalfOf didn't approve the contract that receives flashloan\\n error NotAnApprovedDelegate();\\n\\n // @notice Thrown when executeOperation on the receiver contract fails\\n error ExecuteFlashLoanFailed();\\n\\n // @notice Thrown when the requested amount is zero\\n error InvalidAmount();\\n\\n // @notice Thrown when failing to create a debt position in mode 1\\n error FailedToCreateDebtPosition();\\n\\n /// @notice Thrown when attempting to interact with an inactive pool\\n error InactivePool(uint96 poolId);\\n\\n /// @notice Thrown when repayment amount is insufficient to cover the fee\\n error NotEnoughRepayment(uint256 repaid, uint256 required);\\n\\n /// @notice Thrown when the vToken market is not listed\\n error MarketNotListed(address vToken);\\n\\n /// @notice Thrown when flash loans are paused system-wide\\n error FlashLoanPausedSystemWide();\\n\\n /// @notice Thrown when too many assets are requested in a single flash loan\\n error TooManyAssetsRequested(uint256 requested, uint256 maximum);\\n\\n enum Error {\\n NO_ERROR,\\n UNAUTHORIZED,\\n COMPTROLLER_MISMATCH,\\n INSUFFICIENT_SHORTFALL,\\n INSUFFICIENT_LIQUIDITY,\\n INVALID_CLOSE_FACTOR,\\n INVALID_COLLATERAL_FACTOR,\\n INVALID_LIQUIDATION_INCENTIVE,\\n MARKET_NOT_ENTERED, // no longer possible\\n MARKET_NOT_LISTED,\\n MARKET_ALREADY_LISTED,\\n MATH_ERROR,\\n NONZERO_BORROW_BALANCE,\\n PRICE_ERROR,\\n REJECTION,\\n SNAPSHOT_ERROR,\\n TOO_MANY_ASSETS,\\n TOO_MUCH_REPAY,\\n INSUFFICIENT_BALANCE_FOR_VAI,\\n MARKET_NOT_COLLATERAL,\\n INVALID_LIQUIDATION_THRESHOLD\\n }\\n\\n enum FailureInfo {\\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\\n ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK,\\n EXIT_MARKET_BALANCE_OWED,\\n EXIT_MARKET_REJECTION,\\n SET_CLOSE_FACTOR_OWNER_CHECK,\\n SET_CLOSE_FACTOR_VALIDATION,\\n SET_COLLATERAL_FACTOR_OWNER_CHECK,\\n SET_COLLATERAL_FACTOR_NO_EXISTS,\\n SET_COLLATERAL_FACTOR_VALIDATION,\\n SET_COLLATERAL_FACTOR_WITHOUT_PRICE,\\n SET_IMPLEMENTATION_OWNER_CHECK,\\n SET_LIQUIDATION_INCENTIVE_OWNER_CHECK,\\n SET_LIQUIDATION_INCENTIVE_VALIDATION,\\n SET_MAX_ASSETS_OWNER_CHECK,\\n SET_PENDING_ADMIN_OWNER_CHECK,\\n SET_PENDING_IMPLEMENTATION_OWNER_CHECK,\\n SET_PRICE_ORACLE_OWNER_CHECK,\\n SUPPORT_MARKET_EXISTS,\\n SUPPORT_MARKET_OWNER_CHECK,\\n SET_PAUSE_GUARDIAN_OWNER_CHECK,\\n SET_VAI_MINT_RATE_CHECK,\\n SET_VAICONTROLLER_OWNER_CHECK,\\n SET_MINTED_VAI_REJECTION,\\n SET_TREASURY_OWNER_CHECK,\\n UNLIST_MARKET_NOT_LISTED,\\n SET_LIQUIDATION_THRESHOLD_VALIDATION,\\n COLLATERAL_FACTOR_GREATER_THAN_LIQUIDATION_THRESHOLD\\n }\\n\\n /**\\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\\n **/\\n event Failure(uint error, uint info, uint detail);\\n\\n /**\\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\\n */\\n function fail(Error err, FailureInfo info) internal returns (uint) {\\n emit Failure(uint(err), uint(info), 0);\\n\\n return uint(err);\\n }\\n\\n /**\\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\\n */\\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\\n emit Failure(uint(err), uint(info), opaqueError);\\n\\n return uint(err);\\n }\\n}\\n\\ncontract TokenErrorReporter {\\n enum Error {\\n NO_ERROR,\\n UNAUTHORIZED,\\n BAD_INPUT,\\n COMPTROLLER_REJECTION,\\n COMPTROLLER_CALCULATION_ERROR,\\n INTEREST_RATE_MODEL_ERROR,\\n INVALID_ACCOUNT_PAIR,\\n INVALID_CLOSE_AMOUNT_REQUESTED,\\n INVALID_COLLATERAL_FACTOR,\\n MATH_ERROR,\\n MARKET_NOT_FRESH,\\n MARKET_NOT_LISTED,\\n TOKEN_INSUFFICIENT_ALLOWANCE,\\n TOKEN_INSUFFICIENT_BALANCE,\\n TOKEN_INSUFFICIENT_CASH,\\n TOKEN_TRANSFER_IN_FAILED,\\n TOKEN_TRANSFER_OUT_FAILED,\\n TOKEN_PRICE_ERROR\\n }\\n\\n /*\\n * Note: FailureInfo (but not Error) is kept in alphabetical order\\n * This is because FailureInfo grows significantly faster, and\\n * the order of Error has some meaning, while the order of FailureInfo\\n * is entirely arbitrary.\\n */\\n enum FailureInfo {\\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\\n ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED,\\n ACCRUE_INTEREST_BORROW_RATE_CALCULATION_FAILED,\\n ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED,\\n ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED,\\n ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED,\\n ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED,\\n BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\\n BORROW_ACCRUE_INTEREST_FAILED,\\n BORROW_CASH_NOT_AVAILABLE,\\n BORROW_FRESHNESS_CHECK,\\n BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED,\\n BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED,\\n BORROW_MARKET_NOT_LISTED,\\n BORROW_COMPTROLLER_REJECTION,\\n LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED,\\n LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED,\\n LIQUIDATE_COLLATERAL_FRESHNESS_CHECK,\\n LIQUIDATE_COMPTROLLER_REJECTION,\\n LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED,\\n LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX,\\n LIQUIDATE_CLOSE_AMOUNT_IS_ZERO,\\n LIQUIDATE_FRESHNESS_CHECK,\\n LIQUIDATE_LIQUIDATOR_IS_BORROWER,\\n LIQUIDATE_REPAY_BORROW_FRESH_FAILED,\\n LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED,\\n LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED,\\n LIQUIDATE_SEIZE_COMPTROLLER_REJECTION,\\n LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER,\\n LIQUIDATE_SEIZE_TOO_MUCH,\\n MINT_ACCRUE_INTEREST_FAILED,\\n MINT_COMPTROLLER_REJECTION,\\n MINT_EXCHANGE_CALCULATION_FAILED,\\n MINT_EXCHANGE_RATE_READ_FAILED,\\n MINT_FRESHNESS_CHECK,\\n MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED,\\n MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED,\\n MINT_TRANSFER_IN_FAILED,\\n MINT_TRANSFER_IN_NOT_POSSIBLE,\\n REDEEM_ACCRUE_INTEREST_FAILED,\\n REDEEM_COMPTROLLER_REJECTION,\\n REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED,\\n REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED,\\n REDEEM_EXCHANGE_RATE_READ_FAILED,\\n REDEEM_FRESHNESS_CHECK,\\n REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED,\\n REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED,\\n REDEEM_TRANSFER_OUT_NOT_POSSIBLE,\\n REDUCE_RESERVES_ACCRUE_INTEREST_FAILED,\\n REDUCE_RESERVES_ADMIN_CHECK,\\n REDUCE_RESERVES_CASH_NOT_AVAILABLE,\\n REDUCE_RESERVES_FRESH_CHECK,\\n REDUCE_RESERVES_VALIDATION,\\n REPAY_BEHALF_ACCRUE_INTEREST_FAILED,\\n REPAY_BORROW_ACCRUE_INTEREST_FAILED,\\n REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\\n REPAY_BORROW_COMPTROLLER_REJECTION,\\n REPAY_BORROW_FRESHNESS_CHECK,\\n REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED,\\n REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED,\\n REPAY_BORROW_TRANSFER_IN_NOT_POSSIBLE,\\n SET_COLLATERAL_FACTOR_OWNER_CHECK,\\n SET_COLLATERAL_FACTOR_VALIDATION,\\n SET_COMPTROLLER_OWNER_CHECK,\\n SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED,\\n SET_INTEREST_RATE_MODEL_FRESH_CHECK,\\n SET_INTEREST_RATE_MODEL_OWNER_CHECK,\\n SET_MAX_ASSETS_OWNER_CHECK,\\n SET_ORACLE_MARKET_NOT_LISTED,\\n SET_PENDING_ADMIN_OWNER_CHECK,\\n SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED,\\n SET_RESERVE_FACTOR_ADMIN_CHECK,\\n SET_RESERVE_FACTOR_FRESH_CHECK,\\n SET_RESERVE_FACTOR_BOUNDS_CHECK,\\n TRANSFER_COMPTROLLER_REJECTION,\\n TRANSFER_NOT_ALLOWED,\\n TRANSFER_NOT_ENOUGH,\\n TRANSFER_TOO_MUCH,\\n ADD_RESERVES_ACCRUE_INTEREST_FAILED,\\n ADD_RESERVES_FRESH_CHECK,\\n ADD_RESERVES_TRANSFER_IN_NOT_POSSIBLE,\\n TOKEN_GET_UNDERLYING_PRICE_ERROR,\\n REPAY_VAI_COMPTROLLER_REJECTION,\\n REPAY_VAI_FRESHNESS_CHECK,\\n VAI_MINT_EXCHANGE_CALCULATION_FAILED,\\n SFT_MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED\\n }\\n\\n /**\\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\\n **/\\n event Failure(uint error, uint info, uint detail);\\n\\n /**\\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\\n */\\n function fail(Error err, FailureInfo info) internal returns (uint) {\\n emit Failure(uint(err), uint(info), 0);\\n\\n return uint(err);\\n }\\n\\n /**\\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\\n */\\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\\n emit Failure(uint(err), uint(info), opaqueError);\\n\\n return uint(err);\\n }\\n}\\n\\ncontract VAIControllerErrorReporter {\\n enum Error {\\n NO_ERROR,\\n UNAUTHORIZED, // The sender is not authorized to perform this action.\\n REJECTION, // The action would violate the comptroller, vaicontroller policy.\\n SNAPSHOT_ERROR, // The comptroller could not get the account borrows and exchange rate from the market.\\n PRICE_ERROR, // The comptroller could not obtain a required price of an asset.\\n MATH_ERROR, // A math calculation error occurred.\\n INSUFFICIENT_BALANCE_FOR_VAI // Caller does not have sufficient balance to mint VAI.\\n }\\n\\n enum FailureInfo {\\n SET_PENDING_ADMIN_OWNER_CHECK,\\n SET_PENDING_IMPLEMENTATION_OWNER_CHECK,\\n SET_COMPTROLLER_OWNER_CHECK,\\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\\n ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK,\\n VAI_MINT_REJECTION,\\n VAI_BURN_REJECTION,\\n VAI_LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED,\\n VAI_LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED,\\n VAI_LIQUIDATE_COLLATERAL_FRESHNESS_CHECK,\\n VAI_LIQUIDATE_COMPTROLLER_REJECTION,\\n VAI_LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED,\\n VAI_LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX,\\n VAI_LIQUIDATE_CLOSE_AMOUNT_IS_ZERO,\\n VAI_LIQUIDATE_FRESHNESS_CHECK,\\n VAI_LIQUIDATE_LIQUIDATOR_IS_BORROWER,\\n VAI_LIQUIDATE_REPAY_BORROW_FRESH_FAILED,\\n VAI_LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED,\\n VAI_LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED,\\n VAI_LIQUIDATE_SEIZE_COMPTROLLER_REJECTION,\\n VAI_LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER,\\n VAI_LIQUIDATE_SEIZE_TOO_MUCH,\\n MINT_FEE_CALCULATION_FAILED,\\n SET_TREASURY_OWNER_CHECK\\n }\\n\\n /**\\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\\n **/\\n event Failure(uint error, uint info, uint detail);\\n\\n /**\\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\\n */\\n function fail(Error err, FailureInfo info) internal returns (uint) {\\n emit Failure(uint(err), uint(info), 0);\\n\\n return uint(err);\\n }\\n\\n /**\\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\\n */\\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\\n emit Failure(uint(err), uint(info), opaqueError);\\n\\n return uint(err);\\n }\\n}\\n\",\"keccak256\":\"0x9b498db9a2d5f5178b48e5724849f7561c1c0100c1faef4d6dbd741f599861f2\",\"license\":\"BSD-3-Clause\"},\"contracts/Utils/Exponential.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { CarefulMath } from \\\"./CarefulMath.sol\\\";\\nimport { ExponentialNoError } from \\\"./ExponentialNoError.sol\\\";\\n\\n/**\\n * @title Exponential module for storing fixed-precision decimals\\n * @author Venus\\n * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places.\\n * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is:\\n * `Exp({mantissa: 5100000000000000000})`.\\n */\\ncontract Exponential is CarefulMath, ExponentialNoError {\\n /**\\n * @dev Creates an exponential from numerator and denominator values.\\n * Note: Returns an error if (`num` * 10e18) > MAX_INT,\\n * or if `denom` is zero.\\n */\\n function getExp(uint num, uint denom) internal pure returns (MathError, Exp memory) {\\n (MathError err0, uint scaledNumerator) = mulUInt(num, expScale);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n\\n (MathError err1, uint rational) = divUInt(scaledNumerator, denom);\\n if (err1 != MathError.NO_ERROR) {\\n return (err1, Exp({ mantissa: 0 }));\\n }\\n\\n return (MathError.NO_ERROR, Exp({ mantissa: rational }));\\n }\\n\\n /**\\n * @dev Adds two exponentials, returning a new exponential.\\n */\\n function addExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\\n (MathError error, uint result) = addUInt(a.mantissa, b.mantissa);\\n\\n return (error, Exp({ mantissa: result }));\\n }\\n\\n /**\\n * @dev Subtracts two exponentials, returning a new exponential.\\n */\\n function subExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\\n (MathError error, uint result) = subUInt(a.mantissa, b.mantissa);\\n\\n return (error, Exp({ mantissa: result }));\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, returning a new Exp.\\n */\\n function mulScalar(Exp memory a, uint scalar) internal pure returns (MathError, Exp memory) {\\n (MathError err0, uint scaledMantissa) = mulUInt(a.mantissa, scalar);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n\\n return (MathError.NO_ERROR, Exp({ mantissa: scaledMantissa }));\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer.\\n */\\n function mulScalarTruncate(Exp memory a, uint scalar) internal pure returns (MathError, uint) {\\n (MathError err, Exp memory product) = mulScalar(a, scalar);\\n if (err != MathError.NO_ERROR) {\\n return (err, 0);\\n }\\n\\n return (MathError.NO_ERROR, truncate(product));\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer.\\n */\\n function mulScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) internal pure returns (MathError, uint) {\\n (MathError err, Exp memory product) = mulScalar(a, scalar);\\n if (err != MathError.NO_ERROR) {\\n return (err, 0);\\n }\\n\\n return addUInt(truncate(product), addend);\\n }\\n\\n /**\\n * @dev Divide an Exp by a scalar, returning a new Exp.\\n */\\n function divScalar(Exp memory a, uint scalar) internal pure returns (MathError, Exp memory) {\\n (MathError err0, uint descaledMantissa) = divUInt(a.mantissa, scalar);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n\\n return (MathError.NO_ERROR, Exp({ mantissa: descaledMantissa }));\\n }\\n\\n /**\\n * @dev Divide a scalar by an Exp, returning a new Exp.\\n */\\n function divScalarByExp(uint scalar, Exp memory divisor) internal pure returns (MathError, Exp memory) {\\n /*\\n We are doing this as:\\n getExp(mulUInt(expScale, scalar), divisor.mantissa)\\n\\n How it works:\\n Exp = a / b;\\n Scalar = s;\\n `s / (a / b)` = `b * s / a` and since for an Exp `a = mantissa, b = expScale`\\n */\\n (MathError err0, uint numerator) = mulUInt(expScale, scalar);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n return getExp(numerator, divisor.mantissa);\\n }\\n\\n /**\\n * @dev Divide a scalar by an Exp, then truncate to return an unsigned integer.\\n */\\n function divScalarByExpTruncate(uint scalar, Exp memory divisor) internal pure returns (MathError, uint) {\\n (MathError err, Exp memory fraction) = divScalarByExp(scalar, divisor);\\n if (err != MathError.NO_ERROR) {\\n return (err, 0);\\n }\\n\\n return (MathError.NO_ERROR, truncate(fraction));\\n }\\n\\n /**\\n * @dev Multiplies two exponentials, returning a new exponential.\\n */\\n function mulExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\\n (MathError err0, uint doubleScaledProduct) = mulUInt(a.mantissa, b.mantissa);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n\\n // We add half the scale before dividing so that we get rounding instead of truncation.\\n // See \\\"Listing 6\\\" and text above it at https://accu.org/index.php/journals/1717\\n // Without this change, a result like 6.6...e-19 will be truncated to 0 instead of being rounded to 1e-18.\\n (MathError err1, uint doubleScaledProductWithHalfScale) = addUInt(halfExpScale, doubleScaledProduct);\\n if (err1 != MathError.NO_ERROR) {\\n return (err1, Exp({ mantissa: 0 }));\\n }\\n\\n (MathError err2, uint product) = divUInt(doubleScaledProductWithHalfScale, expScale);\\n // The only error `div` can return is MathError.DIVISION_BY_ZERO but we control `expScale` and it is not zero.\\n assert(err2 == MathError.NO_ERROR);\\n\\n return (MathError.NO_ERROR, Exp({ mantissa: product }));\\n }\\n\\n /**\\n * @dev Multiplies two exponentials given their mantissas, returning a new exponential.\\n */\\n function mulExp(uint a, uint b) internal pure returns (MathError, Exp memory) {\\n return mulExp(Exp({ mantissa: a }), Exp({ mantissa: b }));\\n }\\n\\n /**\\n * @dev Multiplies three exponentials, returning a new exponential.\\n */\\n function mulExp3(Exp memory a, Exp memory b, Exp memory c) internal pure returns (MathError, Exp memory) {\\n (MathError err, Exp memory ab) = mulExp(a, b);\\n if (err != MathError.NO_ERROR) {\\n return (err, ab);\\n }\\n return mulExp(ab, c);\\n }\\n\\n /**\\n * @dev Divides two exponentials, returning a new exponential.\\n * (a/scale) / (b/scale) = (a/scale) * (scale/b) = a/b,\\n * which we can scale as an Exp by calling getExp(a.mantissa, b.mantissa)\\n */\\n function divExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\\n return getExp(a.mantissa, b.mantissa);\\n }\\n}\\n\",\"keccak256\":\"0x6b9b293a09a3ec69ac16f58dce449a553b44121eb9aad666a777d8f6f4ae83f0\",\"license\":\"BSD-3-Clause\"},\"contracts/Utils/ExponentialNoError.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\n/**\\n * @title Exponential module for storing fixed-precision decimals\\n * @author Compound\\n * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places.\\n * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is:\\n * `Exp({mantissa: 5100000000000000000})`.\\n */\\ncontract ExponentialNoError {\\n uint internal constant expScale = 1e18;\\n uint internal constant doubleScale = 1e36;\\n uint internal constant halfExpScale = expScale / 2;\\n uint internal constant mantissaOne = expScale;\\n\\n struct Exp {\\n uint mantissa;\\n }\\n\\n struct Double {\\n uint mantissa;\\n }\\n\\n /**\\n * @dev Truncates the given exp to a whole number value.\\n * For example, truncate(Exp{mantissa: 15 * expScale}) = 15\\n */\\n function truncate(Exp memory exp) internal pure returns (uint) {\\n // Note: We are not using careful math here as we're performing a division that cannot fail\\n return exp.mantissa / expScale;\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer.\\n */\\n function mul_ScalarTruncate(Exp memory a, uint scalar) internal pure returns (uint) {\\n Exp memory product = mul_(a, scalar);\\n return truncate(product);\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer.\\n */\\n function mul_ScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) internal pure returns (uint) {\\n Exp memory product = mul_(a, scalar);\\n return add_(truncate(product), addend);\\n }\\n\\n /**\\n * @dev Checks if first Exp is less than second Exp.\\n */\\n function lessThanExp(Exp memory left, Exp memory right) internal pure returns (bool) {\\n return left.mantissa < right.mantissa;\\n }\\n\\n /**\\n * @dev Checks if left Exp <= right Exp.\\n */\\n function lessThanOrEqualExp(Exp memory left, Exp memory right) internal pure returns (bool) {\\n return left.mantissa <= right.mantissa;\\n }\\n\\n /**\\n * @dev Checks if left Exp > right Exp.\\n */\\n function greaterThanExp(Exp memory left, Exp memory right) internal pure returns (bool) {\\n return left.mantissa > right.mantissa;\\n }\\n\\n /**\\n * @dev returns true if Exp is exactly zero\\n */\\n function isZeroExp(Exp memory value) internal pure returns (bool) {\\n return value.mantissa == 0;\\n }\\n\\n function safe224(uint n, string memory errorMessage) internal pure returns (uint224) {\\n require(n < 2 ** 224, errorMessage);\\n return uint224(n);\\n }\\n\\n function safe32(uint n, string memory errorMessage) internal pure returns (uint32) {\\n require(n < 2 ** 32, errorMessage);\\n return uint32(n);\\n }\\n\\n function add_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: add_(a.mantissa, b.mantissa) });\\n }\\n\\n function add_(Double memory a, Double memory b) internal pure returns (Double memory) {\\n return Double({ mantissa: add_(a.mantissa, b.mantissa) });\\n }\\n\\n function add_(uint a, uint b) internal pure returns (uint) {\\n return add_(a, b, \\\"addition overflow\\\");\\n }\\n\\n function add_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\\n uint c = a + b;\\n require(c >= a, errorMessage);\\n return c;\\n }\\n\\n function sub_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: sub_(a.mantissa, b.mantissa) });\\n }\\n\\n function sub_(Double memory a, Double memory b) internal pure returns (Double memory) {\\n return Double({ mantissa: sub_(a.mantissa, b.mantissa) });\\n }\\n\\n function sub_(uint a, uint b) internal pure returns (uint) {\\n return sub_(a, b, \\\"subtraction underflow\\\");\\n }\\n\\n function sub_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\\n require(b <= a, errorMessage);\\n return a - b;\\n }\\n\\n function mul_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: mul_(a.mantissa, b.mantissa) / expScale });\\n }\\n\\n function mul_(Exp memory a, uint b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: mul_(a.mantissa, b) });\\n }\\n\\n function mul_(uint a, Exp memory b) internal pure returns (uint) {\\n return mul_(a, b.mantissa) / expScale;\\n }\\n\\n function mul_(Double memory a, Double memory b) internal pure returns (Double memory) {\\n return Double({ mantissa: mul_(a.mantissa, b.mantissa) / doubleScale });\\n }\\n\\n function mul_(Double memory a, uint b) internal pure returns (Double memory) {\\n return Double({ mantissa: mul_(a.mantissa, b) });\\n }\\n\\n function mul_(uint a, Double memory b) internal pure returns (uint) {\\n return mul_(a, b.mantissa) / doubleScale;\\n }\\n\\n function mul_(uint a, uint b) internal pure returns (uint) {\\n return mul_(a, b, \\\"multiplication overflow\\\");\\n }\\n\\n function mul_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\\n if (a == 0 || b == 0) {\\n return 0;\\n }\\n uint c = a * b;\\n require(c / a == b, errorMessage);\\n return c;\\n }\\n\\n function div_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: div_(mul_(a.mantissa, expScale), b.mantissa) });\\n }\\n\\n function div_(Exp memory a, uint b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: div_(a.mantissa, b) });\\n }\\n\\n function div_(uint a, Exp memory b) internal pure returns (uint) {\\n return div_(mul_(a, expScale), b.mantissa);\\n }\\n\\n function div_(Double memory a, Double memory b) internal pure returns (Double memory) {\\n return Double({ mantissa: div_(mul_(a.mantissa, doubleScale), b.mantissa) });\\n }\\n\\n function div_(Double memory a, uint b) internal pure returns (Double memory) {\\n return Double({ mantissa: div_(a.mantissa, b) });\\n }\\n\\n function div_(uint a, Double memory b) internal pure returns (uint) {\\n return div_(mul_(a, doubleScale), b.mantissa);\\n }\\n\\n function div_(uint a, uint b) internal pure returns (uint) {\\n return div_(a, b, \\\"divide by zero\\\");\\n }\\n\\n function div_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\\n require(b > 0, errorMessage);\\n return a / b;\\n }\\n\\n function fraction(uint a, uint b) internal pure returns (Double memory) {\\n return Double({ mantissa: div_(mul_(a, doubleScale), b) });\\n }\\n}\\n\",\"keccak256\":\"0xce75802a56763fb96b2046b374127f080a425cfe3634670767ec040ccb6ea7e0\",\"license\":\"BSD-3-Clause\"},\"contracts/external/IProtocolShareReserve.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\ninterface IProtocolShareReserve {\\n enum IncomeType {\\n SPREAD,\\n LIQUIDATION,\\n ERC4626_WRAPPER_REWARDS,\\n FLASHLOAN\\n }\\n\\n function updateAssetsState(address comptroller, address asset, IncomeType incomeType) external;\\n}\\n\",\"keccak256\":\"0x029861e068e9c4d802650eaf6dd8cfed0980d2ce9a45e6fcc1e1e628ecc1f523\",\"license\":\"BSD-3-Clause\"},\"contracts/external/IWBNB.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { IERC20Upgradeable } from \\\"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\\\";\\n\\ninterface IWBNB is IERC20Upgradeable {\\n function deposit() external payable;\\n\\n function withdraw(uint256 amount) external;\\n}\\n\",\"keccak256\":\"0x2602fa3bfe075d8ab4608464f02154788a09d0848b3b6e656b0712b176989193\",\"license\":\"BSD-3-Clause\"}},\"version\":1}", + "bytecode": "0x610100604052348015610010575f80fd5b50604051612b90380380612b9083398101604081905261002f9161017d565b61003884610083565b61004183610083565b61004a82610083565b61005381610083565b6001600160a01b0380851660805283811660a05282811660c052811660e05261007a6100ad565b505050506101d9565b6001600160a01b0381166100aa576040516342bcdf7f60e11b815260040160405180910390fd5b50565b5f54610100900460ff16156101185760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b5f5460ff90811614610167575f805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b6001600160a01b03811681146100aa575f80fd5b5f805f8060808587031215610190575f80fd5b845161019b81610169565b60208601519094506101ac81610169565b60408601519093506101bd81610169565b60608601519092506101ce81610169565b939692955090935050565b60805160a05160c05160e0516129306102605f395f81816102b801528181610fb7015281816112db015261148301525f818161012f01528181610adf0152610d6601525f818161017f01528181610a8a01528181610d1f01528181610f1101526111fb01525f81816101d301528181610b7201528181610c9b01526113d801526129305ff3fe608060405260043610610113575f3560e01c80638d72647e1161009d578063e30c397811610062578063e30c397814610354578063e58b51e914610371578063f2fde38b14610390578063f3d7d282146103af578063fc08f9f6146103dd575f80fd5b80638d72647e146102a75780638da5cb5b146102da5780639646f3ea146102f7578063c3c6467414610316578063c4d66de814610335575f80fd5b806360d6acc3116100e357806360d6acc3146101f557806362c06767146102225780636d70f7ae14610241578063715018a61461027f57806379ba509714610293575f80fd5b806314ba4a101461011e57806333e1567f1461016e578063558a7297146101a15780635fe3b567146101c2575f80fd5b3661011a57005b5f80fd5b348015610129575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b348015610179575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b3480156101ac575f80fd5b506101c06101bb3660046120e1565b61040a565b005b3480156101cd575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b348015610200575f80fd5b5061021461020f366004612118565b61047a565b604051908152602001610165565b34801561022d575f80fd5b506101c061023c366004612157565b610630565b34801561024c575f80fd5b5061026f61025b366004612195565b60c96020525f908152604090205460ff1681565b6040519015158152602001610165565b34801561028a575f80fd5b506101c06106b0565b34801561029e575f80fd5b506101c06106c3565b3480156102b2575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b3480156102e5575f80fd5b506033546001600160a01b0316610151565b348015610302575f80fd5b506101c06103113660046121b0565b61073d565b348015610321575f80fd5b506101c06103303660046120e1565b610806565b348015610340575f80fd5b506101c061034f366004612195565b61086e565b34801561035f575f80fd5b506065546001600160a01b0316610151565b34801561037c575f80fd5b506101c061038b366004612118565b610994565b34801561039b575f80fd5b506101c06103aa366004612195565b610c1c565b3480156103ba575f80fd5b5061026f6103c9366004612195565b60ca6020525f908152604090205460ff1681565b3480156103e8575f80fd5b506103fc6103f7366004612260565b610c8d565b60405161016592919061237b565b610412611080565b61041b826110da565b6001600160a01b0382165f81815260c96020908152604091829020805460ff191685151590811790915591519182527f1a594081ae893ab78e67d9b9e843547318164322d32c65369d78a96172d9dc8f91015b60405180910390a25050565b5f61048d6033546001600160a01b031690565b6001600160a01b0316336001600160a01b0316141580156104bd5750335f90815260c9602052604090205460ff16155b156104db57604051631f0853c160e21b815260040160405180910390fd5b6104e3611101565b61050c6104f660a0840160808501612195565b610507610100850160e08601612195565b61115a565b8160c001355f0361053057604051630a1c302560e21b815260040160405180910390fd5b816101400135421115610568576040516302a07ebf60e31b815261014083013560048201524260248201526044015b60405180910390fd5b5f61057a61057584612552565b6111f6565b909250905061058f6040840160208501612195565b6001600160a01b03166105a86060850160408601612195565b6001600160a01b03166105be6020860186612195565b6001600160a01b03167fbfd65b4425967651bb217826b6511e83ef29c5c5ad92f273bb22e7507ed16afb866060013585875f6040516106189493929190938452602084019290925260408301521515606082015260800190565b60405180910390a45061062b6001609755565b919050565b610638611080565b610641836110da565b61064a826110da565b61065e6001600160a01b0384168383611829565b816001600160a01b0316836001600160a01b03167f7b09c29f9106defeccc9ac3b823f3aad0b470d120e5df7aed033b5c43a4bf718836040516106a391815260200190565b60405180910390a3505050565b6106b8611080565b6106c15f611891565b565b60655433906001600160a01b031681146107315760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b606482015260840161055f565b61073a81611891565b50565b610745611080565b61074e826110da565b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f8114610797576040519150601f19603f3d011682016040523d82523d5f602084013e61079c565b606091505b50509050806107be57604051633d2cec6f60e21b815260040160405180910390fd5b826001600160a01b03167f1aedae30ba52b882825adc6b3ed1febc9a717c20eaca0e2c50fd510e4bdc4cd6836040516107f991815260200190565b60405180910390a2505050565b61080e611080565b610817826110da565b6001600160a01b0382165f81815260ca6020908152604091829020805460ff191685151590811790915591519182527f8443a265af33599abe6c9ed0c3cabd8e2a6c5b8ea35c1d1ed40c513242f95d45910161046e565b5f54610100900460ff161580801561088c57505f54600160ff909116105b806108a55750303b1580156108a557505f5460ff166001145b6109085760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161055f565b5f805460ff191660011790558015610929575f805461ff0019166101001790555b610932826110da565b61093a6118aa565b6109426118d8565b61094b82611891565b8015610990575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6033546001600160a01b031633148015906109be5750335f90815260c9602052604090205460ff16155b156109dc57604051631f0853c160e21b815260040160405180910390fd5b6109e4611101565b610a086109f760a0830160808401612195565b610507610100840160e08501612195565b8060c001355f03610a2c57604051630a1c302560e21b815260040160405180910390fd5b806101400135421115610a5f576040516302a07ebf60e31b8152610140820135600482015242602482015260440161055f565b6040805160018082528183019092525f91602080830190803683370190505090506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016610aba6040840160208501612195565b6001600160a01b031614610add57610ad86040830160208401612195565b610aff565b7f00000000000000000000000000000000000000000000000000000000000000005b815f81518110610b1157610b1161255d565b6001600160a01b0392909216602092830291909101909101526040805160018082528183019092525f918160200160208202803683370190505090508260600135815f81518110610b6457610b6461255d565b6020026020010181815250507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316635544ed9c3030858588604051602001610bb491906125db565b6040516020818303038152906040526040518663ffffffff1660e01b8152600401610be3959493929190612743565b5f604051808303815f87803b158015610bfa575f80fd5b505af1158015610c0c573d5f803e3d5ffd5b50505050505061073a6001609755565b610c24611080565b606580546001600160a01b0383166001600160a01b03199091168117909155610c556033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b5f6060336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610cd95760405163880ed0f360e01b815260040160405180910390fd5b6001600160a01b0386163014610d0d576040516322c7df1960e21b81526001600160a01b038716600482015260240161055f565b5f610d1a848601866127ce565b90505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031682602001516001600160a01b031614610d64578160200151610d86565b7f00000000000000000000000000000000000000000000000000000000000000005b9050806001600160a01b03168e8e5f818110610da457610da461255d565b9050602002016020810190610db99190612195565b6001600160a01b031614610de05760405163e60249cb60e01b815260040160405180910390fd5b505f80610dec836111f6565b604080516001808252818301909252929450909250602080830190803683370190505093508a8a5f818110610e2357610e2361255d565b905060200201358d8d5f818110610e3c57610e3c61255d565b90506020020135610e4d9190612814565b845f81518110610e5f57610e5f61255d565b602002602001018181525050835f81518110610e7d57610e7d61255d565b6020026020010151821015610ecb5781845f81518110610e9f57610e9f61255d565b602002602001015160405163f447a23960e01b815260040161055f929190918252602082015260400190565b610fe78f8f5f818110610ee057610ee061255d565b9050602002016020810190610ef59190612195565b855f81518110610f0757610f0761255d565b60200260200101517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031686602001516001600160a01b031614610fb55785602001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f8c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fb09190612827565b610fd7565b7f00000000000000000000000000000000000000000000000000000000000000005b6001600160a01b03169190611906565b82602001516001600160a01b031683604001516001600160a01b0316845f01516001600160a01b03167fbfd65b4425967651bb217826b6511e83ef29c5c5ad92f273bb22e7507ed16afb8660600151858760016040516110629493929190938452602084019290925260408301521515606082015260800190565b60405180910390a4600194505050509a509a98505050505050505050565b6033546001600160a01b031633146106c15760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161055f565b6001600160a01b03811661073a576040516342bcdf7f60e11b815260040160405180910390fd5b6002609754036111535760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015260640161055f565b6002609755565b6001600160a01b0382165f90815260ca602052604090205460ff1661119d5760405163c665406f60e01b81526001600160a01b038316600482015260240161055f565b6001600160a01b038116158015906111cd57506001600160a01b0381165f90815260ca602052604090205460ff16155b156109905760405163c665406f60e01b81526001600160a01b038216600482015260240161055f565b5f805f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031684602001516001600160a01b031614905080801561124c575060e08401516001600160a01b0316155b1561126a57604051631a7a563960e21b815260040160405180910390fd5b5f816112d95784602001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112b0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112d49190612827565b6112fb565b7f00000000000000000000000000000000000000000000000000000000000000005b90505f85604001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa15801561133e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113629190612827565b60408088015190516370a0823160e01b81523060048201529192505f916001600160a01b03909116906370a0823190602401602060405180830381865afa1580156113af573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113d39190612842565b90505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316639bb27d626040518163ffffffff1660e01b8152600401602060405180830381865afa158015611432573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114569190612827565b9050611461816110da565b8415611566576060880151604051632e1a7d4d60e01b815260048101919091527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690632e1a7d4d906024015f604051808303815f87803b1580156114cc575f80fd5b505af11580156114de573d5f803e3d5ffd5b505050506060880151602089015189516040808c01519051630c9fae0f60e31b81526001600160a01b03938416600482015291831660248301526044820184905282166064820152908316916364fd7078916084015f604051808303818588803b15801561154a575f80fd5b505af115801561155c573d5f803e3d5ffd5b5050505050611615565b6060880151611581906001600160a01b038616908390611906565b6020880151885160608a01516040808c01519051630c9fae0f60e31b81526001600160a01b0394851660048201529284166024840152604483019190915282166064820152908216906364fd7078906084015f604051808303815f87803b1580156115ea575f80fd5b505af11580156115fc573d5f803e3d5ffd5b50611615925050506001600160a01b038516825f611906565b60408089015190516370a0823160e01b81523060048201525f9184916001600160a01b03909116906370a0823190602401602060405180830381865afa158015611661573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116859190612842565b61168f9190612859565b6040516370a0823160e01b81523060048201529091505f906001600160a01b038616906370a0823190602401602060405180830381865afa1580156116d6573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116fa9190612842565b90505f8a604001516001600160a01b031663db006a75846040518263ffffffff1660e01b815260040161172f91815260200190565b6020604051808303815f875af115801561174b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061176f9190612842565b905080156117935760405163eeddaac560e01b81526004810182905260240161055f565b6040516370a0823160e01b815230600482015282906001600160a01b038816906370a0823190602401602060405180830381865afa1580156117d7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117fb9190612842565b6118059190612859565b985061181387878b8e61199a565b99505050505050505050915091565b6001609755565b6040516001600160a01b03831660248201526044810182905261188c90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152611c67565b505050565b606580546001600160a01b031916905561073a81611d3a565b5f54610100900460ff166118d05760405162461bcd60e51b815260040161055f9061286c565b6106c1611d8b565b5f54610100900460ff166118fe5760405162461bcd60e51b815260040161055f9061286c565b6106c1611dba565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b1790526119578482611de0565b611994576040516001600160a01b03841660248201525f604482015261198a90859063095ea7b360e01b90606401611855565b6119948482611c67565b50505050565b6040516370a0823160e01b81523060048201525f9081906001600160a01b038716906370a0823190602401602060405180830381865afa1580156119e0573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a049190612842565b60e08401519091506001600160a01b0316611a3257611a2d8584608001518560a0015187611e83565b611bb5565b6101208301516001600160a01b03161580611a635750856001600160a01b03168361012001516001600160a01b0316145b80611a845750846001600160a01b03168361012001516001600160a01b0316145b15611aa257604051631a7a563960e21b815260040160405180910390fd5b6101208301516040516370a0823160e01b81523060048201525f906001600160a01b038316906370a0823190602401602060405180830381865afa158015611aec573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b109190612842565b9050611b268786608001518760a0015189611e83565b6040516370a0823160e01b81523060048201525f9082906001600160a01b038516906370a0823190602401602060405180830381865afa158015611b6c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b909190612842565b611b9a9190612859565b9050611bb1838760e0015188610100015184611e83565b5050505b6040516370a0823160e01b815230600482015281906001600160a01b038816906370a0823190602401602060405180830381865afa158015611bf9573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c1d9190612842565b611c279190612859565b91508260c00151821015611c5e5760c083015160405163f447a23960e01b815261055f918491600401918252602082015260400190565b50949350505050565b5f611cbb826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316611f2b9092919063ffffffff16565b905080515f1480611cdb575080806020019051810190611cdb91906128b7565b61188c5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161055f565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f54610100900460ff16611db15760405162461bcd60e51b815260040161055f9061286c565b6106c133611891565b5f54610100900460ff166118225760405162461bcd60e51b815260040161055f9061286c565b5f805f846001600160a01b031684604051611dfb91906128d2565b5f604051808303815f865af19150503d805f8114611e34576040519150601f19603f3d011682016040523d82523d5f602084013e611e39565b606091505b5091509150818015611e63575080511580611e63575080806020019051810190611e6391906128b7565b8015611e7857506001600160a01b0385163b15155b925050505b92915050565b611e976001600160a01b0385168483611906565b5f836001600160a01b031683604051611eb091906128d2565b5f604051808303815f865af19150503d805f8114611ee9576040519150601f19603f3d011682016040523d82523d5f602084013e611eee565b606091505b5050905080611f105760405163081ceff360e41b815260040160405180910390fd5b611f246001600160a01b038616855f611906565b5050505050565b6060611f3984845f85611f41565b949350505050565b606082471015611fa25760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840161055f565b5f80866001600160a01b03168587604051611fbd91906128d2565b5f6040518083038185875af1925050503d805f8114611ff7576040519150601f19603f3d011682016040523d82523d5f602084013e611ffc565b606091505b509150915061200d87838387612018565b979650505050505050565b606083156120865782515f0361207f576001600160a01b0385163b61207f5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161055f565b5081611f39565b611f39838381511561209b5781518083602001fd5b8060405162461bcd60e51b815260040161055f91906128e8565b6001600160a01b038116811461073a575f80fd5b803561062b816120b5565b801515811461073a575f80fd5b5f80604083850312156120f2575f80fd5b82356120fd816120b5565b9150602083013561210d816120d4565b809150509250929050565b5f60208284031215612128575f80fd5b813567ffffffffffffffff81111561213e575f80fd5b82016101608185031215612150575f80fd5b9392505050565b5f805f60608486031215612169575f80fd5b8335612174816120b5565b92506020840135612184816120b5565b929592945050506040919091013590565b5f602082840312156121a5575f80fd5b8135612150816120b5565b5f80604083850312156121c1575f80fd5b82356121cc816120b5565b946020939093013593505050565b5f8083601f8401126121ea575f80fd5b50813567ffffffffffffffff811115612201575f80fd5b6020830191508360208260051b850101111561221b575f80fd5b9250929050565b5f8083601f840112612232575f80fd5b50813567ffffffffffffffff811115612249575f80fd5b60208301915083602082850101111561221b575f80fd5b5f805f805f805f805f8060c08b8d031215612279575f80fd5b8a3567ffffffffffffffff80821115612290575f80fd5b61229c8e838f016121da565b909c509a5060208d01359150808211156122b4575f80fd5b6122c08e838f016121da565b909a50985060408d01359150808211156122d8575f80fd5b6122e48e838f016121da565b90985096508691506122f860608e016120c9565b955061230660808e016120c9565b945060a08d013591508082111561231b575f80fd5b506123288d828e01612222565b915080935050809150509295989b9194979a5092959850565b5f815180845260208085019450602084015f5b8381101561237057815187529582019590820190600101612354565b509495945050505050565b8215158152604060208201525f611f396040830184612341565b634e487b7160e01b5f52604160045260245ffd5b604051610160810167ffffffffffffffff811182821017156123cd576123cd612395565b60405290565b5f82601f8301126123e2575f80fd5b813567ffffffffffffffff808211156123fd576123fd612395565b604051601f8301601f19908116603f0116810190828211818310171561242557612425612395565b8160405283815286602085880101111561243d575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f610160828403121561246d575f80fd5b6124756123a9565b9050612480826120c9565b815261248e602083016120c9565b602082015261249f604083016120c9565b6040820152606082013560608201526124ba608083016120c9565b608082015260a082013567ffffffffffffffff808211156124d9575f80fd5b6124e5858386016123d3565b60a084015260c084013560c084015261250060e085016120c9565b60e08401526101009150818401358181111561251a575f80fd5b612526868287016123d3565b8385015250505061012061253b8184016120c9565b818301525061014080830135818301525092915050565b5f611e7d368361245c565b634e487b7160e01b5f52603260045260245ffd5b5f808335601e19843603018112612586575f80fd5b830160208101925035905067ffffffffffffffff8111156125a5575f80fd5b80360382131561221b575f80fd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b602081526125fc602082016125ef846120c9565b6001600160a01b03169052565b5f612609602084016120c9565b6001600160a01b038116604084015250612625604084016120c9565b6001600160a01b0381166060840152506060830135608083015261264b608084016120c9565b6001600160a01b03811660a08401525061266860a0840184612571565b6101608060c0860152612680610180860183856125b3565b925060c086013560e086015261269860e087016120c9565b91506101006126b1818701846001600160a01b03169052565b6126bd81880188612571565b93509050610120601f1987860301818801526126da8585846125b3565b94506126e78189016120c9565b93505050610140612702818701846001600160a01b03169052565b9590950135939094019290925250919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f60a0820160018060a01b0380891684526020818916602086015260a0604086015282885180855260c08701915060208a0194505f5b81811015612797578551851683529483019491830191600101612779565b505085810360608701526127ab8189612341565b935050505082810360808401526127c28185612715565b98975050505050505050565b5f602082840312156127de575f80fd5b813567ffffffffffffffff8111156127f4575f80fd5b611f398482850161245c565b634e487b7160e01b5f52601160045260245ffd5b80820180821115611e7d57611e7d612800565b5f60208284031215612837575f80fd5b8151612150816120b5565b5f60208284031215612852575f80fd5b5051919050565b81810381811115611e7d57611e7d612800565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f602082840312156128c7575f80fd5b8151612150816120d4565b5f82518060208501845e5f920191825250919050565b602081525f612150602083018461271556fea2646970667358221220bd07941a77f491ec603d4823289eb0f70dbf4bd640d52af7e41ca27357b38eb264736f6c63430008190033", + "deployedBytecode": "0x608060405260043610610113575f3560e01c80638d72647e1161009d578063e30c397811610062578063e30c397814610354578063e58b51e914610371578063f2fde38b14610390578063f3d7d282146103af578063fc08f9f6146103dd575f80fd5b80638d72647e146102a75780638da5cb5b146102da5780639646f3ea146102f7578063c3c6467414610316578063c4d66de814610335575f80fd5b806360d6acc3116100e357806360d6acc3146101f557806362c06767146102225780636d70f7ae14610241578063715018a61461027f57806379ba509714610293575f80fd5b806314ba4a101461011e57806333e1567f1461016e578063558a7297146101a15780635fe3b567146101c2575f80fd5b3661011a57005b5f80fd5b348015610129575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b348015610179575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b3480156101ac575f80fd5b506101c06101bb3660046120e1565b61040a565b005b3480156101cd575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b348015610200575f80fd5b5061021461020f366004612118565b61047a565b604051908152602001610165565b34801561022d575f80fd5b506101c061023c366004612157565b610630565b34801561024c575f80fd5b5061026f61025b366004612195565b60c96020525f908152604090205460ff1681565b6040519015158152602001610165565b34801561028a575f80fd5b506101c06106b0565b34801561029e575f80fd5b506101c06106c3565b3480156102b2575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b3480156102e5575f80fd5b506033546001600160a01b0316610151565b348015610302575f80fd5b506101c06103113660046121b0565b61073d565b348015610321575f80fd5b506101c06103303660046120e1565b610806565b348015610340575f80fd5b506101c061034f366004612195565b61086e565b34801561035f575f80fd5b506065546001600160a01b0316610151565b34801561037c575f80fd5b506101c061038b366004612118565b610994565b34801561039b575f80fd5b506101c06103aa366004612195565b610c1c565b3480156103ba575f80fd5b5061026f6103c9366004612195565b60ca6020525f908152604090205460ff1681565b3480156103e8575f80fd5b506103fc6103f7366004612260565b610c8d565b60405161016592919061237b565b610412611080565b61041b826110da565b6001600160a01b0382165f81815260c96020908152604091829020805460ff191685151590811790915591519182527f1a594081ae893ab78e67d9b9e843547318164322d32c65369d78a96172d9dc8f91015b60405180910390a25050565b5f61048d6033546001600160a01b031690565b6001600160a01b0316336001600160a01b0316141580156104bd5750335f90815260c9602052604090205460ff16155b156104db57604051631f0853c160e21b815260040160405180910390fd5b6104e3611101565b61050c6104f660a0840160808501612195565b610507610100850160e08601612195565b61115a565b8160c001355f0361053057604051630a1c302560e21b815260040160405180910390fd5b816101400135421115610568576040516302a07ebf60e31b815261014083013560048201524260248201526044015b60405180910390fd5b5f61057a61057584612552565b6111f6565b909250905061058f6040840160208501612195565b6001600160a01b03166105a86060850160408601612195565b6001600160a01b03166105be6020860186612195565b6001600160a01b03167fbfd65b4425967651bb217826b6511e83ef29c5c5ad92f273bb22e7507ed16afb866060013585875f6040516106189493929190938452602084019290925260408301521515606082015260800190565b60405180910390a45061062b6001609755565b919050565b610638611080565b610641836110da565b61064a826110da565b61065e6001600160a01b0384168383611829565b816001600160a01b0316836001600160a01b03167f7b09c29f9106defeccc9ac3b823f3aad0b470d120e5df7aed033b5c43a4bf718836040516106a391815260200190565b60405180910390a3505050565b6106b8611080565b6106c15f611891565b565b60655433906001600160a01b031681146107315760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b606482015260840161055f565b61073a81611891565b50565b610745611080565b61074e826110da565b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f8114610797576040519150601f19603f3d011682016040523d82523d5f602084013e61079c565b606091505b50509050806107be57604051633d2cec6f60e21b815260040160405180910390fd5b826001600160a01b03167f1aedae30ba52b882825adc6b3ed1febc9a717c20eaca0e2c50fd510e4bdc4cd6836040516107f991815260200190565b60405180910390a2505050565b61080e611080565b610817826110da565b6001600160a01b0382165f81815260ca6020908152604091829020805460ff191685151590811790915591519182527f8443a265af33599abe6c9ed0c3cabd8e2a6c5b8ea35c1d1ed40c513242f95d45910161046e565b5f54610100900460ff161580801561088c57505f54600160ff909116105b806108a55750303b1580156108a557505f5460ff166001145b6109085760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161055f565b5f805460ff191660011790558015610929575f805461ff0019166101001790555b610932826110da565b61093a6118aa565b6109426118d8565b61094b82611891565b8015610990575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6033546001600160a01b031633148015906109be5750335f90815260c9602052604090205460ff16155b156109dc57604051631f0853c160e21b815260040160405180910390fd5b6109e4611101565b610a086109f760a0830160808401612195565b610507610100840160e08501612195565b8060c001355f03610a2c57604051630a1c302560e21b815260040160405180910390fd5b806101400135421115610a5f576040516302a07ebf60e31b8152610140820135600482015242602482015260440161055f565b6040805160018082528183019092525f91602080830190803683370190505090506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016610aba6040840160208501612195565b6001600160a01b031614610add57610ad86040830160208401612195565b610aff565b7f00000000000000000000000000000000000000000000000000000000000000005b815f81518110610b1157610b1161255d565b6001600160a01b0392909216602092830291909101909101526040805160018082528183019092525f918160200160208202803683370190505090508260600135815f81518110610b6457610b6461255d565b6020026020010181815250507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316635544ed9c3030858588604051602001610bb491906125db565b6040516020818303038152906040526040518663ffffffff1660e01b8152600401610be3959493929190612743565b5f604051808303815f87803b158015610bfa575f80fd5b505af1158015610c0c573d5f803e3d5ffd5b50505050505061073a6001609755565b610c24611080565b606580546001600160a01b0383166001600160a01b03199091168117909155610c556033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b5f6060336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610cd95760405163880ed0f360e01b815260040160405180910390fd5b6001600160a01b0386163014610d0d576040516322c7df1960e21b81526001600160a01b038716600482015260240161055f565b5f610d1a848601866127ce565b90505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031682602001516001600160a01b031614610d64578160200151610d86565b7f00000000000000000000000000000000000000000000000000000000000000005b9050806001600160a01b03168e8e5f818110610da457610da461255d565b9050602002016020810190610db99190612195565b6001600160a01b031614610de05760405163e60249cb60e01b815260040160405180910390fd5b505f80610dec836111f6565b604080516001808252818301909252929450909250602080830190803683370190505093508a8a5f818110610e2357610e2361255d565b905060200201358d8d5f818110610e3c57610e3c61255d565b90506020020135610e4d9190612814565b845f81518110610e5f57610e5f61255d565b602002602001018181525050835f81518110610e7d57610e7d61255d565b6020026020010151821015610ecb5781845f81518110610e9f57610e9f61255d565b602002602001015160405163f447a23960e01b815260040161055f929190918252602082015260400190565b610fe78f8f5f818110610ee057610ee061255d565b9050602002016020810190610ef59190612195565b855f81518110610f0757610f0761255d565b60200260200101517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031686602001516001600160a01b031614610fb55785602001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f8c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fb09190612827565b610fd7565b7f00000000000000000000000000000000000000000000000000000000000000005b6001600160a01b03169190611906565b82602001516001600160a01b031683604001516001600160a01b0316845f01516001600160a01b03167fbfd65b4425967651bb217826b6511e83ef29c5c5ad92f273bb22e7507ed16afb8660600151858760016040516110629493929190938452602084019290925260408301521515606082015260800190565b60405180910390a4600194505050509a509a98505050505050505050565b6033546001600160a01b031633146106c15760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161055f565b6001600160a01b03811661073a576040516342bcdf7f60e11b815260040160405180910390fd5b6002609754036111535760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015260640161055f565b6002609755565b6001600160a01b0382165f90815260ca602052604090205460ff1661119d5760405163c665406f60e01b81526001600160a01b038316600482015260240161055f565b6001600160a01b038116158015906111cd57506001600160a01b0381165f90815260ca602052604090205460ff16155b156109905760405163c665406f60e01b81526001600160a01b038216600482015260240161055f565b5f805f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031684602001516001600160a01b031614905080801561124c575060e08401516001600160a01b0316155b1561126a57604051631a7a563960e21b815260040160405180910390fd5b5f816112d95784602001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112b0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112d49190612827565b6112fb565b7f00000000000000000000000000000000000000000000000000000000000000005b90505f85604001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa15801561133e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113629190612827565b60408088015190516370a0823160e01b81523060048201529192505f916001600160a01b03909116906370a0823190602401602060405180830381865afa1580156113af573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113d39190612842565b90505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316639bb27d626040518163ffffffff1660e01b8152600401602060405180830381865afa158015611432573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114569190612827565b9050611461816110da565b8415611566576060880151604051632e1a7d4d60e01b815260048101919091527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690632e1a7d4d906024015f604051808303815f87803b1580156114cc575f80fd5b505af11580156114de573d5f803e3d5ffd5b505050506060880151602089015189516040808c01519051630c9fae0f60e31b81526001600160a01b03938416600482015291831660248301526044820184905282166064820152908316916364fd7078916084015f604051808303818588803b15801561154a575f80fd5b505af115801561155c573d5f803e3d5ffd5b5050505050611615565b6060880151611581906001600160a01b038616908390611906565b6020880151885160608a01516040808c01519051630c9fae0f60e31b81526001600160a01b0394851660048201529284166024840152604483019190915282166064820152908216906364fd7078906084015f604051808303815f87803b1580156115ea575f80fd5b505af11580156115fc573d5f803e3d5ffd5b50611615925050506001600160a01b038516825f611906565b60408089015190516370a0823160e01b81523060048201525f9184916001600160a01b03909116906370a0823190602401602060405180830381865afa158015611661573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116859190612842565b61168f9190612859565b6040516370a0823160e01b81523060048201529091505f906001600160a01b038616906370a0823190602401602060405180830381865afa1580156116d6573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116fa9190612842565b90505f8a604001516001600160a01b031663db006a75846040518263ffffffff1660e01b815260040161172f91815260200190565b6020604051808303815f875af115801561174b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061176f9190612842565b905080156117935760405163eeddaac560e01b81526004810182905260240161055f565b6040516370a0823160e01b815230600482015282906001600160a01b038816906370a0823190602401602060405180830381865afa1580156117d7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117fb9190612842565b6118059190612859565b985061181387878b8e61199a565b99505050505050505050915091565b6001609755565b6040516001600160a01b03831660248201526044810182905261188c90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152611c67565b505050565b606580546001600160a01b031916905561073a81611d3a565b5f54610100900460ff166118d05760405162461bcd60e51b815260040161055f9061286c565b6106c1611d8b565b5f54610100900460ff166118fe5760405162461bcd60e51b815260040161055f9061286c565b6106c1611dba565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b1790526119578482611de0565b611994576040516001600160a01b03841660248201525f604482015261198a90859063095ea7b360e01b90606401611855565b6119948482611c67565b50505050565b6040516370a0823160e01b81523060048201525f9081906001600160a01b038716906370a0823190602401602060405180830381865afa1580156119e0573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a049190612842565b60e08401519091506001600160a01b0316611a3257611a2d8584608001518560a0015187611e83565b611bb5565b6101208301516001600160a01b03161580611a635750856001600160a01b03168361012001516001600160a01b0316145b80611a845750846001600160a01b03168361012001516001600160a01b0316145b15611aa257604051631a7a563960e21b815260040160405180910390fd5b6101208301516040516370a0823160e01b81523060048201525f906001600160a01b038316906370a0823190602401602060405180830381865afa158015611aec573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b109190612842565b9050611b268786608001518760a0015189611e83565b6040516370a0823160e01b81523060048201525f9082906001600160a01b038516906370a0823190602401602060405180830381865afa158015611b6c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b909190612842565b611b9a9190612859565b9050611bb1838760e0015188610100015184611e83565b5050505b6040516370a0823160e01b815230600482015281906001600160a01b038816906370a0823190602401602060405180830381865afa158015611bf9573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c1d9190612842565b611c279190612859565b91508260c00151821015611c5e5760c083015160405163f447a23960e01b815261055f918491600401918252602082015260400190565b50949350505050565b5f611cbb826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316611f2b9092919063ffffffff16565b905080515f1480611cdb575080806020019051810190611cdb91906128b7565b61188c5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161055f565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f54610100900460ff16611db15760405162461bcd60e51b815260040161055f9061286c565b6106c133611891565b5f54610100900460ff166118225760405162461bcd60e51b815260040161055f9061286c565b5f805f846001600160a01b031684604051611dfb91906128d2565b5f604051808303815f865af19150503d805f8114611e34576040519150601f19603f3d011682016040523d82523d5f602084013e611e39565b606091505b5091509150818015611e63575080511580611e63575080806020019051810190611e6391906128b7565b8015611e7857506001600160a01b0385163b15155b925050505b92915050565b611e976001600160a01b0385168483611906565b5f836001600160a01b031683604051611eb091906128d2565b5f604051808303815f865af19150503d805f8114611ee9576040519150601f19603f3d011682016040523d82523d5f602084013e611eee565b606091505b5050905080611f105760405163081ceff360e41b815260040160405180910390fd5b611f246001600160a01b038616855f611906565b5050505050565b6060611f3984845f85611f41565b949350505050565b606082471015611fa25760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840161055f565b5f80866001600160a01b03168587604051611fbd91906128d2565b5f6040518083038185875af1925050503d805f8114611ff7576040519150601f19603f3d011682016040523d82523d5f602084013e611ffc565b606091505b509150915061200d87838387612018565b979650505050505050565b606083156120865782515f0361207f576001600160a01b0385163b61207f5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161055f565b5081611f39565b611f39838381511561209b5781518083602001fd5b8060405162461bcd60e51b815260040161055f91906128e8565b6001600160a01b038116811461073a575f80fd5b803561062b816120b5565b801515811461073a575f80fd5b5f80604083850312156120f2575f80fd5b82356120fd816120b5565b9150602083013561210d816120d4565b809150509250929050565b5f60208284031215612128575f80fd5b813567ffffffffffffffff81111561213e575f80fd5b82016101608185031215612150575f80fd5b9392505050565b5f805f60608486031215612169575f80fd5b8335612174816120b5565b92506020840135612184816120b5565b929592945050506040919091013590565b5f602082840312156121a5575f80fd5b8135612150816120b5565b5f80604083850312156121c1575f80fd5b82356121cc816120b5565b946020939093013593505050565b5f8083601f8401126121ea575f80fd5b50813567ffffffffffffffff811115612201575f80fd5b6020830191508360208260051b850101111561221b575f80fd5b9250929050565b5f8083601f840112612232575f80fd5b50813567ffffffffffffffff811115612249575f80fd5b60208301915083602082850101111561221b575f80fd5b5f805f805f805f805f8060c08b8d031215612279575f80fd5b8a3567ffffffffffffffff80821115612290575f80fd5b61229c8e838f016121da565b909c509a5060208d01359150808211156122b4575f80fd5b6122c08e838f016121da565b909a50985060408d01359150808211156122d8575f80fd5b6122e48e838f016121da565b90985096508691506122f860608e016120c9565b955061230660808e016120c9565b945060a08d013591508082111561231b575f80fd5b506123288d828e01612222565b915080935050809150509295989b9194979a5092959850565b5f815180845260208085019450602084015f5b8381101561237057815187529582019590820190600101612354565b509495945050505050565b8215158152604060208201525f611f396040830184612341565b634e487b7160e01b5f52604160045260245ffd5b604051610160810167ffffffffffffffff811182821017156123cd576123cd612395565b60405290565b5f82601f8301126123e2575f80fd5b813567ffffffffffffffff808211156123fd576123fd612395565b604051601f8301601f19908116603f0116810190828211818310171561242557612425612395565b8160405283815286602085880101111561243d575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f610160828403121561246d575f80fd5b6124756123a9565b9050612480826120c9565b815261248e602083016120c9565b602082015261249f604083016120c9565b6040820152606082013560608201526124ba608083016120c9565b608082015260a082013567ffffffffffffffff808211156124d9575f80fd5b6124e5858386016123d3565b60a084015260c084013560c084015261250060e085016120c9565b60e08401526101009150818401358181111561251a575f80fd5b612526868287016123d3565b8385015250505061012061253b8184016120c9565b818301525061014080830135818301525092915050565b5f611e7d368361245c565b634e487b7160e01b5f52603260045260245ffd5b5f808335601e19843603018112612586575f80fd5b830160208101925035905067ffffffffffffffff8111156125a5575f80fd5b80360382131561221b575f80fd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b602081526125fc602082016125ef846120c9565b6001600160a01b03169052565b5f612609602084016120c9565b6001600160a01b038116604084015250612625604084016120c9565b6001600160a01b0381166060840152506060830135608083015261264b608084016120c9565b6001600160a01b03811660a08401525061266860a0840184612571565b6101608060c0860152612680610180860183856125b3565b925060c086013560e086015261269860e087016120c9565b91506101006126b1818701846001600160a01b03169052565b6126bd81880188612571565b93509050610120601f1987860301818801526126da8585846125b3565b94506126e78189016120c9565b93505050610140612702818701846001600160a01b03169052565b9590950135939094019290925250919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f60a0820160018060a01b0380891684526020818916602086015260a0604086015282885180855260c08701915060208a0194505f5b81811015612797578551851683529483019491830191600101612779565b505085810360608701526127ab8189612341565b935050505082810360808401526127c28185612715565b98975050505050505050565b5f602082840312156127de575f80fd5b813567ffffffffffffffff8111156127f4575f80fd5b611f398482850161245c565b634e487b7160e01b5f52601160045260245ffd5b80820180821115611e7d57611e7d612800565b5f60208284031215612837575f80fd5b8151612150816120b5565b5f60208284031215612852575f80fd5b5051919050565b81810381811115611e7d57611e7d612800565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f602082840312156128c7575f80fd5b8151612150816120d4565b5f82518060208501845e5f920191825250919050565b602081525f612150602083018461271556fea2646970667358221220bd07941a77f491ec603d4823289eb0f70dbf4bd640d52af7e41ca27357b38eb264736f6c63430008190033", + "devdoc": { + "author": "Venus", + "events": { + "Initialized(uint8)": { + "details": "Triggered when the contract has been initialized or reinitialized." + }, + "Liquidated(address,address,address,uint256,uint256,uint256,bool)": { + "params": { + "borrower": "The liquidated account.", + "debtOut": "Debt-asset proceeds of the swap.", + "flash": "True if funded by a flash loan, false if from inventory.", + "repayAmount": "Debt underlying repaid.", + "seizedBStock": "Raw bStock redeemed and sold.", + "vBStock": "The seized bStock collateral market.", + "vDebt": "The repaid debt market." + } + } + }, + "kind": "dev", + "methods": { + "acceptOwnership()": { + "details": "The new owner accepts the ownership transfer." + }, + "constructor": { + "custom:oz-upgrades-unsafe-allow": "constructor", + "params": { + "comptroller_": "Venus Core Comptroller (diamond) — gates liquidation and provides flash loans.", + "vBNB_": "Native BNB market; a debt equal to this address is settled in native BNB.", + "vWBNB_": "WBNB market; the flash-borrow source for BNB debt (vBNB itself cannot be flash-repaid).", + "wbnb_": "WBNB token; the debt-accounting asset for BNB debt, unwrapped for the native repay." + } + }, + "executeOperation(address[],uint256[],uint256[],address,address,bytes)": { + "details": "Implementation of this function must ensure at least the premium (fee) is repaid within the same transaction. Any unpaid balance (principal + premium - repaid amount) will be added to the onBehalf address's borrow balance.", + "params": { + "amounts": "The amounts of each underlying asset that were flash-borrowed.", + "initiator": "The address that initiated the flash loan.", + "onBehalf": "The address of the user whose debt position will be used for any unpaid flash loan balance.", + "param": "Additional parameters encoded as bytes. These can be used to pass custom data to the receiver contract.", + "premiums": "The premiums (fees) associated with each flash-borrowed asset.", + "vTokens": "The vToken contracts corresponding to the flash-borrowed underlying assets." + }, + "returns": { + "_0": "True if the operation succeeds (regardless of repayment amount), false if the operation fails.", + "repayAmounts": "Array of uint256 representing the amounts to be repaid for each asset. The receiver contract must approve these amounts to the respective vToken contracts before this function returns." + } + }, + "flashLiquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))": { + "details": "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`.", + "params": { + "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)." + } + }, + "initialize(address)": { + "params": { + "initialOwner": "Address that owns the contract (admin + default operator)." + } + }, + "liquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))": { + "details": "INVENTORY mode spends the contract's OWN debt-asset capital, so — unlike FLASH mode, where `executeOperation` forces the swap proceeds to cover principal + premium — there is no built-in floor tying `debtOut` to `repayAmount`. This asymmetry is intentional: a repay can legitimately out-cost its proceeds (e.g. the Venus Liquidator keeps a treasury cut of the seized collateral, so proceeds land a few % under the repay). `minOut` IS the operator's chosen loss floor for inventory mode — set it to the lowest acceptable debt-asset return.", + "params": { + "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)." + }, + "returns": { + "debtOut": "Debt-asset proceeds realized by the swap chain." + } + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "pendingOwner()": { + "details": "Returns the address of the pending owner." + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." + }, + "setOperator(address,bool)": { + "params": { + "allowed": "True to allow, false to remove.", + "operator": "Address to allowlist or remove." + } + }, + "setRouter(address,bool)": { + "params": { + "allowed": "True to allow, false to remove.", + "router": "Address to allowlist or remove." + } + }, + "sweep(address,address,uint256)": { + "params": { + "amount": "Amount to withdraw.", + "to": "Recipient.", + "token": "Token to withdraw." + } + }, + "sweepNative(address,uint256)": { + "params": { + "amount": "Amount of native BNB to withdraw.", + "to": "Recipient." + } + }, + "transferOwnership(address)": { + "details": "Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. Can only be called by the current owner." + } + }, + "stateVariables": { + "__gap": { + "details": "Reserved storage to allow new state variables in future upgrades without layout clashes." + }, + "comptroller": { + "custom:oz-upgrades-unsafe-allow": "state-variable-immutable" + }, + "vBNB": { + "custom:oz-upgrades-unsafe-allow": "state-variable-immutable" + }, + "vWBNB": { + "custom:oz-upgrades-unsafe-allow": "state-variable-immutable" + }, + "wbnb": { + "custom:oz-upgrades-unsafe-allow": "state-variable-immutable" + } + }, + "title": "BStockLiquidator", + "version": 1 + }, + "userdoc": { + "errors": { + "BadInitiator(address)": [ + { + "notice": "Thrown when the flash-loan initiator is not this contract." + } + ], + "DeadlineExpired(uint256,uint256)": [ + { + "notice": "Thrown when the call is submitted after `params.deadline`." + } + ], + "InsufficientOut(uint256,uint256)": [ + { + "notice": "Thrown when swap proceeds are below `minOut`." + } + ], + "InvalidIntermediate()": [ + { + "notice": "Thrown when a two-hop `intermediateToken` is zero, or equals the debt or bStock token." + } + ], + "NativeTransferFailed()": [ + { + "notice": "Thrown when a native BNB transfer (the `sweepNative` payout) fails." + } + ], + "NotOperator()": [ + { + "notice": "Thrown when the caller is neither the owner nor an allowlisted operator." + } + ], + "OnlyComptroller()": [ + { + "notice": "Thrown when `executeOperation` is called by something other than the Comptroller." + } + ], + "RedeemFailed(uint256)": [ + { + "notice": "Thrown when `vBStock.redeem` returns a non-zero error code." + } + ], + "RouterNotAllowed(address)": [ + { + "notice": "Thrown when the supplied swap router is not allowlisted." + } + ], + "SwapFailed()": [ + { + "notice": "Thrown when the low-level call to the router reverts." + } + ], + "WrongFlashAsset()": [ + { + "notice": "Thrown when the flashed asset does not match `params.vDebt`." + } + ], + "ZeroAddressNotAllowed()": [ + { + "notice": "Thrown if the supplied address is a zero address where it is not allowed" + } + ], + "ZeroMinOut()": [ + { + "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)." + } + ] + }, + "events": { + "Liquidated(address,address,address,uint256,uint256,uint256,bool)": { + "notice": "Emitted on a successful liquidation." + }, + "OperatorSet(address,bool)": { + "notice": "Emitted when an operator is allowlisted or removed." + }, + "RouterSet(address,bool)": { + "notice": "Emitted when a swap router is allowlisted or removed." + }, + "Swept(address,address,uint256)": { + "notice": "Emitted when the owner withdraws a token." + }, + "SweptNative(address,uint256)": { + "notice": "Emitted when the owner withdraws stuck native BNB." + } + }, + "kind": "user", + "methods": { + "comptroller()": { + "notice": "Core Comptroller (diamond): reads the liquidation gate and provides the flash loan via `executeFlashLoan`." + }, + "constructor": { + "notice": "Constructor for the implementation contract. Sets the immutables and locks initializers." + }, + "executeOperation(address[],uint256[],uint256[],address,address,bytes)": { + "notice": "Executes an operation after receiving the flash-borrowed assets." + }, + "flashLiquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))": { + "notice": "Liquidate by flash-borrowing the repay amount from Venus, repaid (+ premium) in the same tx." + }, + "initialize(address)": { + "notice": "Initializes the proxy: sets the owner and the reentrancy guard." + }, + "isOperator(address)": { + "notice": "Addresses allowed to trigger a liquidation." + }, + "isRouter(address)": { + "notice": "Routers allowed as the swap target (defends the low-level call)." + }, + "liquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))": { + "notice": "Liquidate using the contract's own debt-asset inventory." + }, + "setOperator(address,bool)": { + "notice": "Allow or disallow an address to trigger liquidations." + }, + "setRouter(address,bool)": { + "notice": "Allow or disallow a router as the swap target (e.g. the Native router)." + }, + "sweep(address,address,uint256)": { + "notice": "Withdraw any token (profit, leftover inventory, stuck dust) to `to`." + }, + "sweepNative(address,uint256)": { + "notice": "Withdraw stuck native BNB (a stray transfer, or a gate refund) to `to`." + }, + "vBNB()": { + "notice": "The native BNB market (vBNB). A debt equal to this address is settled with native BNB: WBNB is the debt-accounting token, and only the repay amount is unwrapped (see `_liquidate`)." + }, + "vWBNB()": { + "notice": "The WBNB market (vWBNB). BNB debt is flash-funded from here, NOT from vBNB: vBNB cannot be flash-repaid (its `doTransferIn` needs `msg.value`), whereas vWBNB's underlying is a plain ERC20 repaid via `transferFrom`." + }, + "wbnb()": { + "notice": "WBNB token: the debt-accounting asset for BNB debt, unwrapped to native BNB for the repay." + } + }, + "notice": "Atomic backstop liquidator for bStock (ERC-8056 tokenized stock) collateral. In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken, redeems it to the raw bStock, and sells that bStock to the debt asset in one or two hops. Hop 1 always sells bStock through the Native RFQ router using a pre-fetched, MM-signed firm-quote `txRequest`. For USDT debt that single hop lands the debt asset directly. For non-USDT debt an OPTIONAL hop 2 (Native quotes bStock->USDT only) converts the USDT to the debt asset through a second allowlisted router (an AMM/aggregator). Because seize and sell happen in the same tx there is no price-drift window, and the realized debt-asset amount must clear `minOut` or the whole call reverts — the protocol never ends up holding the RFQ-only asset or the intermediate. Two funding modes share the same core (`_liquidate`): - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance. - FLASH: the debt asset is flash-borrowed from Venus (`Comptroller.executeFlashLoan`) and repaid (+ premium) within the same tx; no capital is locked in the contract. Native BNB debt (vBNB): supported in both modes with WBNB as the debt-accounting token. The repay must be native BNB, so exactly the repay amount of WBNB is unwrapped and forwarded to the gate's payable path; the two-hop swap lands WBNB (bStock->USDT->WBNB) and `minOut` is measured in WBNB (1:1 with BNB). FLASH mode borrows from vWBNB, NOT vBNB: vBNB cannot be flash-repaid (its `doTransferIn` requires `msg.value`), whereas vWBNB's underlying is a plain ERC20. Ownership / scope: this is Venus's OWN backstop tool, NOT a public utility — `liquidate` and `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus path with their own funds and their own offload. This contract is intentionally gated because it custodies funds (debt-asset inventory / flash principal) and forwards a CALLER-SUPPLIED calldata blob to an external router — the swap's recipient (`to`) lives inside that calldata, so an open entrypoint would let anyone route the proceeds to themselves and drain the contract. The router allowlist and `minOut` bound the blast radius but cannot replace operator-gating. Security model: - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator). - BOTH swap targets (`router` and, when set, `router2`) must be allowlisted (`isRouter`) — defends the low-level `router.call(swapCalldata)` on each hop. - the approval to each router is the exact amount being sold on that hop, reset to 0 afterwards (bStock on hop 1; the measured intermediate balance delta on hop 2, so pre-existing inventory is never exposed). - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started). - the realized debt-asset amount must clear `minOut` or the tx reverts. Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`, which is always configured on the networks this contract targets. While it is set, a direct `vToken.liquidateBorrow` from an arbitrary caller reverts UNAUTHORIZED, so this contract reads the gate at call time and routes the repay through that Venus Liquidator (the permissionless entry anyone may call), reverting if the gate is ever unset. Routing through the gate needs no governance change, and no other Core market is affected. Note: setting THIS contract as `liquidatorContract` is NOT an option — the gate is pool-wide, so every other market's liquidations would be forced through here.", + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 246, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8" + }, + { + "astId": 249, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool" + }, + { + "astId": 1363, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage" + }, + { + "astId": 118, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "t_address" + }, + { + "astId": 238, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "t_array(t_uint256)49_storage" + }, + { + "astId": 11, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "_pendingOwner", + "offset": 0, + "slot": "101", + "type": "t_address" + }, + { + "astId": 105, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage" + }, + { + "astId": 423, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "_status", + "offset": 0, + "slot": "151", + "type": "t_uint256" + }, + { + "astId": 492, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage" + }, + { + "astId": 2833, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "isOperator", + "offset": 0, + "slot": "201", + "type": "t_mapping(t_address,t_bool)" + }, + { + "astId": 2838, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "isRouter", + "offset": 0, + "slot": "202", + "type": "t_mapping(t_address,t_bool)" + }, + { + "astId": 2843, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "__gap", + "offset": 0, + "slot": "203", + "type": "t_array(t_uint256)50_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)49_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_bool)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "encoding": "inplace", + "label": "uint8", + "numberOfBytes": "1" + } + } + } +} diff --git a/deployments/bscmainnet/BStockLiquidator_Proxy.json b/deployments/bscmainnet/BStockLiquidator_Proxy.json new file mode 100644 index 000000000..8030cbf20 --- /dev/null +++ b/deployments/bscmainnet/BStockLiquidator_Proxy.json @@ -0,0 +1,271 @@ +{ + "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "changeAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "receipt": { + "to": null, + "from": "0x8D43F1776B4d5eB69cDbE2e79899520754a0cd9A", + "contractAddress": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "transactionIndex": 41, + "gasUsed": "797587", + "logsBloom": "0x000000040000000000000000000000004020000000000000008000000000000000000000000000000000000000000800200000000000000000000000000000000000000000000000000000000000020000010000000000000000000000000000000000000200000000000010000008000000008000000000000000801000004000000000000080000000000000000000000000000000c0000000000000800000000000000000010000000000000400000000000000400000000000000000000000000220000000000000000000040000000000000400000000000000000020000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db", + "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "logs": [ + { + "transactionIndex": 41, + "blockNumber": 107817335, + "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x000000000000000000000000883c36bade4babe393fef2fcfcfc24a5e8e1a3d5" + ], + "data": "0x", + "logIndex": 131, + "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + }, + { + "transactionIndex": 41, + "blockNumber": 107817335, + "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000008d43f1776b4d5eb69cdbe2e79899520754a0cd9a" + ], + "data": "0x", + "logIndex": 132, + "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + }, + { + "transactionIndex": 41, + "blockNumber": 107817335, + "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000008d43f1776b4d5eb69cdbe2e79899520754a0cd9a", + "0x00000000000000000000000083f426233b358a36953f6951161e76fb7c866a7a" + ], + "data": "0x", + "logIndex": 133, + "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + }, + { + "transactionIndex": 41, + "blockNumber": 107817335, + "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logIndex": 134, + "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + }, + { + "transactionIndex": 41, + "blockNumber": 107817335, + "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "topics": ["0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f"], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001bb765b741a5f3c2a338369dab539385534e3343", + "logIndex": 135, + "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + } + ], + "blockNumber": 107817335, + "cumulativeGasUsed": "5080085", + "status": 1, + "byzantium": true + }, + "args": [ + "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", + "0x1BB765b741A5f3C2A338369DAb539385534E3343", + "0xc4d66de800000000000000000000000083f426233b358a36953f6951161e76fb7c866a7a" + ], + "numDeployments": 1, + "solcInputHash": "0e89febeebc7444140de8e67c9067d2c", + "metadata": "{\"compiler\":{\"version\":\"0.8.10+commit.fc410830\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_logic\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"admin_\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"previousAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"AdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"beacon\",\"type\":\"address\"}],\"name\":\"BeaconUpgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"admin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"admin_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"changeAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"implementation\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"implementation_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"}],\"name\":\"upgradeTo\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"details\":\"This contract implements a proxy that is upgradeable by an admin. To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector clashing], which can potentially be used in an attack, this contract uses the https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two things that go hand in hand: 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if that call matches one of the admin functions exposed by the proxy itself. 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the implementation. If the admin tries to call a function on the implementation it will fail with an error that says \\\"admin cannot fallback to proxy target\\\". These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to call a function from the proxy implementation. Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.\",\"kind\":\"dev\",\"methods\":{\"admin()\":{\"details\":\"Returns the current admin. NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}. TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\"},\"changeAdmin(address)\":{\"details\":\"Changes the admin of the proxy. Emits an {AdminChanged} event. NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.\"},\"constructor\":{\"details\":\"Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\"},\"implementation()\":{\"details\":\"Returns the current implementation. NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}. TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`\"},\"upgradeTo(address)\":{\"details\":\"Upgrade the implementation of the proxy. NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\"},\"upgradeToAndCall(address,bytes)\":{\"details\":\"Upgrade the implementation of the proxy, and then call a function from the new implementation as specified by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the proxied contract. NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"solc_0.8/openzeppelin/proxy/transparent/TransparentUpgradeableProxy.sol\":\"TransparentUpgradeableProxy\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"solc_0.8/openzeppelin/interfaces/draft-IERC1822.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (interfaces/draft-IERC1822.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified\\n * proxy whose upgrades are fully controlled by the current implementation.\\n */\\ninterface IERC1822Proxiable {\\n /**\\n * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation\\n * address.\\n *\\n * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks\\n * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this\\n * function revert if invoked through a proxy.\\n */\\n function proxiableUUID() external view returns (bytes32);\\n}\\n\",\"keccak256\":\"0x93b4e21c931252739a1ec13ea31d3d35a5c068be3163ccab83e4d70c40355f03\",\"license\":\"MIT\"},\"solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Proxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Proxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../Proxy.sol\\\";\\nimport \\\"./ERC1967Upgrade.sol\\\";\\n\\n/**\\n * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an\\n * implementation address that can be changed. This address is stored in storage in the location specified by\\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the\\n * implementation behind the proxy.\\n */\\ncontract ERC1967Proxy is Proxy, ERC1967Upgrade {\\n /**\\n * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.\\n *\\n * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded\\n * function call, and allows initializating the storage of the proxy like a Solidity constructor.\\n */\\n constructor(address _logic, bytes memory _data) payable {\\n assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256(\\\"eip1967.proxy.implementation\\\")) - 1));\\n _upgradeToAndCall(_logic, _data, false);\\n }\\n\\n /**\\n * @dev Returns the current implementation address.\\n */\\n function _implementation() internal view virtual override returns (address impl) {\\n return ERC1967Upgrade._getImplementation();\\n }\\n}\\n\",\"keccak256\":\"0x6309f9f39dc6f4f45a24f296543867aa358e32946cd6b2874627a996d606b3a0\",\"license\":\"MIT\"},\"solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Upgrade.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/ERC1967/ERC1967Upgrade.sol)\\n\\npragma solidity ^0.8.2;\\n\\nimport \\\"../beacon/IBeacon.sol\\\";\\nimport \\\"../../interfaces/draft-IERC1822.sol\\\";\\nimport \\\"../../utils/Address.sol\\\";\\nimport \\\"../../utils/StorageSlot.sol\\\";\\n\\n/**\\n * @dev This abstract contract provides getters and event emitting update functions for\\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.\\n *\\n * _Available since v4.1._\\n *\\n * @custom:oz-upgrades-unsafe-allow delegatecall\\n */\\nabstract contract ERC1967Upgrade {\\n // This is the keccak-256 hash of \\\"eip1967.proxy.rollback\\\" subtracted by 1\\n bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;\\n\\n /**\\n * @dev Storage slot with the address of the current implementation.\\n * This is the keccak-256 hash of \\\"eip1967.proxy.implementation\\\" subtracted by 1, and is\\n * validated in the constructor.\\n */\\n bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n\\n /**\\n * @dev Emitted when the implementation is upgraded.\\n */\\n event Upgraded(address indexed implementation);\\n\\n /**\\n * @dev Returns the current implementation address.\\n */\\n function _getImplementation() internal view returns (address) {\\n return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new address in the EIP1967 implementation slot.\\n */\\n function _setImplementation(address newImplementation) private {\\n require(Address.isContract(newImplementation), \\\"ERC1967: new implementation is not a contract\\\");\\n StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\\n }\\n\\n /**\\n * @dev Perform implementation upgrade\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeTo(address newImplementation) internal {\\n _setImplementation(newImplementation);\\n emit Upgraded(newImplementation);\\n }\\n\\n /**\\n * @dev Perform implementation upgrade with additional setup call.\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeToAndCall(\\n address newImplementation,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n _upgradeTo(newImplementation);\\n if (data.length > 0 || forceCall) {\\n Address.functionDelegateCall(newImplementation, data);\\n }\\n }\\n\\n /**\\n * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeToAndCallUUPS(\\n address newImplementation,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n // Upgrades from old implementations will perform a rollback test. This test requires the new\\n // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing\\n // this special case will break upgrade paths from old UUPS implementation to new ones.\\n if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {\\n _setImplementation(newImplementation);\\n } else {\\n try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {\\n require(slot == _IMPLEMENTATION_SLOT, \\\"ERC1967Upgrade: unsupported proxiableUUID\\\");\\n } catch {\\n revert(\\\"ERC1967Upgrade: new implementation is not UUPS\\\");\\n }\\n _upgradeToAndCall(newImplementation, data, forceCall);\\n }\\n }\\n\\n /**\\n * @dev Storage slot with the admin of the contract.\\n * This is the keccak-256 hash of \\\"eip1967.proxy.admin\\\" subtracted by 1, and is\\n * validated in the constructor.\\n */\\n bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\\n\\n /**\\n * @dev Emitted when the admin account has changed.\\n */\\n event AdminChanged(address previousAdmin, address newAdmin);\\n\\n /**\\n * @dev Returns the current admin.\\n */\\n function _getAdmin() internal view virtual returns (address) {\\n return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new address in the EIP1967 admin slot.\\n */\\n function _setAdmin(address newAdmin) private {\\n require(newAdmin != address(0), \\\"ERC1967: new admin is the zero address\\\");\\n StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;\\n }\\n\\n /**\\n * @dev Changes the admin of the proxy.\\n *\\n * Emits an {AdminChanged} event.\\n */\\n function _changeAdmin(address newAdmin) internal {\\n emit AdminChanged(_getAdmin(), newAdmin);\\n _setAdmin(newAdmin);\\n }\\n\\n /**\\n * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.\\n * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.\\n */\\n bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;\\n\\n /**\\n * @dev Emitted when the beacon is upgraded.\\n */\\n event BeaconUpgraded(address indexed beacon);\\n\\n /**\\n * @dev Returns the current beacon.\\n */\\n function _getBeacon() internal view returns (address) {\\n return StorageSlot.getAddressSlot(_BEACON_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new beacon in the EIP1967 beacon slot.\\n */\\n function _setBeacon(address newBeacon) private {\\n require(Address.isContract(newBeacon), \\\"ERC1967: new beacon is not a contract\\\");\\n require(Address.isContract(IBeacon(newBeacon).implementation()), \\\"ERC1967: beacon implementation is not a contract\\\");\\n StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;\\n }\\n\\n /**\\n * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does\\n * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).\\n *\\n * Emits a {BeaconUpgraded} event.\\n */\\n function _upgradeBeaconToAndCall(\\n address newBeacon,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n _setBeacon(newBeacon);\\n emit BeaconUpgraded(newBeacon);\\n if (data.length > 0 || forceCall) {\\n Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);\\n }\\n }\\n}\\n\",\"keccak256\":\"0x17668652127feebed0ce8d9431ef95ccc8c4292f03e3b8cf06c6ca16af396633\",\"license\":\"MIT\"},\"solc_0.8/openzeppelin/proxy/Proxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/Proxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM\\n * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to\\n * be specified by overriding the virtual {_implementation} function.\\n *\\n * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a\\n * different contract through the {_delegate} function.\\n *\\n * The success and return data of the delegated call will be returned back to the caller of the proxy.\\n */\\nabstract contract Proxy {\\n /**\\n * @dev Delegates the current call to `implementation`.\\n *\\n * This function does not return to its internal call site, it will return directly to the external caller.\\n */\\n function _delegate(address implementation) internal virtual {\\n assembly {\\n // Copy msg.data. We take full control of memory in this inline assembly\\n // block because it will not return to Solidity code. We overwrite the\\n // Solidity scratch pad at memory position 0.\\n calldatacopy(0, 0, calldatasize())\\n\\n // Call the implementation.\\n // out and outsize are 0 because we don't know the size yet.\\n let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)\\n\\n // Copy the returned data.\\n returndatacopy(0, 0, returndatasize())\\n\\n switch result\\n // delegatecall returns 0 on error.\\n case 0 {\\n revert(0, returndatasize())\\n }\\n default {\\n return(0, returndatasize())\\n }\\n }\\n }\\n\\n /**\\n * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function\\n * and {_fallback} should delegate.\\n */\\n function _implementation() internal view virtual returns (address);\\n\\n /**\\n * @dev Delegates the current call to the address returned by `_implementation()`.\\n *\\n * This function does not return to its internall call site, it will return directly to the external caller.\\n */\\n function _fallback() internal virtual {\\n _beforeFallback();\\n _delegate(_implementation());\\n }\\n\\n /**\\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other\\n * function in the contract matches the call data.\\n */\\n fallback() external payable virtual {\\n _fallback();\\n }\\n\\n /**\\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data\\n * is empty.\\n */\\n receive() external payable virtual {\\n _fallback();\\n }\\n\\n /**\\n * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`\\n * call, or as part of the Solidity `fallback` or `receive` functions.\\n *\\n * If overriden should call `super._beforeFallback()`.\\n */\\n function _beforeFallback() internal virtual {}\\n}\\n\",\"keccak256\":\"0xd5d1fd16e9faff7fcb3a52e02a8d49156f42a38a03f07b5f1810c21c2149a8ab\",\"license\":\"MIT\"},\"solc_0.8/openzeppelin/proxy/beacon/IBeacon.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev This is the interface that {BeaconProxy} expects of its beacon.\\n */\\ninterface IBeacon {\\n /**\\n * @dev Must return an address that can be used as a delegate call target.\\n *\\n * {BeaconProxy} will check that this address is a contract.\\n */\\n function implementation() external view returns (address);\\n}\\n\",\"keccak256\":\"0xd50a3421ac379ccb1be435fa646d66a65c986b4924f0849839f08692f39dde61\",\"license\":\"MIT\"},\"solc_0.8/openzeppelin/proxy/transparent/TransparentUpgradeableProxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/TransparentUpgradeableProxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../ERC1967/ERC1967Proxy.sol\\\";\\n\\n/**\\n * @dev This contract implements a proxy that is upgradeable by an admin.\\n *\\n * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector\\n * clashing], which can potentially be used in an attack, this contract uses the\\n * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two\\n * things that go hand in hand:\\n *\\n * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if\\n * that call matches one of the admin functions exposed by the proxy itself.\\n * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the\\n * implementation. If the admin tries to call a function on the implementation it will fail with an error that says\\n * \\\"admin cannot fallback to proxy target\\\".\\n *\\n * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing\\n * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due\\n * to sudden errors when trying to call a function from the proxy implementation.\\n *\\n * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,\\n * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.\\n */\\ncontract TransparentUpgradeableProxy is ERC1967Proxy {\\n /**\\n * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and\\n * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\\n */\\n constructor(\\n address _logic,\\n address admin_,\\n bytes memory _data\\n ) payable ERC1967Proxy(_logic, _data) {\\n assert(_ADMIN_SLOT == bytes32(uint256(keccak256(\\\"eip1967.proxy.admin\\\")) - 1));\\n _changeAdmin(admin_);\\n }\\n\\n /**\\n * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.\\n */\\n modifier ifAdmin() {\\n if (msg.sender == _getAdmin()) {\\n _;\\n } else {\\n _fallback();\\n }\\n }\\n\\n /**\\n * @dev Returns the current admin.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.\\n *\\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\\n * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\\n */\\n function admin() external ifAdmin returns (address admin_) {\\n admin_ = _getAdmin();\\n }\\n\\n /**\\n * @dev Returns the current implementation.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.\\n *\\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\\n * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`\\n */\\n function implementation() external ifAdmin returns (address implementation_) {\\n implementation_ = _implementation();\\n }\\n\\n /**\\n * @dev Changes the admin of the proxy.\\n *\\n * Emits an {AdminChanged} event.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.\\n */\\n function changeAdmin(address newAdmin) external virtual ifAdmin {\\n _changeAdmin(newAdmin);\\n }\\n\\n /**\\n * @dev Upgrade the implementation of the proxy.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\\n */\\n function upgradeTo(address newImplementation) external ifAdmin {\\n _upgradeToAndCall(newImplementation, bytes(\\\"\\\"), false);\\n }\\n\\n /**\\n * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified\\n * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the\\n * proxied contract.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.\\n */\\n function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {\\n _upgradeToAndCall(newImplementation, data, true);\\n }\\n\\n /**\\n * @dev Returns the current admin.\\n */\\n function _admin() internal view virtual returns (address) {\\n return _getAdmin();\\n }\\n\\n /**\\n * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.\\n */\\n function _beforeFallback() internal virtual override {\\n require(msg.sender != _getAdmin(), \\\"TransparentUpgradeableProxy: admin cannot fallback to proxy target\\\");\\n super._beforeFallback();\\n }\\n}\\n\",\"keccak256\":\"0x140055a64cf579d622e04f5a198595832bf2cb193cd0005f4f2d4d61ca906253\",\"license\":\"MIT\"},\"solc_0.8/openzeppelin/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x3777e696b62134e6177440dbe6e6601c0c156a443f57167194b67e75527439de\",\"license\":\"MIT\"},\"solc_0.8/openzeppelin/utils/StorageSlot.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Library for reading and writing primitive types to specific storage slots.\\n *\\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\\n * This library helps with reading and writing to such slots without the need for inline assembly.\\n *\\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\\n *\\n * Example usage to set ERC1967 implementation slot:\\n * ```\\n * contract ERC1967 {\\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n *\\n * function _getImplementation() internal view returns (address) {\\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\\n * }\\n *\\n * function _setImplementation(address newImplementation) internal {\\n * require(Address.isContract(newImplementation), \\\"ERC1967: new implementation is not a contract\\\");\\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\\n * }\\n * }\\n * ```\\n *\\n * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._\\n */\\nlibrary StorageSlot {\\n struct AddressSlot {\\n address value;\\n }\\n\\n struct BooleanSlot {\\n bool value;\\n }\\n\\n struct Bytes32Slot {\\n bytes32 value;\\n }\\n\\n struct Uint256Slot {\\n uint256 value;\\n }\\n\\n /**\\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\\n */\\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `BooleanSlot` with member `value` located at `slot`.\\n */\\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.\\n */\\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `Uint256Slot` with member `value` located at `slot`.\\n */\\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n}\\n\",\"keccak256\":\"0xfe1b7a9aa2a530a9e705b220e26cd584e2fbdc9602a3a1066032b12816b46aca\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x6080604052604051620011b2380380620011b2833981016040819052620000269162000519565b82816200005560017f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbd620005f9565b6000805160206200116b833981519152146200007557620000756200061f565b6200008382826000620000e7565b50620000b3905060017fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6104620005f9565b6000805160206200114b83398151915214620000d357620000d36200061f565b620000de8262000124565b50505062000688565b620000f2836200017f565b600082511180620001005750805b156200011f576200011d8383620001c160201b620002ff1760201c565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6200014f620001f0565b604080516001600160a01b03928316815291841660208301520160405180910390a16200017c8162000229565b50565b6200018a81620002de565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6060620001e983836040518060600160405280602781526020016200118b6027913962000381565b9392505050565b60006200021a6000805160206200114b83398151915260001b6200046760201b620002731760201c565b546001600160a01b0316919050565b6001600160a01b038116620002945760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b80620002bd6000805160206200114b83398151915260001b6200046760201b620002731760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b620002f4816200046a60201b6200032b1760201c565b620003585760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016200028b565b80620002bd6000805160206200116b83398151915260001b6200046760201b620002731760201c565b60606001600160a01b0384163b620003eb5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b60648201526084016200028b565b600080856001600160a01b03168560405162000408919062000635565b600060405180830381855af49150503d806000811462000445576040519150601f19603f3d011682016040523d82523d6000602084013e6200044a565b606091505b5090925090506200045d82828662000479565b9695505050505050565b90565b6001600160a01b03163b151590565b606083156200048a575081620001e9565b8251156200049b5782518084602001fd5b8160405162461bcd60e51b81526004016200028b919062000653565b80516001600160a01b0381168114620004cf57600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101562000507578181015183820152602001620004ed565b838111156200011d5750506000910152565b6000806000606084860312156200052f57600080fd5b6200053a84620004b7565b92506200054a60208501620004b7565b60408501519092506001600160401b03808211156200056857600080fd5b818601915086601f8301126200057d57600080fd5b815181811115620005925762000592620004d4565b604051601f8201601f19908116603f01168101908382118183101715620005bd57620005bd620004d4565b81604052828152896020848701011115620005d757600080fd5b620005ea836020830160208801620004ea565b80955050505050509250925092565b6000828210156200061a57634e487b7160e01b600052601160045260246000fd5b500390565b634e487b7160e01b600052600160045260246000fd5b6000825162000649818460208701620004ea565b9190910192915050565b602081526000825180602084015262000674816040850160208701620004ea565b601f01601f19169190910160400192915050565b610ab380620006986000396000f3fe60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100a85780638f283970146100e6578063f851a440146101065761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61011b565b005b61006b61011b565b34801561008157600080fd5b5061006b61009036600461091f565b610135565b61006b6100a336600461093a565b610196565b3480156100b457600080fd5b506100bd610221565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100f257600080fd5b5061006b61010136600461091f565b610276565b34801561011257600080fd5b506100bd6102ba565b610123610347565b61013361012e610435565b61043f565b565b61013d610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561018e5761018b816040518060200160405280600081525060006104a3565b50565b61018b61011b565b61019e610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610219576102148383838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250600192506104a3915050565b505050565b61021461011b565b600061022b610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561026b57610266610435565b905090565b61027361011b565b90565b61027e610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561018e5761018b816104ce565b60006102c4610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561026b57610266610463565b60606103248383604051806060016040528060278152602001610a576027913961052f565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b61034f610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610133576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f6574000000000000000000000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b6000610266610657565b3660008037600080366000845af43d6000803e80801561045e573d6000f35b3d6000fd5b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b5473ffffffffffffffffffffffffffffffffffffffff16919050565b6104ac8361067f565b6000825111806104b95750805b15610214576104c883836102ff565b50505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6104f7610463565b6040805173ffffffffffffffffffffffffffffffffffffffff928316815291841660208301520160405180910390a161018b816106cc565b606073ffffffffffffffffffffffffffffffffffffffff84163b6105d5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f60448201527f6e74726163740000000000000000000000000000000000000000000000000000606482015260840161042c565b6000808573ffffffffffffffffffffffffffffffffffffffff16856040516105fd91906109e9565b600060405180830381855af49150503d8060008114610638576040519150601f19603f3d011682016040523d82523d6000602084013e61063d565b606091505b509150915061064d8282866107d8565b9695505050505050565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610487565b6106888161082b565b60405173ffffffffffffffffffffffffffffffffffffffff8216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b73ffffffffffffffffffffffffffffffffffffffff811661076f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f6464726573730000000000000000000000000000000000000000000000000000606482015260840161042c565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9290921691909117905550565b606083156107e7575081610324565b8251156107f75782518084602001fd5b816040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161042c9190610a05565b73ffffffffffffffffffffffffffffffffffffffff81163b6108cf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e747261637400000000000000000000000000000000000000606482015260840161042c565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610792565b803573ffffffffffffffffffffffffffffffffffffffff8116811461091a57600080fd5b919050565b60006020828403121561093157600080fd5b610324826108f6565b60008060006040848603121561094f57600080fd5b610958846108f6565b9250602084013567ffffffffffffffff8082111561097557600080fd5b818601915086601f83011261098957600080fd5b81358181111561099857600080fd5b8760208285010111156109aa57600080fd5b6020830194508093505050509250925092565b60005b838110156109d85781810151838201526020016109c0565b838111156104c85750506000910152565b600082516109fb8184602087016109bd565b9190910192915050565b6020815260008251806020840152610a248160408501602087016109bd565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220b29caa54336b3ee836679675e9732ec5e526fb3f803cca2fe336cc3555aba62264736f6c634300080a0033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564", + "deployedBytecode": "0x60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100a85780638f283970146100e6578063f851a440146101065761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61011b565b005b61006b61011b565b34801561008157600080fd5b5061006b61009036600461091f565b610135565b61006b6100a336600461093a565b610196565b3480156100b457600080fd5b506100bd610221565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100f257600080fd5b5061006b61010136600461091f565b610276565b34801561011257600080fd5b506100bd6102ba565b610123610347565b61013361012e610435565b61043f565b565b61013d610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561018e5761018b816040518060200160405280600081525060006104a3565b50565b61018b61011b565b61019e610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610219576102148383838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250600192506104a3915050565b505050565b61021461011b565b600061022b610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561026b57610266610435565b905090565b61027361011b565b90565b61027e610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561018e5761018b816104ce565b60006102c4610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561026b57610266610463565b60606103248383604051806060016040528060278152602001610a576027913961052f565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b61034f610463565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610133576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f6574000000000000000000000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b6000610266610657565b3660008037600080366000845af43d6000803e80801561045e573d6000f35b3d6000fd5b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b5473ffffffffffffffffffffffffffffffffffffffff16919050565b6104ac8361067f565b6000825111806104b95750805b15610214576104c883836102ff565b50505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6104f7610463565b6040805173ffffffffffffffffffffffffffffffffffffffff928316815291841660208301520160405180910390a161018b816106cc565b606073ffffffffffffffffffffffffffffffffffffffff84163b6105d5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f60448201527f6e74726163740000000000000000000000000000000000000000000000000000606482015260840161042c565b6000808573ffffffffffffffffffffffffffffffffffffffff16856040516105fd91906109e9565b600060405180830381855af49150503d8060008114610638576040519150601f19603f3d011682016040523d82523d6000602084013e61063d565b606091505b509150915061064d8282866107d8565b9695505050505050565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610487565b6106888161082b565b60405173ffffffffffffffffffffffffffffffffffffffff8216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b73ffffffffffffffffffffffffffffffffffffffff811661076f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f6464726573730000000000000000000000000000000000000000000000000000606482015260840161042c565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9290921691909117905550565b606083156107e7575081610324565b8251156107f75782518084602001fd5b816040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161042c9190610a05565b73ffffffffffffffffffffffffffffffffffffffff81163b6108cf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e747261637400000000000000000000000000000000000000606482015260840161042c565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610792565b803573ffffffffffffffffffffffffffffffffffffffff8116811461091a57600080fd5b919050565b60006020828403121561093157600080fd5b610324826108f6565b60008060006040848603121561094f57600080fd5b610958846108f6565b9250602084013567ffffffffffffffff8082111561097557600080fd5b818601915086601f83011261098957600080fd5b81358181111561099857600080fd5b8760208285010111156109aa57600080fd5b6020830194508093505050509250925092565b60005b838110156109d85781810151838201526020016109c0565b838111156104c85750506000910152565b600082516109fb8184602087016109bd565b9190910192915050565b6020815260008251806020840152610a248160408501602087016109bd565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220b29caa54336b3ee836679675e9732ec5e526fb3f803cca2fe336cc3555aba62264736f6c634300080a0033", + "devdoc": { + "details": "This contract implements a proxy that is upgradeable by an admin. To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector clashing], which can potentially be used in an attack, this contract uses the https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two things that go hand in hand: 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if that call matches one of the admin functions exposed by the proxy itself. 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the implementation. If the admin tries to call a function on the implementation it will fail with an error that says \"admin cannot fallback to proxy target\". These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to call a function from the proxy implementation. Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.", + "kind": "dev", + "methods": { + "admin()": { + "details": "Returns the current admin. NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}. TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`" + }, + "changeAdmin(address)": { + "details": "Changes the admin of the proxy. Emits an {AdminChanged} event. NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}." + }, + "constructor": { + "details": "Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}." + }, + "implementation()": { + "details": "Returns the current implementation. NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}. TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`" + }, + "upgradeTo(address)": { + "details": "Upgrade the implementation of the proxy. NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}." + }, + "upgradeToAndCall(address,bytes)": { + "details": "Upgrade the implementation of the proxy, and then call a function from the new implementation as specified by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the proxied contract. NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + }, + "storageLayout": { + "storage": [], + "types": null + } +} diff --git a/deployments/bscmainnet/solcInputs/b16b9fd9d739d9f7b43142447745f751.json b/deployments/bscmainnet/solcInputs/b16b9fd9d739d9f7b43142447745f751.json new file mode 100644 index 000000000..becbb4ee8 --- /dev/null +++ b/deployments/bscmainnet/solcInputs/b16b9fd9d739d9f7b43142447745f751.json @@ -0,0 +1,142 @@ +{ + "language": "Solidity", + "sources": { + "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./OwnableUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {\n address private _pendingOwner;\n\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n function __Ownable2Step_init() internal onlyInitializing {\n __Ownable_init_unchained();\n }\n\n function __Ownable2Step_init_unchained() internal onlyInitializing {\n }\n /**\n * @dev Returns the address of the pending owner.\n */\n function pendingOwner() public view virtual returns (address) {\n return _pendingOwner;\n }\n\n /**\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual override onlyOwner {\n _pendingOwner = newOwner;\n emit OwnershipTransferStarted(owner(), newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual override {\n delete _pendingOwner;\n super._transferOwnership(newOwner);\n }\n\n /**\n * @dev The new owner accepts the ownership transfer.\n */\n function acceptOwnership() public virtual {\n address sender = _msgSender();\n require(pendingOwner() == sender, \"Ownable2Step: caller is not the new owner\");\n _transferOwnership(sender);\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/ContextUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n function __Ownable_init() internal onlyInitializing {\n __Ownable_init_unchained();\n }\n\n function __Ownable_init_unchained() internal onlyInitializing {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)\n\npragma solidity ^0.8.2;\n\nimport \"../../utils/AddressUpgradeable.sol\";\n\n/**\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\n *\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\n * reused. This mechanism prevents re-execution of each \"step\" but allows the creation of new initialization steps in\n * case an upgrade adds a module that needs to be initialized.\n *\n * For example:\n *\n * [.hljs-theme-light.nopadding]\n * ```solidity\n * contract MyToken is ERC20Upgradeable {\n * function initialize() initializer public {\n * __ERC20_init(\"MyToken\", \"MTK\");\n * }\n * }\n *\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\n * function initializeV2() reinitializer(2) public {\n * __ERC20Permit_init(\"MyToken\");\n * }\n * }\n * ```\n *\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\n *\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\n *\n * [CAUTION]\n * ====\n * Avoid leaving a contract uninitialized.\n *\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * /// @custom:oz-upgrades-unsafe-allow constructor\n * constructor() {\n * _disableInitializers();\n * }\n * ```\n * ====\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n * @custom:oz-retyped-from bool\n */\n uint8 private _initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private _initializing;\n\n /**\n * @dev Triggered when the contract has been initialized or reinitialized.\n */\n event Initialized(uint8 version);\n\n /**\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\n * `onlyInitializing` functions can be used to initialize parent contracts.\n *\n * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a\n * constructor.\n *\n * Emits an {Initialized} event.\n */\n modifier initializer() {\n bool isTopLevelCall = !_initializing;\n require(\n (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),\n \"Initializable: contract is already initialized\"\n );\n _initialized = 1;\n if (isTopLevelCall) {\n _initializing = true;\n }\n _;\n if (isTopLevelCall) {\n _initializing = false;\n emit Initialized(1);\n }\n }\n\n /**\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\n * used to initialize parent contracts.\n *\n * A reinitializer may be used after the original initialization step. This is essential to configure modules that\n * are added through upgrades and that require initialization.\n *\n * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`\n * cannot be nested. If one is invoked in the context of another, execution will revert.\n *\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\n * a contract, executing them in the right order is up to the developer or operator.\n *\n * WARNING: setting the version to 255 will prevent any future reinitialization.\n *\n * Emits an {Initialized} event.\n */\n modifier reinitializer(uint8 version) {\n require(!_initializing && _initialized < version, \"Initializable: contract is already initialized\");\n _initialized = version;\n _initializing = true;\n _;\n _initializing = false;\n emit Initialized(version);\n }\n\n /**\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\n */\n modifier onlyInitializing() {\n require(_initializing, \"Initializable: contract is not initializing\");\n _;\n }\n\n /**\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\n * through proxies.\n *\n * Emits an {Initialized} event the first time it is successfully executed.\n */\n function _disableInitializers() internal virtual {\n require(!_initializing, \"Initializable: contract is initializing\");\n if (_initialized != type(uint8).max) {\n _initialized = type(uint8).max;\n emit Initialized(type(uint8).max);\n }\n }\n\n /**\n * @dev Returns the highest version that has been initialized. See {reinitializer}.\n */\n function _getInitializedVersion() internal view returns (uint8) {\n return _initialized;\n }\n\n /**\n * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.\n */\n function _isInitializing() internal view returns (bool) {\n return _initializing;\n }\n}\n" + }, + "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)\n\npragma solidity ^0.8.0;\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuardUpgradeable is Initializable {\n // Booleans are more expensive than uint256 or any type that takes up a full\n // word because each write operation emits an extra SLOAD to first read the\n // slot's contents, replace the bits taken up by the boolean, and then write\n // back. This is the compiler's defense against contract upgrades and\n // pointer aliasing, and it cannot be disabled.\n\n // The values being non-zero value makes deployment a bit more expensive,\n // but in exchange the refund on every call to nonReentrant will be lower in\n // amount. Since refunds are capped to a percentage of the total\n // transaction's gas, it is best to keep them low in cases like this one, to\n // increase the likelihood of the full refund coming into effect.\n uint256 private constant _NOT_ENTERED = 1;\n uint256 private constant _ENTERED = 2;\n\n uint256 private _status;\n\n function __ReentrancyGuard_init() internal onlyInitializing {\n __ReentrancyGuard_init_unchained();\n }\n\n function __ReentrancyGuard_init_unchained() internal onlyInitializing {\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and making it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n _nonReentrantBefore();\n _;\n _nonReentrantAfter();\n }\n\n function _nonReentrantBefore() private {\n // On the first call to nonReentrant, _status will be _NOT_ENTERED\n require(_status != _ENTERED, \"ReentrancyGuard: reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n _status = _ENTERED;\n }\n\n function _nonReentrantAfter() private {\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev Returns true if the reentrancy guard is currently set to \"entered\", which indicates there is a\n * `nonReentrant` function in the call stack.\n */\n function _reentrancyGuardEntered() internal view returns (bool) {\n return _status == _ENTERED;\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n *\n * ==== Security Considerations\n *\n * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature\n * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be\n * considered as an intention to spend the allowance in any specific way. The second is that because permits have\n * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should\n * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be\n * generally recommended is:\n *\n * ```solidity\n * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {\n * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}\n * doThing(..., value);\n * }\n *\n * function doThing(..., uint256 value) public {\n * token.safeTransferFrom(msg.sender, address(this), value);\n * ...\n * }\n * ```\n *\n * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of\n * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also\n * {SafeERC20-safeTransferFrom}).\n *\n * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so\n * contracts should have entry points that don't rely on permit.\n */\ninterface IERC20PermitUpgradeable {\n /**\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\n * given ``owner``'s signed approval.\n *\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\n * ordering also apply here.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `deadline` must be a timestamp in the future.\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\n * over the EIP712-formatted function arguments.\n * - the signature must use ``owner``'s current nonce (see {nonces}).\n *\n * For more information on the signature format, see the\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\n * section].\n *\n * CAUTION: See Security Considerations above.\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n /**\n * @dev Returns the current nonce for `owner`. This value must be\n * included whenever a signature is generated for {permit}.\n *\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\n * prevents a signature from being used multiple times.\n */\n function nonces(address owner) external view returns (uint256);\n\n /**\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n}\n" + }, + "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20Upgradeable {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20Upgradeable.sol\";\nimport \"../extensions/IERC20PermitUpgradeable.sol\";\nimport \"../../../utils/AddressUpgradeable.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20Upgradeable {\n using AddressUpgradeable for address;\n\n /**\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n */\n function safeTransfer(IERC20Upgradeable token, address to, uint256 value) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n /**\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\n */\n function safeTransferFrom(IERC20Upgradeable token, address from, address to, uint256 value) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(IERC20Upgradeable token, address spender, uint256 value) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n /**\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n */\n function safeIncreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {\n uint256 oldAllowance = token.allowance(address(this), spender);\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));\n }\n\n /**\n * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n */\n function safeDecreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));\n }\n }\n\n /**\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\n * to be set to zero before setting it to a non-zero value, such as USDT.\n */\n function forceApprove(IERC20Upgradeable token, address spender, uint256 value) internal {\n bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);\n\n if (!_callOptionalReturnBool(token, approvalCall)) {\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));\n _callOptionalReturn(token, approvalCall);\n }\n }\n\n /**\n * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.\n * Revert on invalid signature.\n */\n function safePermit(\n IERC20PermitUpgradeable token,\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal {\n uint256 nonceBefore = token.nonces(owner);\n token.permit(owner, spender, value, deadline, v, r, s);\n uint256 nonceAfter = token.nonces(owner);\n require(nonceAfter == nonceBefore + 1, \"SafeERC20: permit did not succeed\");\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20Upgradeable token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n require(returndata.length == 0 || abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n *\n * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.\n */\n function _callOptionalReturnBool(IERC20Upgradeable token, bytes memory data) private returns (bool) {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false\n // and not revert is the subcall reverts.\n\n (bool success, bytes memory returndata) = address(token).call(data);\n return\n success && (returndata.length == 0 || abi.decode(returndata, (bool))) && AddressUpgradeable.isContract(address(token));\n }\n}\n" + }, + "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary AddressUpgradeable {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n *\n * Furthermore, `isContract` will also return true if the target contract within\n * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,\n * which only has an effect at the end of a transaction.\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\n *\n * _Available since v4.8._\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n if (success) {\n if (returndata.length == 0) {\n // only check isContract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n require(isContract(target), \"Address: call to non-contract\");\n }\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason or using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n}\n" + }, + "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\n\npragma solidity ^0.8.0;\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract ContextUpgradeable is Initializable {\n function __Context_init() internal onlyInitializing {\n }\n\n function __Context_init_unchained() internal onlyInitializing {\n }\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[50] private __gap;\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC20.sol\";\nimport \"./extensions/IERC20Metadata.sol\";\nimport \"../../utils/Context.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC20\n * applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is Context, IERC20, IERC20Metadata {\n mapping(address => uint256) private _balances;\n\n mapping(address => mapping(address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * All two of these values are immutable: they can only be set once during\n * construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual override returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual override returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the default value returned by this function, unless\n * it's overridden.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual override returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address to, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `amount`.\n */\n function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, amount);\n _transfer(from, to, amount);\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, allowance(owner, spender) + addedValue);\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n address owner = _msgSender();\n uint256 currentAllowance = allowance(owner, spender);\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\n unchecked {\n _approve(owner, spender, currentAllowance - subtractedValue);\n }\n\n return true;\n }\n\n /**\n * @dev Moves `amount` of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n */\n function _transfer(address from, address to, uint256 amount) internal virtual {\n require(from != address(0), \"ERC20: transfer from the zero address\");\n require(to != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(from, to, amount);\n\n uint256 fromBalance = _balances[from];\n require(fromBalance >= amount, \"ERC20: transfer amount exceeds balance\");\n unchecked {\n _balances[from] = fromBalance - amount;\n // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by\n // decrementing then incrementing.\n _balances[to] += amount;\n }\n\n emit Transfer(from, to, amount);\n\n _afterTokenTransfer(from, to, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply += amount;\n unchecked {\n // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.\n _balances[account] += amount;\n }\n emit Transfer(address(0), account, amount);\n\n _afterTokenTransfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n uint256 accountBalance = _balances[account];\n require(accountBalance >= amount, \"ERC20: burn amount exceeds balance\");\n unchecked {\n _balances[account] = accountBalance - amount;\n // Overflow not possible: amount <= accountBalance <= totalSupply.\n _totalSupply -= amount;\n }\n\n emit Transfer(account, address(0), amount);\n\n _afterTokenTransfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(address owner, address spender, uint256 amount) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Updates `owner` s allowance for `spender` based on spent `amount`.\n *\n * Does not update the allowance amount in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Might emit an {Approval} event.\n */\n function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance != type(uint256).max) {\n require(currentAllowance >= amount, \"ERC20: insufficient allowance\");\n unchecked {\n _approve(owner, spender, currentAllowance - amount);\n }\n }\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}\n\n /**\n * @dev Hook that is called after any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * has been transferred to `to`.\n * - when `from` is zero, `amount` tokens have been minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC20 standard.\n *\n * _Available since v4.1._\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n" + }, + "@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nimport \"@openzeppelin/contracts/access/IAccessControl.sol\";\n\n/**\n * @title IAccessControlManagerV8\n * @author Venus\n * @notice Interface implemented by the `AccessControlManagerV8` contract.\n */\ninterface IAccessControlManagerV8 is IAccessControl {\n function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external;\n\n function revokeCallPermission(\n address contractAddress,\n string calldata functionSig,\n address accountToRevoke\n ) external;\n\n function isAllowedToCall(address account, string calldata functionSig) external view returns (bool);\n\n function hasPermission(\n address account,\n address contractAddress,\n string calldata functionSig\n ) external view returns (bool);\n}\n" + }, + "@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IDeviationBoundedOracle {\n // --- Enums ---\n\n /// @notice Identifies whether a price bound is a minimum or maximum\n enum PriceBoundType {\n MIN,\n MAX\n }\n\n /// @notice Identifies which keeper action a single syncPriceBoundsAndProtections item performs\n enum KeeperAction {\n SetMinPrice,\n SetMaxPrice,\n ExitProtectionMode\n }\n\n // --- Structs ---\n\n /// @notice Per-asset protection state tracking the min/max price window\n struct MarketProtectionState {\n /// @notice Lowest price observed in the current window (packed with maxPrice in one slot)\n uint128 minPrice;\n /// @notice Highest price observed in the current window\n uint128 maxPrice;\n /// @notice Whether protected price is currently being used\n bool currentlyUsingProtectedPrice;\n /// @notice Whether this market is whitelisted for bounded pricing\n bool isBoundedPricingEnabled;\n /// @notice Timestamp of the last protection trigger — reset on every trigger\n uint64 lastProtectionTriggeredAt;\n /// @notice Minimum time protection stays active after last trigger\n uint64 cooldownPeriod;\n /// @notice The underlying asset address, used to verify initialization\n address asset;\n /// @notice Entry deviation threshold (mantissa, e.g. 0.1667e18 = 16.67%); packed with resetThreshold\n uint128 triggerThreshold;\n /// @notice Exit threshold (mantissa); window must converge below this for protection to be disabled\n uint128 resetThreshold;\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\n bool cachingEnabled;\n }\n\n /// @notice One item in an syncPriceBoundsAndProtections payload\n /// @dev `value` is interpreted per-action: the new bound price for SetMinPrice / SetMaxPrice, ignored for ExitProtectionMode\n struct KeeperActionItem {\n address asset;\n KeeperAction action;\n uint256 value;\n }\n\n /// @notice One item in a setTokenConfigs payload\n struct TokenConfigInput {\n /// @notice The underlying asset address\n address asset;\n /// @notice Minimum time protection stays active after the last trigger (seconds)\n uint64 cooldownPeriod;\n /// @notice Entry deviation threshold (mantissa). Must be between 5% and 50%.\n uint256 triggerThreshold;\n /// @notice Exit deviation threshold (mantissa). Must be non-zero and below triggerThreshold.\n uint256 resetThreshold;\n /// @notice Whether to enable bounded pricing immediately upon initialization\n bool enableBoundedPricing;\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\n bool enableCaching;\n }\n\n // --- Events ---\n\n /// @notice Emitted when protection is initialized for an asset\n event ProtectionInitialized(\n address indexed asset,\n uint128 minPrice,\n uint128 maxPrice,\n uint64 cooldownPeriod,\n uint256 triggerThreshold\n );\n\n /// @notice Emitted when protection mode is triggered for an asset\n event ProtectionTriggered(address indexed asset, uint256 spotPrice, uint128 minPrice, uint128 maxPrice);\n\n /// @notice Emitted when protection mode is disabled for an asset\n event ProtectionModeExited(address indexed asset);\n\n /// @notice Emitted when the keeper updates the minimum price for an asset\n event MinPriceUpdated(address indexed asset, uint128 oldMin, uint128 newMin);\n\n /// @notice Emitted when the keeper updates the maximum price for an asset\n event MaxPriceUpdated(address indexed asset, uint128 oldMax, uint128 newMax);\n\n /// @notice Emitted when the entry threshold is updated for an asset\n event TriggerThresholdSet(address indexed asset, uint256 oldThreshold, uint256 newThreshold);\n\n /// @notice Emitted when the exit threshold is updated for an asset\n event ResetThresholdSet(address indexed asset, uint256 oldExitThreshold, uint256 newExitThreshold);\n\n /// @notice Emitted when the cooldown period is updated for an asset\n event CooldownPeriodSet(address indexed asset, uint64 oldCooldown, uint64 newCooldown);\n\n /// @notice Emitted when an asset's whitelist status changes\n event BoundedPricingWhitelistUpdated(address indexed asset, bool whitelisted);\n\n /// @notice Emitted when the per-asset transient caching flag is toggled\n event CachingEnabledUpdated(address indexed asset, bool oldEnabled, bool newEnabled);\n\n // --- Errors ---\n\n /// @notice Thrown when trying to use or update protection for an asset that has not been initialized\n error MarketNotInitialized(address asset);\n\n /// @notice Thrown when trying to initialize an already initialized market\n error MarketAlreadyInitialized(address asset);\n\n /// @notice Thrown when trying to disable protection that is not active\n error ProtectedPriceInactive(address asset);\n\n /// @notice Thrown when trying to disable protection before cooldown has elapsed\n error CooldownNotElapsed(address asset, uint64 lastProtectionTriggeredAt, uint64 cooldownPeriod);\n\n /// @notice Thrown when trying to disable protection before price range has converged\n error PriceRangeNotConverged(address asset, uint256 currentRangeRatio, uint256 resetThreshold);\n\n /// @notice Thrown when keeper tries to set minPrice above current spot\n error InvalidMinPrice(address asset, uint128 newMin, uint256 currentSpot);\n\n /// @notice Thrown when keeper tries to set maxPrice below current spot\n error InvalidMaxPrice(address asset, uint128 newMax, uint256 currentSpot);\n\n /// @notice Thrown when threshold is set below the minimum allowed value\n error ThresholdBelowMinimum(uint256 threshold, uint256 minimum);\n\n /// @notice Thrown when threshold is set above the maximum allowed value\n error ThresholdAboveMaximum(uint256 threshold, uint256 maximum);\n\n /// @notice Thrown when a price exceeds uint128 max\n error PriceExceedsUint128(uint256 price);\n\n /// @notice Thrown when a zero price is provided where a non-zero price is required\n error ZeroPriceNotAllowed();\n\n /// @notice Thrown when trying to initialize protection for VAI\n error VAINotAllowed();\n\n /// @notice Thrown when trying to disable bounded pricing for an asset while protection is active\n error ProtectedPriceActive(address asset);\n\n /// @notice Thrown when the lengths of the arrays are not equal\n error InvalidArrayLength();\n\n /// @notice Thrown when the exit threshold is set at or above the trigger threshold\n error InvalidResetThreshold(uint256 resetThreshold);\n\n /// @notice Thrown when an syncPriceBoundsAndProtections item carries an unsupported action enum value\n error InvalidKeeperAction(uint8 action);\n\n // --- Non-view price functions (update window + trigger protection) ---\n\n /**\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice);\n\n /**\n * @notice Gets the bounded debt price for a given vToken, updating protection state\n * @param vToken vToken address\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice);\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice);\n\n // --- State update (call before view price reads to populate transient cache) ---\n\n /**\n * @notice Updates the protection state for a given vToken, caching the resolved collateral and debt prices\n * @dev Called by PolicyFacet before liquidity calculations so subsequent view price\n * reads in the same transaction are served from transient storage. The transient\n * cache is only populated when the asset's `cachingEnabled` flag is `true`.\n * @param vToken vToken address\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function updateProtectionState(address vToken) external;\n\n // --- View price functions (read stored/cached state only) ---\n\n /**\n * @notice Gets the bounded collateral price for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\n * falls back to ResilientOracle on cache miss or when caching is disabled.\n * @param vToken vToken address\n * @return price The bounded collateral price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 price);\n\n /**\n * @notice Gets the bounded debt price for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\n * falls back to ResilientOracle on cache miss or when caching is disabled.\n * @param vToken vToken address\n * @return price The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 price);\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\n * falls back to ResilientOracle on cache miss or when caching is disabled.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice);\n\n // --- Keeper functions ---\n\n /**\n * @notice Updates the minimum price in the rolling window for a given asset\n * @param asset The underlying asset address\n * @param newMin The new minimum price; must be at or below the current spot and below maxPrice\n * @custom:access Only authorized keeper addresses\n * @custom:error ZeroPriceNotAllowed if newMin is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMinPrice if newMin exceeds the current spot or is at or above maxPrice\n * @custom:event MinPriceUpdated\n */\n function updateMinPrice(address asset, uint128 newMin) external;\n\n /**\n * @notice Updates the maximum price in the rolling window for a given asset\n * @param asset The underlying asset address\n * @param newMax The new maximum price; must be at or above the current spot and above minPrice\n * @custom:access Only authorized keeper addresses\n * @custom:error ZeroPriceNotAllowed if newMax is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMaxPrice if newMax is below the current spot or is at or below minPrice\n * @custom:event MaxPriceUpdated\n */\n function updateMaxPrice(address asset, uint128 newMax) external;\n\n /**\n * @notice Exits protection mode for a given asset once conditions are met\n * @param asset The underlying asset address\n * @custom:access Only authorized monitor/keeper addresses\n * @custom:error ProtectedPriceInactive if protection is not currently active\n * @custom:error CooldownNotElapsed if the cooldown period has not elapsed since the last trigger\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\n * @custom:event ProtectionModeExited\n */\n function exitProtectionMode(address asset) external;\n\n /**\n * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\n * @dev Each item is processed in array order; any item revert rolls back the whole batch.\n * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode.\n * Empty `actions` is a no-op success.\n * @param actions The list of keeper actions to apply\n * @custom:access Only authorized keeper addresses\n * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value\n * @custom:error PriceExceedsUint128 if a SetMin/SetMax item value overflows uint128\n * @custom:error ZeroPriceNotAllowed if a SetMin/SetMax item value is zero\n * @custom:error MarketNotInitialized if any referenced asset has not been initialized\n * @custom:error InvalidMinPrice if a SetMinPrice item violates the spot/maxPrice constraints\n * @custom:error InvalidMaxPrice if a SetMaxPrice item violates the spot/minPrice constraints\n * @custom:error ProtectedPriceInactive if an ExitProtectionMode item targets an asset whose protection is not active\n * @custom:error CooldownNotElapsed if an ExitProtectionMode item is submitted before cooldown elapsed\n * @custom:error PriceRangeNotConverged if an ExitProtectionMode item is submitted before window convergence\n * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\n */\n function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external;\n\n // --- Admin functions (governance-gated) ---\n\n /**\n * @notice Initializes protection parameters for a new asset\n * @dev Seeds the initial min/max window from the current ResilientOracle spot price,\n * confirming the oracle is live for this asset before it is listed.\n * @param tokenConfig_ Token config input for the asset\n * @custom:access Only Governance\n * @custom:error ZeroAddressNotAllowed if asset is the zero address\n * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\n * @custom:error VAINotAllowed if asset is the VAI token\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event ProtectionInitialized\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setTokenConfig(TokenConfigInput calldata tokenConfig_) external;\n\n /**\n * @notice Batch-initializes protection parameters for multiple assets in a single transaction\n * @param tokenConfigs_ Array of token config inputs, one per asset\n * @custom:access Only Governance\n * @custom:error InvalidArrayLength if the input array is empty\n * @custom:error ZeroAddressNotAllowed if any asset is the zero address\n * @custom:error ZeroValueNotAllowed if any cooldownPeriod, triggerThreshold, or resetThreshold is zero\n * @custom:error MarketAlreadyInitialized if any asset has already been initialized\n * @custom:error ThresholdBelowMinimum if any triggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if any triggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if any resetThreshold is at or above its triggerThreshold\n * @custom:error VAINotAllowed if any asset is the VAI token\n * @custom:error PriceExceedsUint128 if the spot price for any asset overflows uint128\n * @custom:event ProtectionInitialized for each asset\n * @custom:event BoundedPricingWhitelistUpdated for each asset\n */\n function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external;\n\n /**\n * @notice Sets the cooldown period for an asset\n * @param asset The underlying asset address\n * @param newCooldown The new cooldown period in seconds; must be non-zero\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:event CooldownPeriodSet\n */\n function setCooldownPeriod(address asset, uint64 newCooldown) external;\n\n /**\n * @notice Sets the trigger and reset thresholds for an asset\n * @param asset The underlying asset address\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\n * @custom:event TriggerThresholdSet if the trigger threshold changed\n * @custom:event ResetThresholdSet if the reset threshold changed\n */\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external;\n\n /**\n * @notice Sets whether bounded pricing is enabled for an asset\n * @param asset The underlying asset address\n * @param enabled Whether bounded pricing should be enabled for the asset\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external;\n\n /**\n * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset\n * @dev When disabled, each view/non-view price call recomputes bounded prices from the\n * live spot instead of reading or writing the transient slots. The initial value is\n * set via the `enableCaching` argument of `setTokenConfig`.\n * @param asset The underlying asset address\n * @param enabled Whether transient caching is enabled for this asset\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:event CachingEnabledUpdated\n */\n function setCachingEnabled(address asset, bool enabled) external;\n\n // --- View helpers ---\n\n /**\n * @notice Returns the full protection state for an asset\n * @param asset The underlying asset address\n * @return minPrice Lowest price observed in the current window\n * @return maxPrice Highest price observed in the current window\n * @return currentlyUsingProtectedPrice Whether protected price is currently active\n * @return isBoundedPricingEnabled Whether the asset is whitelisted for bounded pricing\n * @return lastProtectionTriggeredAt Timestamp of the last protection trigger\n * @return cooldownPeriod Minimum time protection stays active after last trigger\n * @return assetAddr The underlying asset address stored in the struct\n * @return triggerThreshold Entry deviation threshold (mantissa) that activates protection\n * @return resetThreshold Exit deviation threshold (mantissa) below which protection can be disabled\n * @return cachingEnabled Whether transient caching of the bounded pair is enabled for the asset\n */\n function assetProtectionConfig(\n address asset\n )\n external\n view\n returns (\n uint128 minPrice,\n uint128 maxPrice,\n bool currentlyUsingProtectedPrice,\n bool isBoundedPricingEnabled,\n uint64 lastProtectionTriggeredAt,\n uint64 cooldownPeriod,\n address assetAddr,\n uint128 triggerThreshold,\n uint128 resetThreshold,\n bool cachingEnabled\n );\n\n /**\n * @notice Checks if an asset is whitelisted for bounded pricing\n * @param asset The underlying asset address\n * @return True if the asset is whitelisted\n */\n function isBoundedPricingEnabled(address asset) external view returns (bool);\n\n /**\n * @notice Checks if the asset is currently using the protected (bounded) price\n * @param asset The underlying asset address\n * @return True if the asset is currently using the protected price instead of spot\n */\n function currentlyUsingProtectedPrice(address asset) external view returns (bool);\n\n /**\n * @notice Checks if protection can be exited for a given asset\n * @param asset The underlying asset address\n * @return True if both the cooldown has elapsed and the price range has converged below the exit threshold\n */\n function canExitProtection(address asset) external view returns (bool);\n\n /**\n * @notice Returns the initialized asset at the given index (auto-generated array getter)\n * @param index Array index\n * @return The asset address at the given index\n */\n function allAssets(uint256 index) external view returns (address);\n\n /**\n * @notice Returns all currently whitelisted asset addresses\n * @return result Array of whitelisted asset addresses\n */\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory result);\n\n /**\n * @notice Returns all asset addresses that have ever been initialized\n * @return Array of all initialized asset addresses\n */\n function getInitializedAssets() external view returns (address[] memory);\n\n /**\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the keeper deadband\n * @param assets Array of asset addresses to check\n * @param proposedMins Keeper's proposed window minimum prices\n * @param proposedMaxs Keeper's proposed window maximum prices\n * @return needsMinUpdate Whether minPrice drift exceeds the deadband for each asset\n * @return needsMaxUpdate Whether maxPrice drift exceeds the deadband for each asset\n * @custom:error InvalidArrayLength if the input array lengths do not match\n */\n function checkAndGetWindowDrift(\n address[] calldata assets,\n uint128[] calldata proposedMins,\n uint128[] calldata proposedMaxs\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate);\n}\n" + }, + "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface OracleInterface {\n function getPrice(address asset) external view returns (uint256);\n}\n\ninterface ResilientOracleInterface is OracleInterface {\n function updatePrice(address vToken) external;\n\n function updateAssetPrice(address asset) external;\n\n function getUnderlyingPrice(address vToken) external view returns (uint256);\n}\n\ninterface BoundValidatorInterface {\n function validatePriceWithAnchorPrice(\n address asset,\n uint256 reporterPrice,\n uint256 anchorPrice\n ) external view returns (bool);\n}\n" + }, + "@venusprotocol/solidity-utilities/contracts/constants.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\n/// @dev Base unit for computations, usually used in scaling (multiplications, divisions)\nuint256 constant EXP_SCALE = 1e18;\n\n/// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions\nuint256 constant MANTISSA_ONE = EXP_SCALE;\n\n/// @dev The approximate number of seconds per year\nuint256 constant SECONDS_PER_YEAR = 31_536_000;\n" + }, + "@venusprotocol/solidity-utilities/contracts/validators.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n/// @notice Thrown if the supplied address is a zero address where it is not allowed\nerror ZeroAddressNotAllowed();\n\n/// @notice Thrown if the supplied value is 0 where it is not allowed\nerror ZeroValueNotAllowed();\n\n/// @notice Checks if the provided address is nonzero, reverts otherwise\n/// @param address_ Address to check\n/// @custom:error ZeroAddressNotAllowed is thrown if the provided address is a zero address\nfunction ensureNonzeroAddress(address address_) pure {\n if (address_ == address(0)) {\n revert ZeroAddressNotAllowed();\n }\n}\n\n/// @notice Checks if the provided value is nonzero, reverts otherwise\n/// @param value_ Value to check\n/// @custom:error ZeroValueNotAllowed is thrown if the provided value is 0\nfunction ensureNonzeroValue(uint256 value_) pure {\n if (value_ == 0) {\n revert ZeroValueNotAllowed();\n }\n}\n" + }, + "contracts/BStock/BStockLiquidator.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IERC20Upgradeable } from \"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\";\nimport { SafeERC20Upgradeable } from \"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\";\nimport { Ownable2StepUpgradeable } from \"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\";\nimport { ReentrancyGuardUpgradeable } from \"@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\n\nimport { VToken } from \"../Tokens/VTokens/VToken.sol\";\nimport { IFlashLoanReceiver } from \"../FlashLoan/interfaces/IFlashLoanReceiver.sol\";\nimport { IWBNB } from \"../external/IWBNB.sol\";\n\n// Shared Venus interfaces: IComptroller (Core diamond — liquidator gate + flash loan), IVToken\n// (flash-loan asset array element), and ILiquidator (the pool-wide Venus Liquidator gate that pulls\n// the repay and returns our share of the seized collateral).\nimport { IComptroller, IVToken, ILiquidator } from \"../InterfacesV8.sol\";\nimport { IBStockLiquidator } from \"./IBStockLiquidator.sol\";\n\n/**\n * @title BStockLiquidator\n * @author Venus\n * @notice Atomic backstop liquidator for bStock (ERC-8056 tokenized stock) collateral.\n *\n * In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken,\n * redeems it to the raw bStock, and sells that bStock to the debt asset in one or two hops. Hop 1\n * always sells bStock through the Native RFQ router using a pre-fetched, MM-signed firm-quote\n * `txRequest`. For USDT debt that single hop lands the debt asset directly. For non-USDT debt an\n * OPTIONAL hop 2 (Native quotes bStock->USDT only) converts the USDT to the debt asset through a\n * second allowlisted router (an AMM/aggregator). Because seize and sell happen in the same tx there\n * is no price-drift window, and the realized debt-asset amount must clear `minOut` or the whole call\n * reverts — the protocol never ends up holding the RFQ-only asset or the intermediate.\n *\n * Two funding modes share the same core (`_liquidate`):\n * - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance.\n * - FLASH: the debt asset is flash-borrowed from Venus (`Comptroller.executeFlashLoan`) and repaid\n * (+ premium) within the same tx; no capital is locked in the contract.\n *\n * Native BNB debt (vBNB): supported in both modes with WBNB as the debt-accounting token. The repay\n * must be native BNB, so exactly the repay amount of WBNB is unwrapped and forwarded to the gate's\n * payable path; the two-hop swap lands WBNB (bStock->USDT->WBNB) and `minOut` is measured in WBNB\n * (1:1 with BNB). FLASH mode borrows from vWBNB, NOT vBNB: vBNB cannot be flash-repaid (its\n * `doTransferIn` requires `msg.value`), whereas vWBNB's underlying is a plain ERC20.\n *\n * Ownership / scope: this is Venus's OWN backstop tool, NOT a public utility — `liquidate` and\n * `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock\n * liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus\n * path with their own funds and their own offload. This contract is intentionally gated because it\n * custodies funds (debt-asset inventory / flash principal) and forwards a CALLER-SUPPLIED calldata blob to\n * an external router — the swap's recipient (`to`) lives inside that calldata, so an open entrypoint\n * would let anyone route the proceeds to themselves and drain the contract. The router allowlist and\n * `minOut` bound the blast radius but cannot replace operator-gating.\n *\n * Security model:\n * - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator).\n * - BOTH swap targets (`router` and, when set, `router2`) must be allowlisted (`isRouter`) — defends\n * the low-level `router.call(swapCalldata)` on each hop.\n * - the approval to each router is the exact amount being sold on that hop, reset to 0 afterwards\n * (bStock on hop 1; the measured intermediate balance delta on hop 2, so pre-existing inventory\n * is never exposed).\n * - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started).\n * - the realized debt-asset amount must clear `minOut` or the tx reverts.\n *\n * Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`,\n * which is always configured on the networks this contract targets. While it is set, a direct\n * `vToken.liquidateBorrow` from an arbitrary caller reverts UNAUTHORIZED, so this contract reads the gate\n * at call time and routes the repay through that Venus Liquidator (the permissionless entry anyone may\n * call), reverting if the gate is ever unset. Routing through the gate needs no governance change, and no\n * other Core market is affected. Note: setting THIS contract as `liquidatorContract` is NOT an option —\n * the gate is pool-wide, so every other market's liquidations would be forced through here.\n */\ncontract BStockLiquidator is\n Ownable2StepUpgradeable,\n ReentrancyGuardUpgradeable,\n IBStockLiquidator,\n IFlashLoanReceiver\n{\n using SafeERC20Upgradeable for IERC20Upgradeable;\n\n /// @notice Core Comptroller (diamond): reads the liquidation gate and provides the flash loan\n /// via `executeFlashLoan`.\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n IComptroller public immutable comptroller;\n\n /// @notice The native BNB market (vBNB). A debt equal to this address is settled with native BNB:\n /// WBNB is the debt-accounting token, and only the repay amount is unwrapped (see `_liquidate`).\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable vBNB;\n\n /// @notice The WBNB market (vWBNB). BNB debt is flash-funded from here, NOT from vBNB: vBNB cannot be\n /// flash-repaid (its `doTransferIn` needs `msg.value`), whereas vWBNB's underlying is a plain\n /// ERC20 repaid via `transferFrom`.\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n IVToken public immutable vWBNB;\n\n /// @notice WBNB token: the debt-accounting asset for BNB debt, unwrapped to native BNB for the repay.\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n IWBNB public immutable wbnb;\n\n /// @notice Addresses allowed to trigger a liquidation.\n mapping(address => bool) public isOperator;\n\n /// @notice Routers allowed as the swap target (defends the low-level call).\n mapping(address => bool) public isRouter;\n\n /// @dev Reserved storage to allow new state variables in future upgrades without layout clashes.\n uint256[50] private __gap;\n\n modifier onlyOperator() {\n if (msg.sender != owner() && !isOperator[msg.sender]) revert NotOperator();\n _;\n }\n\n /// @notice Constructor for the implementation contract. Sets the immutables and locks initializers.\n /// @param comptroller_ Venus Core Comptroller (diamond) — gates liquidation and provides flash loans.\n /// @param vBNB_ Native BNB market; a debt equal to this address is settled in native BNB.\n /// @param vWBNB_ WBNB market; the flash-borrow source for BNB debt (vBNB itself cannot be flash-repaid).\n /// @param wbnb_ WBNB token; the debt-accounting asset for BNB debt, unwrapped for the native repay.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(IComptroller comptroller_, address vBNB_, IVToken vWBNB_, IWBNB wbnb_) {\n ensureNonzeroAddress(address(comptroller_));\n ensureNonzeroAddress(vBNB_);\n ensureNonzeroAddress(address(vWBNB_));\n ensureNonzeroAddress(address(wbnb_));\n comptroller = comptroller_;\n vBNB = vBNB_;\n vWBNB = vWBNB_;\n wbnb = wbnb_;\n _disableInitializers();\n }\n\n /// @notice Initializes the proxy: sets the owner and the reentrancy guard.\n /// @param initialOwner Address that owns the contract (admin + default operator).\n function initialize(address initialOwner) external initializer {\n ensureNonzeroAddress(initialOwner);\n __Ownable2Step_init();\n __ReentrancyGuard_init();\n _transferOwnership(initialOwner);\n }\n\n /// @notice Accept native BNB. The expected inflow is `wbnb.withdraw` during a BNB liquidation (the\n /// unwrapped repay is forwarded to the gate in the same call, so no BNB is retained on the\n /// happy path). Left permissive (not restricted to `wbnb`) both to keep the receive body\n /// minimal for WBNB's 2300-gas `.transfer` stipend and to tolerate a stray transfer or a\n /// future gate refund — any such balance is recoverable via `sweepNative`.\n receive() external payable {}\n\n // --------------------------------------------------------------------- //\n // Admin //\n // --------------------------------------------------------------------- //\n\n /// @inheritdoc IBStockLiquidator\n function setOperator(address operator, bool allowed) external override onlyOwner {\n ensureNonzeroAddress(operator);\n isOperator[operator] = allowed;\n emit OperatorSet(operator, allowed);\n }\n\n /// @inheritdoc IBStockLiquidator\n function setRouter(address router, bool allowed) external override onlyOwner {\n ensureNonzeroAddress(router);\n isRouter[router] = allowed;\n emit RouterSet(router, allowed);\n }\n\n /// @inheritdoc IBStockLiquidator\n function sweep(address token, address to, uint256 amount) external override onlyOwner {\n ensureNonzeroAddress(token);\n ensureNonzeroAddress(to);\n IERC20Upgradeable(token).safeTransfer(to, amount);\n emit Swept(token, to, amount);\n }\n\n /// @inheritdoc IBStockLiquidator\n function sweepNative(address to, uint256 amount) external override onlyOwner {\n ensureNonzeroAddress(to);\n (bool ok, ) = to.call{ value: amount }(\"\");\n if (!ok) revert NativeTransferFailed();\n emit SweptNative(to, amount);\n }\n\n // --------------------------------------------------------------------- //\n // INVENTORY mode //\n // --------------------------------------------------------------------- //\n\n /// @inheritdoc IBStockLiquidator\n /// @dev INVENTORY mode spends the contract's OWN debt-asset capital, so — unlike FLASH mode, where\n /// `executeOperation` forces the swap proceeds to cover principal + premium — there is no\n /// built-in floor tying `debtOut` to `repayAmount`. This asymmetry is intentional: a repay can\n /// legitimately out-cost its proceeds (e.g. the Venus Liquidator keeps a treasury cut of the\n /// seized collateral, so proceeds land a few % under the repay). `minOut` IS the operator's\n /// chosen loss floor for inventory mode — set it to the lowest acceptable debt-asset return.\n function liquidate(\n LiquidationParams calldata params\n ) external override onlyOperator nonReentrant returns (uint256 debtOut) {\n _validateRouters(params.router, params.router2);\n\n if (params.minOut == 0) revert ZeroMinOut();\n if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp);\n uint256 seizedBStock;\n (debtOut, seizedBStock) = _liquidate(params);\n emit Liquidated(\n params.borrower,\n address(params.vBStock),\n address(params.vDebt),\n params.repayAmount,\n seizedBStock,\n debtOut,\n false\n );\n }\n\n // --------------------------------------------------------------------- //\n // FLASH mode //\n // --------------------------------------------------------------------- //\n\n /// @inheritdoc IBStockLiquidator\n function flashLiquidate(LiquidationParams calldata params) external override onlyOperator nonReentrant {\n _validateRouters(params.router, params.router2);\n\n if (params.minOut == 0) revert ZeroMinOut();\n if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp);\n\n // BNB debt is flash-funded from vWBNB (an ERC20 market), not vBNB: vBNB cannot be flash-repaid.\n // The flashed WBNB is unwrapped to native BNB for the repay inside `_liquidate` (see `executeOperation`).\n IVToken[] memory vTokens = new IVToken[](1);\n vTokens[0] = (address(params.vDebt) == vBNB) ? vWBNB : IVToken(address(params.vDebt));\n uint256[] memory amounts = new uint256[](1);\n amounts[0] = params.repayAmount;\n\n comptroller.executeFlashLoan(\n payable(address(this)),\n payable(address(this)),\n vTokens,\n amounts,\n abi.encode(params)\n );\n }\n\n /// @inheritdoc IFlashLoanReceiver\n function executeOperation(\n VToken[] calldata vTokens,\n uint256[] calldata amounts,\n uint256[] calldata premiums,\n address initiator,\n address /* onBehalf */,\n bytes calldata param\n ) external override returns (bool, uint256[] memory repayAmounts) {\n if (msg.sender != address(comptroller)) revert OnlyComptroller();\n // initiator == this proves the flash was started by our own flashLiquidate: the FlashLoanFacet\n // passes msg.sender (the executeFlashLoan caller) as `initiator`, and only flashLiquidate calls it.\n if (initiator != address(this)) revert BadInitiator(initiator);\n\n LiquidationParams memory params = abi.decode(param, (LiquidationParams));\n // For BNB debt the flash is drawn from vWBNB, not vBNB (see `flashLiquidate`). Scoped so the\n // temporary doesn't count against the stack depth of the rest of the function.\n {\n address expectedFlash = address(params.vDebt) == vBNB ? address(vWBNB) : address(params.vDebt);\n if (address(vTokens[0]) != expectedFlash) revert WrongFlashAsset();\n }\n\n // Repay was just funded by the flash loan; run the liquidation + swap.\n (uint256 debtOut, uint256 seizedBStock) = _liquidate(params);\n\n // The swap proceeds alone MUST cover principal + premium. Without this, any debt-asset inventory\n // held by the contract would silently backfill an underwater swap (a real loss), since the\n // flash repayment is pulled from the total balance, not just the swap output.\n repayAmounts = new uint256[](1);\n repayAmounts[0] = amounts[0] + premiums[0];\n if (debtOut < repayAmounts[0]) revert InsufficientOut(debtOut, repayAmounts[0]);\n\n // Approve the flashed vToken to pull back principal + premium. For BNB debt the flashed asset is\n // WBNB (from vWBNB); the ternary short-circuits so `underlying()` is never called on vBNB (it has none).\n IERC20Upgradeable(address(params.vDebt) == vBNB ? address(wbnb) : params.vDebt.underlying()).forceApprove(\n address(vTokens[0]),\n repayAmounts[0]\n );\n\n emit Liquidated(\n params.borrower,\n address(params.vBStock),\n address(params.vDebt),\n params.repayAmount,\n seizedBStock,\n debtOut,\n true\n );\n return (true, repayAmounts);\n }\n\n // --------------------------------------------------------------------- //\n // Core //\n // --------------------------------------------------------------------- //\n\n /// @dev Pre-flight: every swap router must be allowlisted. `router2` is optional (single-hop when\n /// zero) so it is only checked when set. Liquidatability itself is not pre-checked here — Core's\n /// `liquidateBorrowAllowed` already enforces it, and pre-checking shortfall would wrongly block\n /// forced liquidations (which liquidate healthy accounts).\n function _validateRouters(address router, address router2) private view {\n if (!isRouter[router]) revert RouterNotAllowed(router);\n if (router2 != address(0) && !isRouter[router2]) revert RouterNotAllowed(router2);\n }\n\n /// @dev One swap hop: approve the exact `amount` to the allowlisted `router`, forward the opaque\n /// calldata via a low-level call, then reset the approval to 0. The approval caps what the\n /// router can pull; if the calldata sells less, the remainder stays as inventory.\n function _swap(IERC20Upgradeable token, address router, bytes memory data, uint256 amount) private {\n token.forceApprove(router, amount);\n (bool ok, ) = router.call(data);\n if (!ok) revert SwapFailed();\n token.forceApprove(router, 0); // never leave a standing approval\n }\n\n /**\n * @dev The atomic sequence shared by both funding modes:\n * approve debt -> liquidateBorrow (seize vBStock) -> redeem (raw bStock)\n * -> swap bStock to the debt asset (one hop, or two via an intermediate) -> assert minOut.\n * A `calldata` struct from `liquidate` is copied to memory on entry; `executeOperation`\n * already holds it in memory (decoded from the flash callback).\n * @param params Liquidation parameters.\n * @return debtOut Debt-asset proceeds realized by the swap chain (reverts if below `minOut`).\n * @return seizedBStock Raw bStock redeemed and sold (balance delta).\n */\n function _liquidate(LiquidationParams memory params) private returns (uint256 debtOut, uint256 seizedBStock) {\n // A debt equal to `vBNB` is native BNB. vBNB has no `underlying()`, so the `isBnb` check MUST come\n // first: the ternary short-circuits and `underlying()` is never evaluated for vBNB. WBNB is the\n // debt-accounting token throughout (1:1 with BNB), so the whole swap/minOut path below is reused.\n // KEEP THIS ORDER — hoisting `underlying()` above the check reverts every BNB liquidation.\n bool isBnb = address(params.vDebt) == vBNB;\n // Native RFQ only quotes bStock->USDT, so a BNB debt is inherently two-hop (...->WBNB). Reject a\n // single-hop BNB config up front instead of failing opaquely later on a zero WBNB delta.\n if (isBnb && params.router2 == address(0)) revert InvalidIntermediate();\n IERC20Upgradeable debt = isBnb\n ? IERC20Upgradeable(address(wbnb))\n : IERC20Upgradeable(params.vDebt.underlying());\n IERC20Upgradeable bStock = IERC20Upgradeable(params.vBStock.underlying());\n\n // 1. Repay the borrow, seizing the bStock vToken to this contract.\n // Core has a POOL-WIDE liquidator gate (`liquidatorContract`), which is always configured on\n // the networks this contract targets. While it is set, a direct `vToken.liquidateBorrow`\n // reverts UNAUTHORIZED, so we route the repay through that Venus Liquidator (it pulls our\n // repay and sends us our share of the seized collateral; treasury keeps a cut). The seized\n // amount is read as a BALANCE DELTA, so the Liquidator's cut is handled correctly. We guard\n // against an unset gate so a misconfig fails loudly instead of silently no-op'ing a call to\n // address(0) (a low-level call to a codeless address returns success).\n uint256 vBefore = params.vBStock.balanceOf(address(this));\n address gate = comptroller.liquidatorContract();\n ensureNonzeroAddress(gate);\n if (isBnb) {\n // Unwrap EXACTLY the repay (WBNB held as inventory or drawn from the vWBNB flash) and forward\n // native BNB to the gate's vBNB branch (`{value:}`). Only the repay portion is unwrapped, so\n // pre-existing WBNB inventory is untouched; the swap proceeds below stay as WBNB. No approval\n // is granted (value is forwarded), so there is no standing allowance to reset.\n wbnb.withdraw(params.repayAmount);\n ILiquidator(gate).liquidateBorrow{ value: params.repayAmount }(\n address(params.vDebt),\n params.borrower,\n params.repayAmount,\n params.vBStock\n );\n } else {\n debt.forceApprove(gate, params.repayAmount);\n ILiquidator(gate).liquidateBorrow(\n address(params.vDebt),\n params.borrower,\n params.repayAmount,\n params.vBStock\n );\n // Reset the gate approval: if the Liquidator pulled less than `repayAmount` (e.g. a close-factor\n // cap), the remainder would otherwise linger as a standing allowance. Same invariant as `_swap`.\n debt.forceApprove(gate, 0);\n }\n uint256 seizedV = params.vBStock.balanceOf(address(this)) - vBefore;\n\n // 2. Redeem the seized vBStock for raw bStock. Measure by DELTA so any pre-existing bStock\n // (dust or a stray transfer) is excluded — we only sell what this redeem actually returned.\n uint256 rawBefore = bStock.balanceOf(address(this));\n uint256 redeemErr = params.vBStock.redeem(seizedV);\n if (redeemErr != 0) revert RedeemFailed(redeemErr);\n seizedBStock = bStock.balanceOf(address(this)) - rawBefore;\n\n // 3. Sell the bStock to the debt asset (one hop, or two via an intermediate) and assert minOut.\n // Extracted into `_sellToDebt` to keep this frame within the EVM stack limit.\n debtOut = _sellToDebt(debt, bStock, seizedBStock, params);\n }\n\n /**\n * @dev Sell `seizedBStock` to the debt asset and enforce `minOut`. Single hop by default\n * (bStock -> debt via the Native router). When `params.router2` is set, two hops\n * (bStock -> intermediate -> debt): hop 1 sells bStock to the intermediate (USDT) via the\n * Native router, hop 2 converts that intermediate to the debt asset via a second allowlisted\n * router (AMM/aggregator). `minOut` is measured in the debt asset across the whole chain.\n * @return debtOut Debt-asset proceeds (balance delta), reverting if below `minOut`.\n */\n function _sellToDebt(\n IERC20Upgradeable debt,\n IERC20Upgradeable bStock,\n uint256 seizedBStock,\n LiquidationParams memory params\n ) private returns (uint256 debtOut) {\n uint256 debtBefore = debt.balanceOf(address(this));\n if (params.router2 == address(0)) {\n _swap(bStock, params.router, params.swapCalldata, seizedBStock);\n } else {\n // The intermediate must be a real token distinct from both endpoints: if it equals `debt`,\n // hop 1 would inflate the balance `debtBefore` snapshots against (breaking the proceeds\n // delta); if it equals `bStock`, the hop-1 sell shrinks the balance and the midDelta\n // subtraction underflows.\n if (\n params.intermediateToken == address(0) ||\n params.intermediateToken == address(debt) ||\n params.intermediateToken == address(bStock)\n ) revert InvalidIntermediate();\n\n IERC20Upgradeable mid = IERC20Upgradeable(params.intermediateToken);\n uint256 midBefore = mid.balanceOf(address(this));\n _swap(bStock, params.router, params.swapCalldata, seizedBStock); // hop 1: bStock -> intermediate\n // Only the hop-1 proceeds are sold onward; any pre-existing intermediate inventory is excluded.\n uint256 midDelta = mid.balanceOf(address(this)) - midBefore;\n _swap(mid, params.router2, params.swapCalldata2, midDelta); // hop 2: intermediate -> debt\n }\n\n debtOut = debt.balanceOf(address(this)) - debtBefore;\n if (debtOut < params.minOut) revert InsufficientOut(debtOut, params.minOut);\n }\n}\n" + }, + "contracts/BStock/IBStockLiquidator.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IVBep20 } from \"../InterfacesV8.sol\";\n\n/// @title IBStockLiquidator\n/// @author Venus\n/// @notice External API, events, errors and parameter struct for {BStockLiquidator}.\n/// @dev The flash-loan callback `executeOperation` is intentionally NOT part of this interface — it is\n/// declared by {IFlashLoanReceiver} (owned by the Core flash-loan subsystem) and implemented directly\n/// by {BStockLiquidator}, keeping this interface free of the concrete `VToken` dependency.\ninterface IBStockLiquidator {\n /// @notice Parameters for a single liquidation.\n /// @dev The swap can be one or two hops. When `router2 == address(0)` it is a single hop\n /// (bStock -> debt) and `swapCalldata2` / `intermediateToken` are ignored — behavior is\n /// identical to the single-hop version. When `router2` is set it is two hops\n /// (bStock -> `intermediateToken` -> debt): hop 1 sells bStock via `router`, hop 2 converts\n /// the intermediate to the debt asset via `router2`. `minOut` is always the FINAL debt-asset\n /// floor across the whole chain. `deadline` is a unix-timestamp expiry: the call reverts once\n /// `block.timestamp` passes it, so a stale tx cannot settle against an expired quote.\n struct LiquidationParams {\n address borrower; // account to liquidate\n IVBep20 vDebt; // borrowed market to repay (e.g. vUSDT)\n IVBep20 vBStock; // bStock collateral market to seize (e.g. vTSLAB)\n uint256 repayAmount; // debt underlying to repay (its own decimals)\n address router; // hop-1 router = Native firm-quote txRequest.target (must be allowlisted)\n bytes swapCalldata; // hop-1 calldata (MM-signed Native order): bStock -> intermediate (or -> debt if single-hop)\n uint256 minOut; // minimum FINAL debt-asset amount the swap chain must yield, else revert\n address router2; // hop-2 router (AMM/aggregator): intermediate -> debt; address(0) = single-hop\n bytes swapCalldata2; // hop-2 calldata; the swap recipient inside it MUST be this contract\n address intermediateToken; // token hop 1 outputs and hop 2 consumes (e.g. USDT); required when router2 set\n uint256 deadline; // unix timestamp after which the call reverts; guards a stale tx sitting in the mempool\n }\n\n /// @notice Emitted when an operator is allowlisted or removed.\n event OperatorSet(address indexed operator, bool allowed);\n\n /// @notice Emitted when a swap router is allowlisted or removed.\n event RouterSet(address indexed router, bool allowed);\n\n /// @notice Emitted on a successful liquidation.\n /// @param borrower The liquidated account.\n /// @param vBStock The seized bStock collateral market.\n /// @param vDebt The repaid debt market.\n /// @param repayAmount Debt underlying repaid.\n /// @param seizedBStock Raw bStock redeemed and sold.\n /// @param debtOut Debt-asset proceeds of the swap.\n /// @param flash True if funded by a flash loan, false if from inventory.\n event Liquidated(\n address indexed borrower,\n address indexed vBStock,\n address indexed vDebt,\n uint256 repayAmount,\n uint256 seizedBStock,\n uint256 debtOut,\n bool flash\n );\n\n /// @notice Emitted when the owner withdraws a token.\n event Swept(address indexed token, address indexed to, uint256 amount);\n\n /// @notice Emitted when the owner withdraws stuck native BNB.\n event SweptNative(address indexed to, uint256 amount);\n\n /// @notice Thrown when the caller is neither the owner nor an allowlisted operator.\n error NotOperator();\n\n /// @notice Thrown when the supplied swap router is not allowlisted.\n error RouterNotAllowed(address router);\n\n /// @notice Thrown when `vBStock.redeem` returns a non-zero error code.\n error RedeemFailed(uint256 errCode);\n\n /// @notice Thrown when the low-level call to the router reverts.\n error SwapFailed();\n\n /// @notice Thrown when swap proceeds are below `minOut`.\n error InsufficientOut(uint256 got, uint256 minOut);\n\n /// @notice Thrown when `minOut` is zero: a liquidation must set a non-zero debt-asset floor,\n /// else it would silently accept any proceeds (including zero).\n error ZeroMinOut();\n\n /// @notice Thrown when a two-hop `intermediateToken` is zero, or equals the debt or bStock token.\n error InvalidIntermediate();\n\n /// @notice Thrown when `executeOperation` is called by something other than the Comptroller.\n error OnlyComptroller();\n\n /// @notice Thrown when the flash-loan initiator is not this contract.\n error BadInitiator(address initiator);\n\n /// @notice Thrown when the flashed asset does not match `params.vDebt`.\n error WrongFlashAsset();\n\n /// @notice Thrown when the call is submitted after `params.deadline`.\n error DeadlineExpired(uint256 deadline, uint256 nowTs);\n\n /// @notice Thrown when a native BNB transfer (the `sweepNative` payout) fails.\n error NativeTransferFailed();\n\n /// @notice Allow or disallow an address to trigger liquidations.\n /// @param operator Address to allowlist or remove.\n /// @param allowed True to allow, false to remove.\n function setOperator(address operator, bool allowed) external;\n\n /// @notice Allow or disallow a router as the swap target (e.g. the Native router).\n /// @param router Address to allowlist or remove.\n /// @param allowed True to allow, false to remove.\n function setRouter(address router, bool allowed) external;\n\n /// @notice Withdraw any token (profit, leftover inventory, stuck dust) to `to`.\n /// @param token Token to withdraw.\n /// @param to Recipient.\n /// @param amount Amount to withdraw.\n function sweep(address token, address to, uint256 amount) external;\n\n /// @notice Withdraw stuck native BNB (a stray transfer, or a gate refund) to `to`.\n /// @param to Recipient.\n /// @param amount Amount of native BNB to withdraw.\n function sweepNative(address to, uint256 amount) external;\n\n /// @notice Liquidate using the contract's own debt-asset inventory.\n /// @dev The contract must already hold >= `repayAmount` of `vDebt.underlying()`.\n /// Profit (proceeds - repay) stays in the contract; withdraw it with `sweep`.\n /// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final\n /// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt).\n /// @return debtOut Debt-asset proceeds realized by the swap chain.\n function liquidate(LiquidationParams calldata params) external returns (uint256 debtOut);\n\n /// @notice Liquidate by flash-borrowing the repay amount from Venus, repaid (+ premium) in the same tx.\n /// @dev Requires this contract to be `authorizedFlashLoan` in the Comptroller and `vDebt` flash-enabled.\n /// Profit (proceeds - repay - premium) stays in the contract; withdraw it with `sweep`.\n /// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final\n /// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt).\n function flashLiquidate(LiquidationParams calldata params) external;\n}\n" + }, + "contracts/Comptroller/ComptrollerInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\nimport { ResilientOracleInterface } from \"@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol\";\nimport { IDeviationBoundedOracle } from \"@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol\";\n\nimport { VToken } from \"../Tokens/VTokens/VToken.sol\";\nimport { VAIControllerInterface } from \"../Tokens/VAI/VAIControllerInterface.sol\";\nimport { WeightFunction } from \"./Diamond/interfaces/IFacetBase.sol\";\n\nenum Action {\n MINT,\n REDEEM,\n BORROW,\n REPAY,\n SEIZE,\n LIQUIDATE,\n TRANSFER,\n ENTER_MARKET,\n EXIT_MARKET\n}\n\ninterface ComptrollerInterface {\n /// @notice Indicator that this is a Comptroller contract (for inspection)\n function isComptroller() external pure returns (bool);\n\n /*** Assets You Are In ***/\n\n function enterMarkets(address[] calldata vTokens) external returns (uint[] memory);\n\n function exitMarket(address vToken) external returns (uint);\n\n /*** Policy Hooks ***/\n\n function mintAllowed(address vToken, address minter, uint mintAmount) external returns (uint);\n\n function mintVerify(address vToken, address minter, uint mintAmount, uint mintTokens) external;\n\n function redeemAllowed(address vToken, address redeemer, uint redeemTokens) external returns (uint);\n\n function redeemVerify(address vToken, address redeemer, uint redeemAmount, uint redeemTokens) external;\n\n function borrowAllowed(address vToken, address borrower, uint borrowAmount) external returns (uint);\n\n function borrowVerify(address vToken, address borrower, uint borrowAmount) external;\n\n function executeFlashLoan(\n address payable onBehalf,\n address payable receiver,\n VToken[] calldata vTokens,\n uint256[] calldata underlyingAmounts,\n bytes calldata param\n ) external;\n\n function repayBorrowAllowed(\n address vToken,\n address payer,\n address borrower,\n uint repayAmount\n ) external returns (uint);\n\n function repayBorrowVerify(\n address vToken,\n address payer,\n address borrower,\n uint repayAmount,\n uint borrowerIndex\n ) external;\n\n function liquidateBorrowAllowed(\n address vTokenBorrowed,\n address vTokenCollateral,\n address liquidator,\n address borrower,\n uint repayAmount\n ) external returns (uint);\n\n function liquidateBorrowVerify(\n address vTokenBorrowed,\n address vTokenCollateral,\n address liquidator,\n address borrower,\n uint repayAmount,\n uint seizeTokens\n ) external;\n\n function seizeAllowed(\n address vTokenCollateral,\n address vTokenBorrowed,\n address liquidator,\n address borrower,\n uint seizeTokens\n ) external returns (uint);\n\n function seizeVerify(\n address vTokenCollateral,\n address vTokenBorrowed,\n address liquidator,\n address borrower,\n uint seizeTokens\n ) external;\n\n function transferAllowed(address vToken, address src, address dst, uint transferTokens) external returns (uint);\n\n function transferVerify(address vToken, address src, address dst, uint transferTokens) external;\n\n /*** Liquidity/Liquidation Calculations ***/\n\n function liquidateCalculateSeizeTokens(\n address vTokenBorrowed,\n address vTokenCollateral,\n uint repayAmount\n ) external view returns (uint, uint);\n\n function liquidateCalculateSeizeTokens(\n address borrower,\n address vTokenBorrowed,\n address vTokenCollateral,\n uint repayAmount\n ) external view returns (uint, uint);\n\n function setMintedVAIOf(address owner, uint amount) external returns (uint);\n\n function liquidateVAICalculateSeizeTokens(\n address vTokenCollateral,\n uint repayAmount\n ) external view returns (uint, uint);\n\n function getXVSAddress() external view returns (address);\n\n function markets(address) external view returns (bool, uint, bool, uint, uint, uint96, bool);\n\n function oracle() external view returns (ResilientOracleInterface);\n\n function deviationBoundedOracle() external view returns (IDeviationBoundedOracle);\n\n function getAccountLiquidity(address) external view returns (uint, uint, uint);\n\n function getAssetsIn(address) external view returns (VToken[] memory);\n\n function claimVenus(address) external;\n\n function venusAccrued(address) external view returns (uint);\n\n function venusSupplySpeeds(address) external view returns (uint);\n\n function venusBorrowSpeeds(address) external view returns (uint);\n\n function getAllMarkets() external view returns (VToken[] memory);\n\n function venusSupplierIndex(address, address) external view returns (uint);\n\n function venusInitialIndex() external view returns (uint224);\n\n function venusBorrowerIndex(address, address) external view returns (uint);\n\n function venusBorrowState(address) external view returns (uint224, uint32);\n\n function venusSupplyState(address) external view returns (uint224, uint32);\n\n function approvedDelegates(address borrower, address delegate) external view returns (bool);\n\n function vaiController() external view returns (VAIControllerInterface);\n\n function protocolPaused() external view returns (bool);\n\n function actionPaused(address market, Action action) external view returns (bool);\n\n function mintedVAIs(address user) external view returns (uint);\n\n function vaiMintRate() external view returns (uint);\n\n function authorizedFlashLoan(address account) external view returns (bool);\n\n function userPoolId(address account) external view returns (uint96);\n\n function getLiquidationIncentive(address vToken) external view returns (uint256);\n\n function getEffectiveLiquidationIncentive(address account, address vToken) external view returns (uint256);\n\n function getEffectiveLtvFactor(\n address account,\n address vToken,\n WeightFunction weightingStrategy\n ) external view returns (uint256);\n\n function lastPoolId() external view returns (uint96);\n\n function corePoolId() external pure returns (uint96);\n\n function pools(\n uint96 poolId\n ) external view returns (string memory label, bool isActive, bool allowCorePoolFallback);\n\n function getPoolVTokens(uint96 poolId) external view returns (address[] memory);\n\n function poolMarkets(\n uint96 poolId,\n address vToken\n )\n external\n view\n returns (\n bool isListed,\n uint256 collateralFactorMantissa,\n bool isVenus,\n uint256 liquidationThresholdMantissa,\n uint256 liquidationIncentiveMantissa,\n uint96 marketPoolId,\n bool isBorrowAllowed\n );\n\n function isFlashLoanPaused() external view returns (bool);\n}\n\ninterface IVAIVault {\n function updatePendingRewards() external;\n}\n\ninterface IComptroller {\n /*** Treasury Data ***/\n function treasuryAddress() external view returns (address);\n\n function treasuryPercent() external view returns (uint);\n}\n" + }, + "contracts/Comptroller/Diamond/interfaces/IFacetBase.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\nimport { Action } from \"../../../Comptroller/ComptrollerInterface.sol\";\nimport { PoolMarketId } from \"../../../Comptroller/Types/PoolMarketId.sol\";\n\nenum WeightFunction {\n /// @notice Use the collateral factor of the asset for weighting\n USE_COLLATERAL_FACTOR,\n /// @notice Use the liquidation threshold of the asset for weighting\n USE_LIQUIDATION_THRESHOLD\n}\n\ninterface IFacetBase {\n /**\n * @notice The initial XVS rewards index for a market\n */\n function venusInitialIndex() external pure returns (uint224);\n\n /**\n * @notice Checks if a certain action is paused on a market\n * @param action Action id\n * @param market vToken address\n */\n function actionPaused(address market, Action action) external view returns (bool);\n\n /**\n * @notice Returns the XVS address\n * @return The address of XVS token\n */\n function getXVSAddress() external view returns (address);\n\n function getPoolMarketIndex(uint96 poolId, address vToken) external pure returns (PoolMarketId);\n\n function corePoolId() external pure returns (uint96);\n}\n" + }, + "contracts/Comptroller/Types/PoolMarketId.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\n/// @notice Strongly-typed identifier for pool markets mapping keys\n/// @dev Underlying storage is bytes32: first 12 bytes (96 bits) = poolId, last 20 bytes = vToken address\ntype PoolMarketId is bytes32;\n\n " + }, + "contracts/external/IProtocolShareReserve.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\ninterface IProtocolShareReserve {\n enum IncomeType {\n SPREAD,\n LIQUIDATION,\n ERC4626_WRAPPER_REWARDS,\n FLASHLOAN\n }\n\n function updateAssetsState(address comptroller, address asset, IncomeType incomeType) external;\n}\n" + }, + "contracts/external/IWBNB.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\nimport { IERC20Upgradeable } from \"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\";\n\ninterface IWBNB is IERC20Upgradeable {\n function deposit() external payable;\n\n function withdraw(uint256 amount) external;\n}\n" + }, + "contracts/FlashLoan/interfaces/IFlashLoanReceiver.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { VToken } from \"../../Tokens/VTokens/VToken.sol\";\n\n/// @title IFlashLoanReceiver\n/// @notice Interface for flashLoan receiver contract, which executes custom logic with flash-borrowed assets.\n/// @dev This interface defines the method that must be implemented by any contract wishing to interact with the flashLoan system.\n/// Contracts must ensure they have the means to repay at least the premium (fee), with any unpaid balance becoming debt.\ninterface IFlashLoanReceiver {\n /**\n * @notice Executes an operation after receiving the flash-borrowed assets.\n * @dev Implementation of this function must ensure at least the premium (fee) is repaid within the same transaction.\n * Any unpaid balance (principal + premium - repaid amount) will be added to the onBehalf address's borrow balance.\n * @param vTokens The vToken contracts corresponding to the flash-borrowed underlying assets.\n * @param amounts The amounts of each underlying asset that were flash-borrowed.\n * @param premiums The premiums (fees) associated with each flash-borrowed asset.\n * @param initiator The address that initiated the flash loan.\n * @param onBehalf The address of the user whose debt position will be used for any unpaid flash loan balance.\n * @param param Additional parameters encoded as bytes. These can be used to pass custom data to the receiver contract.\n * @return success True if the operation succeeds (regardless of repayment amount), false if the operation fails.\n * @return repayAmounts Array of uint256 representing the amounts to be repaid for each asset. The receiver contract\n * must approve these amounts to the respective vToken contracts before this function returns.\n */\n function executeOperation(\n VToken[] calldata vTokens,\n uint256[] calldata amounts,\n uint256[] calldata premiums,\n address initiator,\n address onBehalf,\n bytes calldata param\n ) external returns (bool success, uint256[] memory repayAmounts);\n}\n" + }, + "contracts/InterestRateModels/InterestRateModelV8.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n/**\n * @title Venus's InterestRateModelV8 Interface\n * @author Venus\n */\nabstract contract InterestRateModelV8 {\n /// @notice Indicator that this is an InterestRateModel contract (for inspection)\n bool public constant isInterestRateModel = true;\n\n /**\n * @notice Calculates the current borrow interest rate per block\n * @param cash The total amount of cash the market has\n * @param borrows The total amount of borrows the market has outstanding\n * @param reserves The total amnount of reserves the market has\n * @return The borrow rate per block (as a percentage, and scaled by 1e18)\n */\n function getBorrowRate(uint256 cash, uint256 borrows, uint256 reserves) external view virtual returns (uint256);\n\n /**\n * @notice Calculates the current supply interest rate per block\n * @param cash The total amount of cash the market has\n * @param borrows The total amount of borrows the market has outstanding\n * @param reserves The total amnount of reserves the market has\n * @param reserveFactorMantissa The current reserve factor the market has\n * @return The supply rate per block (as a percentage, and scaled by 1e18)\n */\n function getSupplyRate(\n uint256 cash,\n uint256 borrows,\n uint256 reserves,\n uint256 reserveFactorMantissa\n ) external view virtual returns (uint256);\n}\n" + }, + "contracts/InterfacesV8.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nimport { IERC20Upgradeable } from \"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\";\nimport { ResilientOracleInterface } from \"@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol\";\n\ninterface IVToken is IERC20Upgradeable {\n function accrueInterest() external returns (uint256);\n\n function redeem(uint256 redeemTokens) external returns (uint256);\n\n function redeemUnderlying(uint256 redeemAmount) external returns (uint256);\n\n function borrowBalanceCurrent(address borrower) external returns (uint256);\n\n function balanceOfUnderlying(address owner) external returns (uint256);\n\n function comptroller() external view returns (IComptroller);\n\n function borrowBalanceStored(address account) external view returns (uint256);\n}\n\ninterface IVBep20 is IVToken {\n function borrowBehalf(address borrower, uint256 borrowAmount) external returns (uint256);\n\n function repayBorrowBehalf(address borrower, uint256 repayAmount) external returns (uint256);\n\n function liquidateBorrow(\n address borrower,\n uint256 repayAmount,\n IVToken vTokenCollateral\n ) external returns (uint256);\n\n function underlying() external view returns (address);\n}\n\ninterface IVBNB is IVToken {\n function repayBorrowBehalf(address borrower) external payable;\n\n function liquidateBorrow(address borrower, IVToken vTokenCollateral) external payable;\n}\n\ninterface IVAIController {\n function accrueVAIInterest() external;\n\n function liquidateVAI(\n address borrower,\n uint256 repayAmount,\n IVToken vTokenCollateral\n ) external returns (uint256, uint256);\n\n function repayVAIBehalf(address borrower, uint256 amount) external returns (uint256, uint256);\n\n function getVAIAddress() external view returns (address);\n\n function getVAIRepayAmount(address borrower) external view returns (uint256);\n}\n\ninterface IComptroller {\n enum Action {\n MINT,\n REDEEM,\n BORROW,\n REPAY,\n SEIZE,\n LIQUIDATE,\n TRANSFER,\n ENTER_MARKET,\n EXIT_MARKET\n }\n\n function _setActionsPaused(address[] calldata markets_, Action[] calldata actions_, bool paused_) external;\n\n function executeFlashLoan(\n address payable onBehalf,\n address payable receiver,\n IVToken[] calldata vTokens,\n uint256[] calldata underlyingAmounts,\n bytes calldata param\n ) external;\n\n function vaiController() external view returns (IVAIController);\n\n function liquidatorContract() external view returns (address);\n\n function getAccountLiquidity(address account) external view returns (uint256, uint256, uint256);\n\n function oracle() external view returns (ResilientOracleInterface);\n\n function actionPaused(address market, Action action) external view returns (bool);\n\n function markets(address) external view returns (bool, uint256, bool);\n\n function isForcedLiquidationEnabled(address) external view returns (bool);\n\n function getEffectiveLiquidationIncentive(address account, address vToken) external view returns (uint256);\n}\n\ninterface ILiquidator {\n function restrictLiquidation(address borrower) external;\n\n function unrestrictLiquidation(address borrower) external;\n\n function addToAllowlist(address borrower, address liquidator) external;\n\n function removeFromAllowlist(address borrower, address liquidator) external;\n\n function liquidateBorrow(\n address vToken,\n address borrower,\n uint256 repayAmount,\n IVToken vTokenCollateral\n ) external payable;\n\n function setTreasuryPercent(uint256 newTreasuryPercentMantissa) external;\n\n function treasuryPercentMantissa() external view returns (uint256);\n}\n" + }, + "contracts/test/BStockLiquidationMocks.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\n/// @notice Test-only mocks for exercising the BStockLiquidator contract (and the off-chain\n/// scripts) on a local network, mimicking the Venus Core + Native interfaces they call.\n/// Not for production.\n\ncontract MockMintableERC20 is ERC20 {\n uint8 private _decimals;\n\n constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {\n _decimals = decimals_;\n }\n\n function decimals() public view override returns (uint8) {\n return _decimals;\n }\n\n function mint(address to, uint256 amount) external {\n _mint(to, amount);\n }\n}\n\n/// @dev Receiver surface the flash comptroller calls back into. `address[]` matches the\n/// `VToken[]` selector (a contract type canonicalizes to `address`).\ninterface IFlashReceiverLike {\n function executeOperation(\n address[] calldata vTokens,\n uint256[] calldata amounts,\n uint256[] calldata premiums,\n address initiator,\n address onBehalf,\n bytes calldata param\n ) external returns (bool, uint256[] memory);\n}\n\n/// @dev Minimal Comptroller surface the script + BStockLiquidator read, plus a flash lender.\ncontract MockComptrollerLite {\n uint256 public shortfall;\n uint256 public closeFactorMantissa = 0.5e18;\n uint256 public liquidationIncentiveMantissa = 1.1e18;\n uint256 public flashPremiumMantissa; // fee on flash principal, 1e18-scaled (default 0)\n address public liquidatorContract; // pool-wide gate; 0 = permissionless (direct liquidateBorrow)\n uint256 public treasuryPercent; // redeem fee, 1e18-scaled (default 0)\n\n function setShortfall(uint256 s) external {\n shortfall = s;\n }\n\n function setTreasuryPercent(uint256 p) external {\n treasuryPercent = p;\n }\n\n function setLiquidatorContract(address l) external {\n liquidatorContract = l;\n }\n\n function setFlashPremium(uint256 m) external {\n flashPremiumMantissa = m;\n }\n\n function getAccountLiquidity(address) external view returns (uint256, uint256, uint256) {\n return (0, 0, shortfall);\n }\n\n /// @dev Mirrors the seize math: seizeTokens = repay * incentive (1:1 collateral rate here).\n function liquidateCalculateSeizeTokens(\n address,\n address,\n uint256 repayAmount\n ) external view returns (uint256, uint256) {\n return (0, (repayAmount * liquidationIncentiveMantissa) / 1e18);\n }\n\n /// @dev Read by the off-chain script to size the Venus Liquidator's bonus cut.\n function getEffectiveLiquidationIncentive(address, address) external view returns (uint256) {\n return liquidationIncentiveMantissa;\n }\n\n /// @dev Flash lender: send principal from the (pre-funded) debt vToken, call back, pull repay.\n function executeFlashLoan(\n address payable /* onBehalf */,\n address payable receiver,\n address[] calldata vTokens,\n uint256[] calldata amounts,\n bytes calldata param\n ) external {\n uint256[] memory premiums = new uint256[](1);\n premiums[0] = (amounts[0] * flashPremiumMantissa) / 1e18;\n\n MockVTokenDebt(vTokens[0]).flashOut(receiver, amounts[0]);\n (bool ok, uint256[] memory repay) = IFlashReceiverLike(receiver).executeOperation(\n vTokens,\n amounts,\n premiums,\n msg.sender,\n msg.sender,\n param\n );\n require(ok, \"executeOperation failed\");\n MockVTokenDebt(vTokens[0]).flashPull(receiver, repay[0]);\n }\n}\n\n/// @dev Collateral vToken mock. Tracks vToken balances; redeem() burns vTokens\n/// and pays out the underlying bStock 1:1 (exchangeRate = 1 for the test).\ncontract MockVTokenCollateral {\n address public immutable underlying;\n address public immutable comptroller;\n mapping(address => uint256) public balanceOf;\n\n constructor(address underlying_, address comptroller_) {\n underlying = underlying_;\n comptroller = comptroller_;\n }\n\n /// Called by the debt vToken to credit seized collateral to the liquidator.\n function creditSeize(address to, uint256 vAmount) external {\n balanceOf[to] += vAmount;\n }\n\n uint256 public redeemError; // non-zero -> redeem returns this code (exercises RedeemFailed)\n\n function setRedeemError(uint256 e) external {\n redeemError = e;\n }\n\n function redeem(uint256 redeemTokens) external returns (uint256) {\n if (redeemError != 0) return redeemError;\n require(balanceOf[msg.sender] >= redeemTokens, \"insufficient vTokens\");\n balanceOf[msg.sender] -= redeemTokens;\n // 1:1 exchange rate for the test\n require(ERC20(underlying).transfer(msg.sender, redeemTokens), \"redeem transfer failed\");\n return 0;\n }\n\n /// @dev 1:1, matching `redeem` above. Read by the off-chain script to precompute the seize.\n function exchangeRateStored() external pure returns (uint256) {\n return 1e18;\n }\n}\n\n/// @dev Debt vToken mock. liquidateBorrow pulls `repayAmount` of the debt\n/// underlying from the caller and credits seized collateral (repay + 10%).\ncontract MockVTokenDebt {\n address public immutable underlying;\n address public immutable comptroller;\n uint256 public incentiveMantissa = 1.1e18;\n uint256 public liquidateError; // non-zero -> liquidateBorrow returns this code (exercises LiquidateBorrowFailed)\n bool public constant isFlashLoanEnabled = true;\n\n constructor(address underlying_, address comptroller_) {\n underlying = underlying_;\n comptroller = comptroller_;\n }\n\n function setLiquidateError(uint256 e) external {\n liquidateError = e;\n }\n\n function liquidateBorrow(\n address /* borrower */,\n uint256 repayAmount,\n address vTokenCollateral\n ) external returns (uint256) {\n if (liquidateError != 0) return liquidateError;\n require(ERC20(underlying).transferFrom(msg.sender, address(this), repayAmount), \"repay pull failed\");\n uint256 seizeV = (repayAmount * incentiveMantissa) / 1e18;\n MockVTokenCollateral(vTokenCollateral).creditSeize(msg.sender, seizeV);\n return 0;\n }\n\n /// @dev Flash helpers, driven by MockComptrollerLite. Pre-fund this contract with `underlying`.\n function flashOut(address to, uint256 amount) external {\n require(msg.sender == comptroller, \"only comptroller\");\n require(ERC20(underlying).transfer(to, amount), \"flash out failed\");\n }\n\n function flashPull(address from, uint256 amount) external {\n require(msg.sender == comptroller, \"only comptroller\");\n // `from` (the receiver) approved THIS vToken as spender, per the real flash interface.\n require(ERC20(underlying).transferFrom(from, address(this), amount), \"flash pull failed\");\n }\n}\n\n/// @dev Stand-in for the Native router. Pulls tokenIn from the caller and pays\n/// tokenOut at a configurable rate (1e18 = 1:1). Pre-fund it with tokenOut.\ncontract MockNativeRouter {\n uint256 public rate = 1e18; // tokenOut per tokenIn, 18-dec fixed point\n\n function setRate(uint256 r) external {\n rate = r;\n }\n\n function swap(address tokenIn, uint256 amountIn, address tokenOut, address to) external {\n require(ERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), \"in pull failed\");\n uint256 amountOut = (amountIn * rate) / 1e18;\n require(ERC20(tokenOut).transfer(to, amountOut), \"out transfer failed\");\n }\n\n /// @dev Pulls the caller's ENTIRE tokenIn balance (what the liquidator actually holds after\n /// redeem) and pays tokenOut at `rate`. Avoids fixed-amount rounding mismatches on forks.\n function swapAll(address tokenIn, address tokenOut, address to) external {\n uint256 amountIn = ERC20(tokenIn).balanceOf(msg.sender);\n require(ERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), \"in pull failed\");\n uint256 amountOut = (amountIn * rate) / 1e18;\n require(ERC20(tokenOut).transfer(to, amountOut), \"out transfer failed\");\n }\n}\n\n/// @dev Malicious router: during the swap hop it re-enters the liquidator (via a preconfigured call)\n/// before completing a normal swap. Used to prove the `nonReentrant` guard blocks re-entry while\n/// the outer liquidation still settles. Must be allowlisted AND set as an operator so the re-entry\n/// clears `onlyOperator` and actually reaches the reentrancy guard.\ncontract MockReentrantRouter {\n uint256 public rate = 1e18;\n address public target; // the liquidator to re-enter\n bytes public reentryCalldata; // encoded liquidate/flashLiquidate call\n bool public reentrySucceeded; // true iff the re-entry call returned success (guard FAILED)\n bool public reentryAttempted;\n\n function configure(address target_, bytes calldata reentryCalldata_) external {\n target = target_;\n reentryCalldata = reentryCalldata_;\n }\n\n function swap(address tokenIn, uint256 amountIn, address tokenOut, address to) external {\n reentryAttempted = true;\n // Attempt to re-enter; swallow the result so the OUTER swap/liquidation can still complete.\n (bool ok, ) = target.call(reentryCalldata);\n reentrySucceeded = ok;\n require(ERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), \"in pull failed\");\n require(ERC20(tokenOut).transfer(to, (amountIn * rate) / 1e18), \"out transfer failed\");\n }\n\n /// @dev Same re-entry attempt, but pulls the caller's ENTIRE tokenIn balance and pays out — so the\n /// OUTER liquidation actually settles (clearing minOut), letting the test observe that the\n /// re-entry was blocked while the surrounding call succeeded.\n function swapAll(address tokenIn, address tokenOut, address to) external {\n reentryAttempted = true;\n (bool ok, ) = target.call(reentryCalldata);\n reentrySucceeded = ok;\n uint256 amountIn = ERC20(tokenIn).balanceOf(msg.sender);\n require(ERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), \"in pull failed\");\n require(ERC20(tokenOut).transfer(to, (amountIn * rate) / 1e18), \"out transfer failed\");\n }\n\n function setRate(uint256 r) external {\n rate = r;\n }\n}\n\n/// @dev Stand-in for the pool-wide Venus Liquidator (`liquidatorContract`). Pulls the repay from the\n/// caller and credits the seized collateral (repay * incentive) to the caller, minus a treasury cut.\ncontract MockVenusLiquidator {\n uint256 public incentiveMantissa = 1.1e18;\n uint256 public treasuryCutMantissa; // cut of the seized collateral kept by the liquidator (default 0)\n uint256 public pullMantissa = 1e18; // fraction of repayAmount actually pulled (default 100%)\n address public vBnb; // native BNB market; a repay for this vToken must arrive as msg.value\n\n function setTreasuryCut(uint256 m) external {\n treasuryCutMantissa = m;\n }\n\n /// @dev Simulate a partial repay pull (e.g. a close-factor cap): the gate pulls less than the\n /// caller approved, so a standing allowance would linger unless the caller resets it.\n function setPullMantissa(uint256 m) external {\n pullMantissa = m;\n }\n\n /// @dev Mirrors the real gate: a vBNB repay is native BNB (msg.value), not an ERC20 transferFrom.\n function setVBnb(address v) external {\n vBnb = v;\n }\n\n /// @dev Mirrors the real Venus Liquidator getter the off-chain script reads to precompute the cut.\n function treasuryPercentMantissa() external view returns (uint256) {\n return treasuryCutMantissa;\n }\n\n function liquidateBorrow(\n address vToken,\n address /* borrower */,\n uint256 repayAmount,\n address vTokenCollateral\n ) external payable {\n if (vToken == vBnb) {\n // Native BNB repay: require exact msg.value (mirrors Liquidator.sol) and keep the BNB.\n require(msg.value == repayAmount, \"bad value\");\n } else {\n address underlying = MockVTokenDebt(vToken).underlying();\n uint256 pulled = (repayAmount * pullMantissa) / 1e18;\n require(ERC20(underlying).transferFrom(msg.sender, address(this), pulled), \"repay pull failed\");\n }\n uint256 seizeV = (repayAmount * incentiveMantissa) / 1e18;\n uint256 toCaller = seizeV - (seizeV * treasuryCutMantissa) / 1e18;\n MockVTokenCollateral(vTokenCollateral).creditSeize(msg.sender, toCaller);\n }\n}\n\n/// @dev Comptroller stand-in that drives the flash callback with deliberately wrong arguments, so the\n/// receiver's mid-flight guards can be exercised. `mode` selects which invariant to violate; the\n/// contract under test is deployed with this as its immutable comptroller. `getAccountLiquidity`\n/// always reports a shortfall so the pre-flight `_check` passes and the callback is reached.\ncontract MockMaliciousFlashComptroller {\n enum Mode {\n BadInitiator, // report an initiator != the receiver\n WrongAsset // flash a vToken != params.vDebt\n }\n\n Mode public mode;\n address public liquidatorContract; // unset: the receiver would liquidate directly\n\n function setMode(Mode mode_) external {\n mode = mode_;\n }\n\n function getAccountLiquidity(address) external pure returns (uint256, uint256, uint256) {\n return (0, 0, 1);\n }\n\n function executeFlashLoan(\n address payable /* onBehalf */,\n address payable receiver,\n address[] calldata vTokens,\n uint256[] calldata amounts,\n bytes calldata param\n ) external {\n uint256[] memory premiums = new uint256[](1);\n if (mode == Mode.BadInitiator) {\n IFlashReceiverLike(receiver).executeOperation(\n vTokens,\n amounts,\n premiums,\n address(uint160(0xbad)), // initiator != receiver\n msg.sender,\n param\n );\n } else {\n address[] memory wrongAsset = new address[](1);\n wrongAsset[0] = address(uint160(0xdead)); // != params.vDebt\n IFlashReceiverLike(receiver).executeOperation(wrongAsset, amounts, premiums, receiver, msg.sender, param);\n }\n }\n}\n" + }, + "contracts/Tokens/VAI/VAIControllerInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\nimport { VTokenInterface } from \"../VTokens/VTokenInterfaces.sol\";\n\ninterface VAIControllerInterface {\n function mintVAI(uint256 mintVAIAmount) external returns (uint256);\n\n function repayVAI(uint256 amount) external returns (uint256, uint256);\n\n function repayVAIBehalf(address borrower, uint256 amount) external returns (uint256, uint256);\n\n function liquidateVAI(\n address borrower,\n uint256 repayAmount,\n VTokenInterface vTokenCollateral\n ) external returns (uint256, uint256);\n\n function getMintableVAI(address minter) external view returns (uint256, uint256);\n\n function getVAIAddress() external view returns (address);\n\n function getVAIRepayAmount(address account) external view returns (uint256);\n}\n" + }, + "contracts/Tokens/VTokens/VToken.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\nimport { IAccessControlManagerV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol\";\nimport { IProtocolShareReserve } from \"../../external/IProtocolShareReserve.sol\";\nimport { ComptrollerInterface, IComptroller } from \"../../Comptroller/ComptrollerInterface.sol\";\nimport { TokenErrorReporter } from \"../../Utils/ErrorReporter.sol\";\nimport { Exponential } from \"../../Utils/Exponential.sol\";\nimport { InterestRateModelV8 } from \"../../InterestRateModels/InterestRateModelV8.sol\";\nimport { VTokenInterface } from \"./VTokenInterfaces.sol\";\nimport { MANTISSA_ONE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\n\n/**\n * @title Venus's vToken Contract\n * @notice Abstract base for vTokens\n * @author Venus\n */\nabstract contract VToken is VTokenInterface, Exponential, TokenErrorReporter {\n struct MintLocalVars {\n MathError mathErr;\n uint exchangeRateMantissa;\n uint mintTokens;\n uint totalSupplyNew;\n uint accountTokensNew;\n uint actualMintAmount;\n }\n\n struct RedeemLocalVars {\n MathError mathErr;\n uint exchangeRateMantissa;\n uint redeemTokens;\n uint redeemAmount;\n uint totalSupplyNew;\n uint accountTokensNew;\n }\n\n struct BorrowLocalVars {\n MathError mathErr;\n uint accountBorrows;\n uint accountBorrowsNew;\n uint totalBorrowsNew;\n }\n\n struct RepayBorrowLocalVars {\n Error err;\n MathError mathErr;\n uint repayAmount;\n uint borrowerIndex;\n uint accountBorrows;\n uint accountBorrowsNew;\n uint totalBorrowsNew;\n uint actualRepayAmount;\n }\n\n /*** Reentrancy Guard ***/\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n */\n modifier nonReentrant() {\n require(_notEntered, \"re-entered\");\n _notEntered = false;\n _;\n _notEntered = true; // get a gas-refund post-Istanbul\n }\n\n /**\n * @notice Transfer `amount` tokens from `msg.sender` to `dst`\n * @param dst The address of the destination account\n * @param amount The number of tokens to transfer\n * @return Whether or not the transfer succeeded\n */\n // @custom:event Emits Transfer event\n function transfer(address dst, uint256 amount) external override nonReentrant returns (bool) {\n return transferTokens(msg.sender, msg.sender, dst, amount) == uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Transfer `amount` tokens from `src` to `dst`\n * @param src The address of the source account\n * @param dst The address of the destination account\n * @param amount The number of tokens to transfer\n * @return Whether or not the transfer succeeded\n */\n // @custom:event Emits Transfer event\n function transferFrom(address src, address dst, uint256 amount) external override nonReentrant returns (bool) {\n return transferTokens(msg.sender, src, dst, amount) == uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Approve `spender` to transfer up to `amount` from `src`\n * @dev This will overwrite the approval amount for `spender`\n * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)\n * @param spender The address of the account which may transfer tokens\n * @param amount The number of tokens that are approved (type(uint256).max means infinite)\n * @return Whether or not the approval succeeded\n */\n // @custom:event Emits Approval event on successful approve\n function approve(address spender, uint256 amount) external override returns (bool) {\n transferAllowances[msg.sender][spender] = amount;\n emit Approval(msg.sender, spender, amount);\n return true;\n }\n\n /**\n * @notice Get the underlying balance of the `owner`\n * @dev This also accrues interest in a transaction\n * @param owner The address of the account to query\n * @return The amount of underlying owned by `owner`\n */\n function balanceOfUnderlying(address owner) external override returns (uint) {\n Exp memory exchangeRate = Exp({ mantissa: exchangeRateCurrent() });\n (MathError mErr, uint balance) = mulScalarTruncate(exchangeRate, accountTokens[owner]);\n ensureNoMathError(mErr);\n return balance;\n }\n\n /**\n * @notice Returns the current total borrows plus accrued interest\n * @return The total borrows with interest\n */\n function totalBorrowsCurrent() external override nonReentrant returns (uint) {\n require(accrueInterest() == uint(Error.NO_ERROR), \"accrue interest failed\");\n return totalBorrows;\n }\n\n /**\n * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex\n * @param account The address whose balance should be calculated after updating borrowIndex\n * @return The calculated balance\n */\n function borrowBalanceCurrent(address account) external override nonReentrant returns (uint) {\n require(accrueInterest() == uint(Error.NO_ERROR), \"accrue interest failed\");\n return borrowBalanceStored(account);\n }\n\n /**\n * @notice Transfers collateral tokens (this market) to the liquidator.\n * @dev Will fail unless called by another vToken during the process of liquidation.\n * Its absolutely critical to use msg.sender as the borrowed vToken and not a parameter.\n * @param liquidator The account receiving seized collateral\n * @param borrower The account having collateral seized\n * @param seizeTokens The number of vTokens to seize\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n // @custom:event Emits Transfer event\n function seize(\n address liquidator,\n address borrower,\n uint seizeTokens\n ) external override nonReentrant returns (uint) {\n return seizeInternal(msg.sender, liquidator, borrower, seizeTokens);\n }\n\n /**\n * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.\n * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.\n * @param newPendingAdmin New pending admin.\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n // @custom:event Emits NewPendingAdmin event with old and new admin addresses\n function _setPendingAdmin(address payable newPendingAdmin) external override returns (uint) {\n // Check caller = admin\n ensureAdmin(msg.sender);\n\n // Save current value, if any, for inclusion in log\n address oldPendingAdmin = pendingAdmin;\n\n // Store pendingAdmin with value newPendingAdmin\n pendingAdmin = newPendingAdmin;\n\n // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin)\n emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin\n * @dev Admin function for pending admin to accept role and update admin\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n // @custom:event Emits NewAdmin event on successful acceptance\n // @custom:event Emits NewPendingAdmin event with null new pending admin\n function _acceptAdmin() external override returns (uint) {\n // Check caller is pendingAdmin\n if (msg.sender != pendingAdmin) {\n return fail(Error.UNAUTHORIZED, FailureInfo.ACCEPT_ADMIN_PENDING_ADMIN_CHECK);\n }\n\n // Save current values for inclusion in log\n address oldAdmin = admin;\n address oldPendingAdmin = pendingAdmin;\n\n // Store admin with value pendingAdmin\n admin = pendingAdmin;\n\n // Clear the pending value\n pendingAdmin = payable(address(0));\n\n emit NewAdmin(oldAdmin, admin);\n emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice accrues interest and sets a new reserve factor for the protocol using `_setReserveFactorFresh`\n * @dev Governor function to accrue interest and set a new reserve factor\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n // @custom:event Emits NewReserveFactor event\n function _setReserveFactor(uint newReserveFactorMantissa_) external override nonReentrant returns (uint) {\n ensureAllowed(\"_setReserveFactor(uint256)\");\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reserve factor change failed.\n return fail(Error(error), FailureInfo.SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED);\n }\n\n // _setReserveFactorFresh emits reserve-factor-specific logs on errors, so we don't need to.\n return _setReserveFactorFresh(newReserveFactorMantissa_);\n }\n\n /**\n * @notice Sets the address of the access control manager of this contract\n * @dev Admin function to set the access control address\n * @param newAccessControlManagerAddress New address for the access control\n * @return uint 0=success, otherwise will revert\n */\n function setAccessControlManager(address newAccessControlManagerAddress) external returns (uint) {\n // Check caller is admin\n ensureAdmin(msg.sender);\n\n ensureNonZeroAddress(newAccessControlManagerAddress);\n\n emit NewAccessControlManager(accessControlManager, newAccessControlManagerAddress);\n accessControlManager = newAccessControlManagerAddress;\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Accrues interest and reduces reserves by transferring to protocol share reserve\n * @param reduceAmount_ Amount of reduction to reserves\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n // @custom:event Emits ReservesReduced event\n function _reduceReserves(uint reduceAmount_) external virtual override nonReentrant returns (uint) {\n ensureAllowed(\"_reduceReserves(uint256)\");\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed.\n return fail(Error(error), FailureInfo.REDUCE_RESERVES_ACCRUE_INTEREST_FAILED);\n }\n\n // If reserves were reduced in accrueInterest\n if (reduceReservesBlockNumber == block.number) return (uint(Error.NO_ERROR));\n // _reduceReservesFresh emits reserve-reduction-specific logs on errors, so we don't need to.\n return _reduceReservesFresh(reduceAmount_);\n }\n\n /**\n * @notice Get the current allowance from `owner` for `spender`\n * @param owner The address of the account which owns the tokens to be spent\n * @param spender The address of the account which may transfer tokens\n * @return The number of tokens allowed to be spent (type(uint256).max means infinite)\n */\n function allowance(address owner, address spender) external view override returns (uint256) {\n return transferAllowances[owner][spender];\n }\n\n /**\n * @notice Get the token balance of the `owner`\n * @param owner The address of the account to query\n * @return The number of tokens owned by `owner`\n */\n function balanceOf(address owner) external view override returns (uint256) {\n return accountTokens[owner];\n }\n\n /**\n * @notice Get a snapshot of the account's balances, and the cached exchange rate\n * @dev This is used by comptroller to more efficiently perform liquidity checks.\n * @param account Address of the account to snapshot\n * @return (possible error, token balance, borrow balance, exchange rate mantissa)\n */\n function getAccountSnapshot(address account) external view override returns (uint, uint, uint, uint) {\n uint borrowBalance;\n uint exchangeRateMantissa;\n\n MathError mErr;\n\n (mErr, borrowBalance) = borrowBalanceStoredInternal(account);\n if (mErr != MathError.NO_ERROR) {\n return (uint(Error.MATH_ERROR), 0, 0, 0);\n }\n\n (mErr, exchangeRateMantissa) = exchangeRateStoredInternal();\n if (mErr != MathError.NO_ERROR) {\n return (uint(Error.MATH_ERROR), 0, 0, 0);\n }\n\n return (uint(Error.NO_ERROR), accountTokens[account], borrowBalance, exchangeRateMantissa);\n }\n\n /**\n * @notice Returns the current per-block supply interest rate for this vToken\n * @dev The calculation includes `flashLoanAmount` in the cash balance to account for active flash loans.\n * @return The supply interest rate per block, scaled by 1e18\n */\n function supplyRatePerBlock() external view override returns (uint) {\n return\n interestRateModel.getSupplyRate(\n _getCashPriorWithFlashLoan(),\n totalBorrows,\n totalReserves,\n reserveFactorMantissa\n );\n }\n\n /**\n * @notice Returns the current per-block borrow interest rate for this vToken\n * @dev The calculation includes `flashLoanAmount` in the cash balance to account for active flash loans.\n * @return The borrow interest rate per block, scaled by 1e18\n */\n function borrowRatePerBlock() external view override returns (uint) {\n return interestRateModel.getBorrowRate(_getCashPriorWithFlashLoan(), totalBorrows, totalReserves);\n }\n\n /**\n * @notice Get cash balance of this vToken in the underlying asset\n * @return The quantity of underlying asset owned by this contract\n */\n function getCash() external view override returns (uint) {\n return getCashPrior();\n }\n\n /**\n * @notice Governance function to set new threshold of block difference after which funds will be sent to the protocol share reserve\n * @param newReduceReservesBlockDelta_ block difference value\n */\n function setReduceReservesBlockDelta(uint256 newReduceReservesBlockDelta_) external {\n require(newReduceReservesBlockDelta_ > 0, \"Invalid Input\");\n ensureAllowed(\"setReduceReservesBlockDelta(uint256)\");\n emit NewReduceReservesBlockDelta(reduceReservesBlockDelta, newReduceReservesBlockDelta_);\n reduceReservesBlockDelta = newReduceReservesBlockDelta_;\n }\n\n /**\n * @notice Sets protocol share reserve contract address\n * @param protcolShareReserve_ The address of protocol share reserve contract\n */\n function setProtocolShareReserve(address payable protcolShareReserve_) external {\n // Check caller is admin\n ensureAdmin(msg.sender);\n ensureNonZeroAddress(protcolShareReserve_);\n emit NewProtocolShareReserve(protocolShareReserve, protcolShareReserve_);\n protocolShareReserve = protcolShareReserve_;\n }\n\n /**\n * @notice Transfers the underlying asset to the specified address for flash loan purposes.\n * @dev Can only be called by the Comptroller contract. This function performs the actual transfer of the underlying\n * asset by calling the `doTransferOut` internal function.\n * - The caller must be the Comptroller contract.\n * - Sets the flashLoanAmount to track the borrowed amount during the flash loan process.\n * @param to The address to which the underlying asset is to be transferred.\n * @param amount The amount of the underlying asset to transfer.\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\n * @custom:error FlashLoanAlreadyActive is thrown if there is already an active flash loan.\n * @custom:error InsufficientCash is thrown when the vToken does not have enough cash to lend\n * @custom:event Emits TransferOutUnderlyingFlashLoan event on successful transfer of amount to receiver\n */\n function transferOutUnderlyingFlashLoan(address payable to, uint256 amount) external nonReentrant {\n if (msg.sender != address(comptroller)) {\n revert InvalidComptroller();\n }\n\n if (flashLoanAmount > 0) {\n revert FlashLoanAlreadyActive();\n }\n\n if (getCashPrior() < amount) {\n revert InsufficientCash();\n }\n flashLoanAmount = amount;\n doTransferOut(to, amount);\n emit TransferOutUnderlyingFlashLoan(underlying, to, amount);\n }\n\n /**\n * @notice Transfers the underlying asset from the specified address during flash loan repayment.\n * @dev Can only be called by the Comptroller contract. This function performs the actual transfer of the underlying\n * asset by calling the `doTransferIn` internal function and handles protocol fee distribution.\n * - The caller must be the Comptroller contract.\n * - Transfers the protocol fee to the protocol share reserve.\n * - Resets the flashLoanAmount to 0 to complete the flash loan cycle.\n * @param from The address from which the underlying asset is to be transferred.\n * @param repaymentAmount The amount of the underlying asset being repaid by the receiver.\n * @param totalFee The total fee amount for the flash loan.\n * @param protocolFee The protocol fee amount to be transferred to the protocol share reserve.\n * @return actualAmountTransferred The actual amount transferred in from the receiver.\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\n * @custom:error InsufficientRepayment is thrown when the repayment amount is insufficient to cover the total fee\n * @custom:event Emits TransferInUnderlyingFlashLoan event on successful transfer of amount from the receiver to the vToken\n */\n function transferInUnderlyingFlashLoan(\n address payable from,\n uint256 repaymentAmount,\n uint256 totalFee,\n uint256 protocolFee\n ) external nonReentrant returns (uint256) {\n if (msg.sender != address(comptroller)) {\n revert InvalidComptroller();\n }\n\n uint256 actualAmountTransferred = doTransferIn(from, repaymentAmount);\n\n if (actualAmountTransferred < totalFee) {\n revert InsufficientRepayment(actualAmountTransferred, totalFee);\n }\n\n // Transfer protocol fee to protocol share reserve\n doTransferOut(protocolShareReserve, protocolFee);\n\n IProtocolShareReserve(protocolShareReserve).updateAssetsState(\n address(comptroller),\n underlying,\n IProtocolShareReserve.IncomeType.FLASHLOAN\n );\n\n // Reset flashLoanAmount to complete the flash loan cycle\n flashLoanAmount = 0;\n\n emit TransferInUnderlyingFlashLoan(underlying, from, actualAmountTransferred, totalFee, protocolFee);\n return actualAmountTransferred;\n }\n\n /**\n * @notice Sets flash loan status for the market\n * @param enabled True to enable flash loans, false to disable\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n * @custom:access Only Governance\n * @custom:event Emits FlashLoanStatusChanged event on success\n */\n function setFlashLoanEnabled(bool enabled) external returns (uint256) {\n ensureAllowed(\"setFlashLoanEnabled(bool)\");\n\n if (isFlashLoanEnabled != enabled) {\n emit FlashLoanStatusChanged(isFlashLoanEnabled, enabled);\n isFlashLoanEnabled = enabled;\n }\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Update flashLoan fee mantissa\n * @param flashLoanFeeMantissa_ FlashLoan fee, scaled by 1e18\n * @param flashLoanProtocolShare_ FlashLoan protocol fee share, transferred to protocol share reserve\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n * @custom:access Only Governance\n * @custom:error FlashLoanFeeTooHigh is thrown when flash loan fee exceeds maximum allowed\n * @custom:error FlashLoanProtocolShareTooHigh is thrown when flash loan fee protocol share exceeds maximum allowed\n * @custom:event Emits FlashLoanFeeUpdated event on success\n */\n function setFlashLoanFeeMantissa(\n uint256 flashLoanFeeMantissa_,\n uint256 flashLoanProtocolShare_\n ) external returns (uint256) {\n ensureAllowed(\"setFlashLoanFeeMantissa(uint256,uint256)\");\n\n if (flashLoanFeeMantissa_ > MANTISSA_ONE) {\n revert FlashLoanFeeTooHigh(flashLoanFeeMantissa_, MANTISSA_ONE);\n }\n\n if (flashLoanProtocolShare_ > MANTISSA_ONE) {\n revert FlashLoanProtocolShareTooHigh(flashLoanProtocolShare_, MANTISSA_ONE);\n }\n\n // Only proceed if values are changing\n if (\n flashLoanFeeMantissa != flashLoanFeeMantissa_ || flashLoanProtocolShareMantissa != flashLoanProtocolShare_\n ) {\n emit FlashLoanFeeUpdated(\n flashLoanFeeMantissa,\n flashLoanFeeMantissa_,\n flashLoanProtocolShareMantissa,\n flashLoanProtocolShare_\n );\n\n flashLoanFeeMantissa = flashLoanFeeMantissa_;\n flashLoanProtocolShareMantissa = flashLoanProtocolShare_;\n }\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Initialize the money market\n * @param comptroller_ The address of the Comptroller\n * @param interestRateModel_ The address of the interest rate model\n * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18\n * @param name_ EIP-20 name of this token\n * @param symbol_ EIP-20 symbol of this token\n * @param decimals_ EIP-20 decimal precision of this token\n */\n function initialize(\n ComptrollerInterface comptroller_,\n InterestRateModelV8 interestRateModel_,\n uint initialExchangeRateMantissa_,\n string memory name_,\n string memory symbol_,\n uint8 decimals_\n ) public {\n ensureAdmin(msg.sender);\n require(accrualBlockNumber == 0 && borrowIndex == 0, \"market may only be initialized once\");\n\n // Set initial exchange rate\n initialExchangeRateMantissa = initialExchangeRateMantissa_;\n require(initialExchangeRateMantissa > 0, \"initial exchange rate must be greater than zero.\");\n\n // Set the comptroller\n uint err = _setComptroller(comptroller_);\n require(err == uint(Error.NO_ERROR), \"setting comptroller failed\");\n\n // Initialize block number and borrow index (block number mocks depend on comptroller being set)\n accrualBlockNumber = block.number;\n borrowIndex = mantissaOne;\n\n // Set the interest rate model (depends on block number / borrow index)\n err = _setInterestRateModelFresh(interestRateModel_);\n require(err == uint(Error.NO_ERROR), \"setting interest rate model failed\");\n\n name = name_;\n symbol = symbol_;\n decimals = decimals_;\n\n // The counter starts true to prevent changing it from zero to non-zero (i.e. smaller cost/refund)\n _notEntered = true;\n }\n\n /**\n * @notice Accrue interest then return the up-to-date exchange rate\n * @return Calculated exchange rate scaled by 1e18\n */\n function exchangeRateCurrent() public override nonReentrant returns (uint) {\n require(accrueInterest() == uint(Error.NO_ERROR), \"accrue interest failed\");\n return exchangeRateStored();\n }\n\n /**\n * @notice Applies accrued interest to total borrows and reserves\n * @dev This calculates interest accrued from the last checkpointed block\n * up to the current block and writes new checkpoint to storage and\n * reduce spread reserves to protocol share reserve\n * if currentBlock - reduceReservesBlockNumber >= blockDelta\n */\n // @custom:event Emits AccrueInterest event\n function accrueInterest() public virtual override returns (uint) {\n /* Remember the initial block number */\n uint currentBlockNumber = block.number;\n uint accrualBlockNumberPrior = accrualBlockNumber;\n\n /* Short-circuit accumulating 0 interest */\n if (accrualBlockNumberPrior == currentBlockNumber) {\n return uint(Error.NO_ERROR);\n }\n\n /* Read the previous values out of storage */\n uint cashPrior = _getCashPriorWithFlashLoan();\n uint borrowsPrior = totalBorrows;\n uint reservesPrior = totalReserves;\n uint borrowIndexPrior = borrowIndex;\n\n /* Calculate the current borrow interest rate */\n uint borrowRateMantissa = interestRateModel.getBorrowRate(cashPrior, borrowsPrior, reservesPrior);\n require(borrowRateMantissa <= borrowRateMaxMantissa, \"borrow rate is absurdly high\");\n\n /* Calculate the number of blocks elapsed since the last accrual */\n (MathError mathErr, uint blockDelta) = subUInt(currentBlockNumber, accrualBlockNumberPrior);\n ensureNoMathError(mathErr);\n\n /*\n * Calculate the interest accumulated into borrows and reserves and the new index:\n * simpleInterestFactor = borrowRate * blockDelta\n * interestAccumulated = simpleInterestFactor * totalBorrows\n * totalBorrowsNew = interestAccumulated + totalBorrows\n * totalReservesNew = interestAccumulated * reserveFactor + totalReserves\n * borrowIndexNew = simpleInterestFactor * borrowIndex + borrowIndex\n */\n\n Exp memory simpleInterestFactor;\n uint interestAccumulated;\n uint totalBorrowsNew;\n uint totalReservesNew;\n uint borrowIndexNew;\n\n (mathErr, simpleInterestFactor) = mulScalar(Exp({ mantissa: borrowRateMantissa }), blockDelta);\n if (mathErr != MathError.NO_ERROR) {\n return\n failOpaque(\n Error.MATH_ERROR,\n FailureInfo.ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED,\n uint(mathErr)\n );\n }\n\n (mathErr, interestAccumulated) = mulScalarTruncate(simpleInterestFactor, borrowsPrior);\n if (mathErr != MathError.NO_ERROR) {\n return\n failOpaque(\n Error.MATH_ERROR,\n FailureInfo.ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED,\n uint(mathErr)\n );\n }\n\n (mathErr, totalBorrowsNew) = addUInt(interestAccumulated, borrowsPrior);\n if (mathErr != MathError.NO_ERROR) {\n return\n failOpaque(\n Error.MATH_ERROR,\n FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED,\n uint(mathErr)\n );\n }\n\n (mathErr, totalReservesNew) = mulScalarTruncateAddUInt(\n Exp({ mantissa: reserveFactorMantissa }),\n interestAccumulated,\n reservesPrior\n );\n if (mathErr != MathError.NO_ERROR) {\n return\n failOpaque(\n Error.MATH_ERROR,\n FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED,\n uint(mathErr)\n );\n }\n\n (mathErr, borrowIndexNew) = mulScalarTruncateAddUInt(simpleInterestFactor, borrowIndexPrior, borrowIndexPrior);\n if (mathErr != MathError.NO_ERROR) {\n return\n failOpaque(\n Error.MATH_ERROR,\n FailureInfo.ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED,\n uint(mathErr)\n );\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /* We write the previously calculated values into storage */\n accrualBlockNumber = currentBlockNumber;\n borrowIndex = borrowIndexNew;\n totalBorrows = totalBorrowsNew;\n totalReserves = totalReservesNew;\n\n (mathErr, blockDelta) = subUInt(currentBlockNumber, reduceReservesBlockNumber);\n ensureNoMathError(mathErr);\n if (blockDelta >= reduceReservesBlockDelta) {\n reduceReservesBlockNumber = currentBlockNumber;\n uint actualCash = getCashPrior();\n if (actualCash < totalReservesNew) {\n _reduceReservesFresh(actualCash);\n } else {\n _reduceReservesFresh(totalReservesNew);\n }\n }\n\n /* We emit an AccrueInterest event */\n emit AccrueInterest(cashPrior, interestAccumulated, borrowIndexNew, totalBorrowsNew);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Sets a new comptroller for the market\n * @dev Admin function to set a new comptroller\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n // @custom:event Emits NewComptroller event\n function _setComptroller(ComptrollerInterface newComptroller) public override returns (uint) {\n // Check caller is admin\n ensureAdmin(msg.sender);\n\n // Ensure invoke comptroller.isComptroller() returns true\n require(newComptroller.isComptroller(), \"marker method returned false\");\n\n // Emit NewComptroller(oldComptroller, newComptroller)\n emit NewComptroller(comptroller, newComptroller);\n\n // Set market's comptroller to newComptroller\n comptroller = newComptroller;\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Accrues interest and updates the interest rate model using _setInterestRateModelFresh\n * @dev Governance function to accrue interest and update the interest rate model\n * @param newInterestRateModel_ The new interest rate model to use\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function _setInterestRateModel(InterestRateModelV8 newInterestRateModel_) public override returns (uint) {\n ensureAllowed(\"_setInterestRateModel(address)\");\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted change of interest rate model failed\n return fail(Error(error), FailureInfo.SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED);\n }\n\n // _setInterestRateModelFresh emits interest-rate-model-update-specific logs on errors, so we don't need to.\n return _setInterestRateModelFresh(newInterestRateModel_);\n }\n\n /**\n * @notice Calculates the exchange rate from the underlying to the VToken\n * @dev This function does not accrue interest before calculating the exchange rate\n * @return Calculated exchange rate scaled by 1e18\n */\n function exchangeRateStored() public view override returns (uint) {\n (MathError err, uint result) = exchangeRateStoredInternal();\n ensureNoMathError(err);\n return result;\n }\n\n /**\n * @notice Return the borrow balance of account based on stored data\n * @param account The address whose balance should be calculated\n * @return The calculated balance\n */\n function borrowBalanceStored(address account) public view override returns (uint) {\n (MathError err, uint result) = borrowBalanceStoredInternal(account);\n ensureNoMathError(err);\n return result;\n }\n\n /**\n * @notice Opens a debt position for the borrower as part of flash loan repayment\n * @dev This function is specifically called during flash loan operations when the repayment\n * is insufficient to cover the full borrowed amount plus fees. It creates a debt position\n * for the unpaid balance. The function checks if the borrow is allowed, accrues interest,\n * and updates the borrower's balance. It also emits a Borrow event and calls the\n * comptroller's borrowVerify function. It reverts if the borrow is not allowed or\n * if the market's block number is not current.\n * @param borrower The address of the borrower who will have the debt position created\n * @param borrowAmount The amount of underlying asset that becomes debt (unpaid flash loan balance)\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\n */\n function flashLoanDebtPosition(address borrower, uint borrowAmount) external nonReentrant returns (uint256) {\n // Reverts if the caller is not the comptroller\n if (msg.sender != address(comptroller)) {\n revert InvalidComptroller();\n }\n\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors.\n return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED);\n }\n\n // borrowFresh emits borrow-specific logs on errors, so we don't need to\n return borrowFresh(borrower, payable(address(0)), borrowAmount, false);\n }\n\n /**\n * @notice Calculates the total fee and protocol fee for a flash loan..\n * @param amount The amount of the flash loan.\n * @return totalFee The total fee for the flash loan.\n * @return protocolFee The portion of the total fee allocated to the protocol.\n */\n function calculateFlashLoanFee(uint256 amount) public view returns (uint256, uint256) {\n MathError mErr;\n uint256 totalFee;\n uint256 protocolFee;\n\n (mErr, totalFee) = mulScalarTruncate(Exp({ mantissa: amount }), flashLoanFeeMantissa);\n ensureNoMathError(mErr);\n\n (mErr, protocolFee) = mulScalarTruncate(Exp({ mantissa: totalFee }), flashLoanProtocolShareMantissa);\n ensureNoMathError(mErr);\n\n return (totalFee, protocolFee);\n }\n\n /**\n * @notice Transfers `tokens` tokens from `src` to `dst` by `spender`\n * @dev Called by both `transfer` and `transferFrom` internally\n * @param spender The address of the account performing the transfer\n * @param src The address of the source account\n * @param dst The address of the destination account\n * @param tokens The number of tokens to transfer\n * @return Whether or not the transfer succeeded\n */\n function transferTokens(address spender, address src, address dst, uint tokens) internal returns (uint) {\n /* Fail if transfer not allowed */\n uint allowed = comptroller.transferAllowed(address(this), src, dst, tokens);\n if (allowed != 0) {\n return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.TRANSFER_COMPTROLLER_REJECTION, allowed);\n }\n\n /* Do not allow self-transfers */\n if (src == dst) {\n return fail(Error.BAD_INPUT, FailureInfo.TRANSFER_NOT_ALLOWED);\n }\n\n /* Get the allowance, infinite for the account owner */\n uint startingAllowance = 0;\n if (spender == src) {\n startingAllowance = type(uint256).max;\n } else {\n startingAllowance = transferAllowances[src][spender];\n }\n\n /* Do the calculations, checking for {under,over}flow */\n MathError mathErr;\n uint allowanceNew;\n uint srvTokensNew;\n uint dstTokensNew;\n\n (mathErr, allowanceNew) = subUInt(startingAllowance, tokens);\n if (mathErr != MathError.NO_ERROR) {\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ALLOWED);\n }\n\n (mathErr, srvTokensNew) = subUInt(accountTokens[src], tokens);\n if (mathErr != MathError.NO_ERROR) {\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ENOUGH);\n }\n\n (mathErr, dstTokensNew) = addUInt(accountTokens[dst], tokens);\n if (mathErr != MathError.NO_ERROR) {\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_TOO_MUCH);\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n accountTokens[src] = srvTokensNew;\n accountTokens[dst] = dstTokensNew;\n\n /* Eat some of the allowance (if necessary) */\n if (startingAllowance != type(uint256).max) {\n transferAllowances[src][spender] = allowanceNew;\n }\n\n /* We emit a Transfer event */\n emit Transfer(src, dst, tokens);\n\n comptroller.transferVerify(address(this), src, dst, tokens);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Sender supplies assets into the market and receives vTokens in exchange\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\n * @param mintAmount The amount of the underlying asset to supply\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\n */\n function mintInternal(uint mintAmount) internal nonReentrant returns (uint, uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted mint failed\n return (fail(Error(error), FailureInfo.MINT_ACCRUE_INTEREST_FAILED), 0);\n }\n\n // mintFresh emits the actual Mint event if successful and logs on errors, so we don't need to\n return mintFresh(msg.sender, mintAmount);\n }\n\n /**\n * @notice User supplies assets into the market and receives vTokens in exchange\n * @dev Assumes interest has already been accrued up to the current block\n * @param minter The address of the account which is supplying the assets\n * @param mintAmount The amount of the underlying asset to supply\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\n */\n function mintFresh(address minter, uint mintAmount) internal returns (uint, uint) {\n /* Fail if mint not allowed */\n uint allowed = comptroller.mintAllowed(address(this), minter, mintAmount);\n if (allowed != 0) {\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed), 0);\n }\n\n /* Verify market's block number equals current block number */\n if (accrualBlockNumber != block.number) {\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.MINT_FRESHNESS_CHECK), 0);\n }\n\n MintLocalVars memory vars;\n\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\n if (vars.mathErr != MathError.NO_ERROR) {\n return (failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)), 0);\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /*\n * We call `doTransferIn` for the minter and the mintAmount.\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\n * `doTransferIn` reverts if anything goes wrong, since we can't be sure if\n * side-effects occurred. The function returns the amount actually transferred,\n * in case of a fee. On success, the vToken holds an additional `actualMintAmount`\n * of cash.\n */\n vars.actualMintAmount = doTransferIn(minter, mintAmount);\n\n /*\n * We get the current exchange rate and calculate the number of vTokens to be minted:\n * mintTokens = actualMintAmount / exchangeRate\n */\n\n (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(\n vars.actualMintAmount,\n Exp({ mantissa: vars.exchangeRateMantissa })\n );\n ensureNoMathError(vars.mathErr);\n\n /*\n * We calculate the new total supply of vTokens and minter token balance, checking for overflow:\n * totalSupplyNew = totalSupply + mintTokens\n * accountTokensNew = accountTokens[minter] + mintTokens\n */\n (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens);\n ensureNoMathError(vars.mathErr);\n (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[minter], vars.mintTokens);\n ensureNoMathError(vars.mathErr);\n\n /* We write previously calculated values into storage */\n totalSupply = vars.totalSupplyNew;\n accountTokens[minter] = vars.accountTokensNew;\n\n /* We emit a Mint event, and a Transfer event */\n emit Mint(minter, vars.actualMintAmount, vars.mintTokens, vars.accountTokensNew);\n emit Transfer(address(this), minter, vars.mintTokens);\n\n /* We call the defense and prime accrue interest hook */\n comptroller.mintVerify(address(this), minter, vars.actualMintAmount, vars.mintTokens);\n\n return (uint(Error.NO_ERROR), vars.actualMintAmount);\n }\n\n /**\n * @notice Sender supplies assets into the market and receiver receives vTokens in exchange\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\n * @param receiver The address of the account which is receiving the vTokens\n * @param mintAmount The amount of the underlying asset to supply\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\n */\n function mintBehalfInternal(address receiver, uint mintAmount) internal nonReentrant returns (uint, uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted mintBehalf failed\n return (fail(Error(error), FailureInfo.MINT_ACCRUE_INTEREST_FAILED), 0);\n }\n\n // mintBehalfFresh emits the actual Mint event if successful and logs on errors, so we don't need to\n return mintBehalfFresh(msg.sender, receiver, mintAmount);\n }\n\n /**\n * @notice Payer supplies assets into the market and receiver receives vTokens in exchange\n * @dev Assumes interest has already been accrued up to the current block\n * @param payer The address of the account which is paying the underlying token\n * @param receiver The address of the account which is receiving vToken\n * @param mintAmount The amount of the underlying asset to supply\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\n */\n function mintBehalfFresh(address payer, address receiver, uint mintAmount) internal returns (uint, uint) {\n ensureNonZeroAddress(receiver);\n /* Fail if mint not allowed */\n uint allowed = comptroller.mintAllowed(address(this), receiver, mintAmount);\n if (allowed != 0) {\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed), 0);\n }\n\n /* Verify market's block number equals current block number */\n if (accrualBlockNumber != block.number) {\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.MINT_FRESHNESS_CHECK), 0);\n }\n\n MintLocalVars memory vars;\n\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\n if (vars.mathErr != MathError.NO_ERROR) {\n return (failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)), 0);\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /*\n * We call `doTransferIn` for the payer and the mintAmount.\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\n * `doTransferIn` reverts if anything goes wrong, since we can't be sure if\n * side-effects occurred. The function returns the amount actually transferred,\n * in case of a fee. On success, the vToken holds an additional `actualMintAmount`\n * of cash.\n */\n vars.actualMintAmount = doTransferIn(payer, mintAmount);\n\n /*\n * We get the current exchange rate and calculate the number of vTokens to be minted:\n * mintTokens = actualMintAmount / exchangeRate\n */\n\n (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(\n vars.actualMintAmount,\n Exp({ mantissa: vars.exchangeRateMantissa })\n );\n ensureNoMathError(vars.mathErr);\n\n /*\n * We calculate the new total supply of vTokens and receiver token balance, checking for overflow:\n * totalSupplyNew = totalSupply + mintTokens\n * accountTokensNew = accountTokens[receiver] + mintTokens\n */\n (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens);\n ensureNoMathError(vars.mathErr);\n\n (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[receiver], vars.mintTokens);\n ensureNoMathError(vars.mathErr);\n\n /* We write previously calculated values into storage */\n totalSupply = vars.totalSupplyNew;\n accountTokens[receiver] = vars.accountTokensNew;\n\n /* We emit a MintBehalf event, and a Transfer event */\n emit MintBehalf(payer, receiver, vars.actualMintAmount, vars.mintTokens, vars.accountTokensNew);\n emit Transfer(address(this), receiver, vars.mintTokens);\n\n /* We call the defense and prime accrue interest hook */\n comptroller.mintVerify(address(this), receiver, vars.actualMintAmount, vars.mintTokens);\n\n return (uint(Error.NO_ERROR), vars.actualMintAmount);\n }\n\n /**\n * @notice Redeemer redeems vTokens in exchange for the underlying assets, transferred to the receiver. Redeemer and receiver can be the same\n * address, or different addresses if the receiver was previously approved by the redeemer as a valid delegate (see MarketFacet.updateDelegate)\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\n * @param redeemer The address of the account which is redeeming the tokens\n * @param receiver The receiver of the tokens\n * @param redeemTokens The number of vTokens to redeem into underlying\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function redeemInternal(\n address redeemer,\n address payable receiver,\n uint redeemTokens\n ) internal nonReentrant returns (uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed\n return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED);\n }\n\n // redeemFresh emits redeem-specific logs on errors, so we don't need to\n return redeemFresh(redeemer, receiver, redeemTokens, 0);\n }\n\n /**\n * @notice Sender redeems underlying assets on behalf of some other address. This function is only available\n * for senders, explicitly marked as delegates of the supplier using `comptroller.updateDelegate`\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\n * @param redeemer The address of the account which is redeeming the tokens\n * @param receiver The receiver of the tokens, if called by a delegate\n * @param redeemAmount The amount of underlying to receive from redeeming vTokens\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function redeemUnderlyingInternal(\n address redeemer,\n address payable receiver,\n uint redeemAmount\n ) internal nonReentrant returns (uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed\n return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED);\n }\n\n // redeemFresh emits redeem-specific logs on errors, so we don't need to\n return redeemFresh(redeemer, receiver, 0, redeemAmount);\n }\n\n /**\n * @notice Redeemer redeems vTokens in exchange for the underlying assets, transferred to the receiver. Redeemer and receiver can be the same\n * address, or different addresses if the receiver was previously approved by the redeemer as a valid delegate (see MarketFacet.updateDelegate)\n * @dev Assumes interest has already been accrued up to the current block\n * @param redeemer The address of the account which is redeeming the tokens\n * @param receiver The receiver of the tokens\n * @param redeemTokensIn The number of vTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be non-zero)\n * @param redeemAmountIn The number of underlying tokens to receive from redeeming vTokens (only one of redeemTokensIn or redeemAmountIn may be non-zero)\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n // solhint-disable-next-line code-complexity\n function redeemFresh(\n address redeemer,\n address payable receiver,\n uint redeemTokensIn,\n uint redeemAmountIn\n ) internal returns (uint) {\n require(redeemTokensIn == 0 || redeemAmountIn == 0, \"one of redeemTokensIn or redeemAmountIn must be zero\");\n\n RedeemLocalVars memory vars;\n\n /* exchangeRate = invoke Exchange Rate Stored() */\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\n ensureNoMathError(vars.mathErr);\n\n /* If redeemTokensIn > 0: */\n if (redeemTokensIn > 0) {\n /*\n * We calculate the exchange rate and the amount of underlying to be redeemed:\n * redeemTokens = redeemTokensIn\n * redeemAmount = redeemTokensIn x exchangeRateCurrent\n */\n vars.redeemTokens = redeemTokensIn;\n\n (vars.mathErr, vars.redeemAmount) = mulScalarTruncate(\n Exp({ mantissa: vars.exchangeRateMantissa }),\n redeemTokensIn\n );\n ensureNoMathError(vars.mathErr);\n } else {\n /*\n * We get the current exchange rate and calculate the amount to be redeemed:\n * redeemTokens = redeemAmountIn / exchangeRate\n * redeemAmount = redeemAmountIn\n */\n\n (vars.mathErr, vars.redeemTokens) = divScalarByExpTruncate(\n redeemAmountIn,\n Exp({ mantissa: vars.exchangeRateMantissa })\n );\n ensureNoMathError(vars.mathErr);\n\n vars.redeemAmount = redeemAmountIn;\n }\n\n /* Fail if redeem not allowed */\n uint allowed = comptroller.redeemAllowed(address(this), redeemer, vars.redeemTokens);\n if (allowed != 0) {\n revert(\"math error\");\n }\n\n /* Verify market's block number equals current block number */\n if (accrualBlockNumber != block.number) {\n revert(\"math error\");\n }\n\n /*\n * We calculate the new total supply and redeemer balance, checking for underflow:\n * totalSupplyNew = totalSupply - redeemTokens\n * accountTokensNew = accountTokens[redeemer] - redeemTokens\n */\n (vars.mathErr, vars.totalSupplyNew) = subUInt(totalSupply, vars.redeemTokens);\n ensureNoMathError(vars.mathErr);\n\n (vars.mathErr, vars.accountTokensNew) = subUInt(accountTokens[redeemer], vars.redeemTokens);\n ensureNoMathError(vars.mathErr);\n\n /* Fail gracefully if protocol has insufficient cash */\n if (getCashPrior() < vars.redeemAmount) {\n revert(\"math error\");\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /* We write previously calculated values into storage */\n totalSupply = vars.totalSupplyNew;\n accountTokens[redeemer] = vars.accountTokensNew;\n\n /*\n * We invoke doTransferOut for the receiver and the redeemAmount.\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\n * On success, the vToken has redeemAmount less of cash.\n * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\n */\n\n uint feeAmount;\n uint remainedAmount;\n if (IComptroller(address(comptroller)).treasuryPercent() != 0) {\n (vars.mathErr, feeAmount) = mulUInt(\n vars.redeemAmount,\n IComptroller(address(comptroller)).treasuryPercent()\n );\n ensureNoMathError(vars.mathErr);\n\n (vars.mathErr, feeAmount) = divUInt(feeAmount, MANTISSA_ONE);\n ensureNoMathError(vars.mathErr);\n\n (vars.mathErr, remainedAmount) = subUInt(vars.redeemAmount, feeAmount);\n ensureNoMathError(vars.mathErr);\n\n address payable treasuryAddress = payable(IComptroller(address(comptroller)).treasuryAddress());\n doTransferOut(treasuryAddress, feeAmount);\n\n emit RedeemFee(redeemer, feeAmount, vars.redeemTokens);\n } else {\n remainedAmount = vars.redeemAmount;\n }\n\n doTransferOut(receiver, remainedAmount);\n\n /* We emit a Transfer event, and a Redeem event */\n emit Transfer(redeemer, address(this), vars.redeemTokens);\n emit Redeem(redeemer, remainedAmount, vars.redeemTokens, vars.accountTokensNew);\n\n /* We call the defense and prime accrue interest hook */\n comptroller.redeemVerify(address(this), redeemer, vars.redeemAmount, vars.redeemTokens);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Receiver gets the borrow on behalf of the borrower address\n * @param borrower The borrower, on behalf of whom to borrow\n * @param receiver The account that would receive the funds (can be the same as the borrower)\n * @param borrowAmount The amount of the underlying asset to borrow\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function borrowInternal(\n address borrower,\n address payable receiver,\n uint borrowAmount\n ) internal nonReentrant returns (uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\n return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED);\n }\n\n // borrowFresh emits borrow-specific logs on errors, so we don't need to\n return borrowFresh(borrower, receiver, borrowAmount, true);\n }\n\n /**\n * @notice Receiver gets the borrow on behalf of the borrower address, controls whether to do the transfer\n * @dev Before calling this function, ensure that the interest has been accrued\n * @param borrower The borrower, on behalf of whom to borrow\n * @param receiver The account that would receive the funds (can be the same as the borrower)\n * @param borrowAmount The amount of the underlying asset to borrow\n * @param shouldTransfer Whether to call doTransferOut for the receiver\n * @return uint Returns 0 on success, otherwise revert (see ErrorReporter.sol for details).\n */\n function borrowFresh(\n address borrower,\n address payable receiver,\n uint borrowAmount,\n bool shouldTransfer\n ) internal returns (uint) {\n /* Revert if borrow not allowed */\n uint allowed = comptroller.borrowAllowed(address(this), borrower, borrowAmount);\n\n if (allowed != 0) {\n revert(\"math error\");\n }\n\n /* Verify market's block number equals current block number */\n if (accrualBlockNumber != block.number) {\n revert(\"math error\");\n }\n\n /* Revert if protocol has insufficient underlying cash */\n if (shouldTransfer && getCashPrior() < borrowAmount) {\n revert(\"math error\");\n }\n\n BorrowLocalVars memory vars;\n\n /*\n * We calculate the new borrower and total borrow balances, failing on overflow:\n * accountBorrowsNew = accountBorrows + borrowAmount\n * totalBorrowsNew = totalBorrows + borrowAmount\n */\n (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower);\n ensureNoMathError(vars.mathErr);\n\n (vars.mathErr, vars.accountBorrowsNew) = addUInt(vars.accountBorrows, borrowAmount);\n ensureNoMathError(vars.mathErr);\n\n (vars.mathErr, vars.totalBorrowsNew) = addUInt(totalBorrows, borrowAmount);\n ensureNoMathError(vars.mathErr);\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /* We write the previously calculated values into storage */\n accountBorrows[borrower].principal = vars.accountBorrowsNew;\n accountBorrows[borrower].interestIndex = borrowIndex;\n totalBorrows = vars.totalBorrowsNew;\n\n if (shouldTransfer) {\n /*\n * We invoke doTransferOut for the receiver and the borrowAmount.\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\n * On success, the vToken borrowAmount less of cash.\n * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\n */\n doTransferOut(receiver, borrowAmount);\n }\n\n /* We emit a Borrow event */\n emit Borrow(borrower, borrowAmount, vars.accountBorrowsNew, vars.totalBorrowsNew);\n\n /* We call the defense and prime accrue interest hook */\n comptroller.borrowVerify(address(this), borrower, borrowAmount);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Sender repays their own borrow\n * @param repayAmount The amount to repay\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\n */\n function repayBorrowInternal(uint repayAmount) internal nonReentrant returns (uint, uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\n return (fail(Error(error), FailureInfo.REPAY_BORROW_ACCRUE_INTEREST_FAILED), 0);\n }\n\n // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to\n return repayBorrowFresh(msg.sender, msg.sender, repayAmount);\n }\n\n /**\n * @notice Sender repays a borrow belonging to another borrowing account\n * @param borrower The account with the debt being payed off\n * @param repayAmount The amount to repay\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\n */\n function repayBorrowBehalfInternal(address borrower, uint repayAmount) internal nonReentrant returns (uint, uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\n return (fail(Error(error), FailureInfo.REPAY_BEHALF_ACCRUE_INTEREST_FAILED), 0);\n }\n\n // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to\n return repayBorrowFresh(msg.sender, borrower, repayAmount);\n }\n\n /**\n * @notice Borrows are repaid by another user (possibly the borrower).\n * @param payer The account paying off the borrow\n * @param borrower The account with the debt being payed off\n * @param repayAmount The amount of undelrying tokens being returned\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\n */\n function repayBorrowFresh(address payer, address borrower, uint repayAmount) internal returns (uint, uint) {\n /* Fail if repayBorrow not allowed */\n uint allowed = comptroller.repayBorrowAllowed(address(this), payer, borrower, repayAmount);\n if (allowed != 0) {\n return (\n failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REPAY_BORROW_COMPTROLLER_REJECTION, allowed),\n 0\n );\n }\n\n /* Verify market's block number equals current block number */\n if (accrualBlockNumber != block.number) {\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.REPAY_BORROW_FRESHNESS_CHECK), 0);\n }\n\n RepayBorrowLocalVars memory vars;\n\n /* We remember the original borrowerIndex for verification purposes */\n vars.borrowerIndex = accountBorrows[borrower].interestIndex;\n\n /* We fetch the amount the borrower owes, with accumulated interest */\n (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower);\n if (vars.mathErr != MathError.NO_ERROR) {\n return (\n failOpaque(\n Error.MATH_ERROR,\n FailureInfo.REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\n uint(vars.mathErr)\n ),\n 0\n );\n }\n\n // caps the repayAmount to the actual owed amount\n vars.repayAmount = repayAmount >= vars.accountBorrows ? vars.accountBorrows : repayAmount;\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /*\n * We call doTransferIn for the payer and the repayAmount\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\n * On success, the vToken holds an additional repayAmount of cash.\n * doTransferIn reverts if anything goes wrong, since we can't be sure if side effects occurred.\n * it returns the amount actually transferred, in case of a fee.\n */\n vars.actualRepayAmount = doTransferIn(payer, vars.repayAmount);\n\n /*\n * We calculate the new borrower and total borrow balances, failing on underflow:\n * accountBorrowsNew = accountBorrows - actualRepayAmount\n * totalBorrowsNew = totalBorrows - actualRepayAmount\n */\n (vars.mathErr, vars.accountBorrowsNew) = subUInt(vars.accountBorrows, vars.actualRepayAmount);\n ensureNoMathError(vars.mathErr);\n\n (vars.mathErr, vars.totalBorrowsNew) = subUInt(totalBorrows, vars.actualRepayAmount);\n ensureNoMathError(vars.mathErr);\n\n /* We write the previously calculated values into storage */\n accountBorrows[borrower].principal = vars.accountBorrowsNew;\n accountBorrows[borrower].interestIndex = borrowIndex;\n totalBorrows = vars.totalBorrowsNew;\n\n /* We emit a RepayBorrow event */\n emit RepayBorrow(payer, borrower, vars.actualRepayAmount, vars.accountBorrowsNew, vars.totalBorrowsNew);\n\n /* We call the defense and prime accrue interest hook */\n comptroller.repayBorrowVerify(address(this), payer, borrower, vars.actualRepayAmount, vars.borrowerIndex);\n\n return (uint(Error.NO_ERROR), vars.actualRepayAmount);\n }\n\n /**\n * @notice The sender liquidates the borrowers collateral.\n * The collateral seized is transferred to the liquidator.\n * @param borrower The borrower of this vToken to be liquidated\n * @param vTokenCollateral The market in which to seize collateral from the borrower\n * @param repayAmount The amount of the underlying borrowed asset to repay\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\n */\n function liquidateBorrowInternal(\n address borrower,\n uint repayAmount,\n VTokenInterface vTokenCollateral\n ) internal nonReentrant returns (uint, uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed\n return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED), 0);\n }\n\n error = vTokenCollateral.accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed\n return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED), 0);\n }\n\n // liquidateBorrowFresh emits borrow-specific logs on errors, so we don't need to\n return liquidateBorrowFresh(msg.sender, borrower, repayAmount, vTokenCollateral);\n }\n\n /**\n * @notice The liquidator liquidates the borrowers collateral.\n * The collateral seized is transferred to the liquidator.\n * @param borrower The borrower of this vToken to be liquidated\n * @param liquidator The address repaying the borrow and seizing collateral\n * @param vTokenCollateral The market in which to seize collateral from the borrower\n * @param repayAmount The amount of the underlying borrowed asset to repay\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\n */\n // solhint-disable-next-line code-complexity\n function liquidateBorrowFresh(\n address liquidator,\n address borrower,\n uint repayAmount,\n VTokenInterface vTokenCollateral\n ) internal returns (uint, uint) {\n /* Fail if liquidate not allowed */\n uint allowed = comptroller.liquidateBorrowAllowed(\n address(this),\n address(vTokenCollateral),\n liquidator,\n borrower,\n repayAmount\n );\n if (allowed != 0) {\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_COMPTROLLER_REJECTION, allowed), 0);\n }\n\n /* Verify market's block number equals current block number */\n if (accrualBlockNumber != block.number) {\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_FRESHNESS_CHECK), 0);\n }\n\n /* Verify vTokenCollateral market's block number equals current block number */\n if (vTokenCollateral.accrualBlockNumber() != block.number) {\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_COLLATERAL_FRESHNESS_CHECK), 0);\n }\n\n /* Fail if borrower = liquidator */\n if (borrower == liquidator) {\n return (fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_LIQUIDATOR_IS_BORROWER), 0);\n }\n\n /* Fail if repayAmount = 0 */\n if (repayAmount == 0) {\n return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_ZERO), 0);\n }\n\n /* Fail if repayAmount = type(uint256).max */\n if (repayAmount == type(uint256).max) {\n return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX), 0);\n }\n\n /* Fail if repayBorrow fails */\n (uint repayBorrowError, uint actualRepayAmount) = repayBorrowFresh(liquidator, borrower, repayAmount);\n if (repayBorrowError != uint(Error.NO_ERROR)) {\n return (fail(Error(repayBorrowError), FailureInfo.LIQUIDATE_REPAY_BORROW_FRESH_FAILED), 0);\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /* We calculate the number of collateral tokens that will be seized */\n (uint amountSeizeError, uint seizeTokens) = comptroller.liquidateCalculateSeizeTokens(\n borrower,\n address(this),\n address(vTokenCollateral),\n actualRepayAmount\n );\n\n require(amountSeizeError == uint(Error.NO_ERROR), \"LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED\");\n\n /* Revert if borrower collateral token balance < seizeTokens */\n require(vTokenCollateral.balanceOf(borrower) >= seizeTokens, \"LIQUIDATE_SEIZE_TOO_MUCH\");\n\n // If this is also the collateral, run seizeInternal to avoid re-entrancy, otherwise make an external call\n uint seizeError;\n if (address(vTokenCollateral) == address(this)) {\n seizeError = seizeInternal(address(this), liquidator, borrower, seizeTokens);\n } else {\n seizeError = vTokenCollateral.seize(liquidator, borrower, seizeTokens);\n }\n\n /* Revert if seize tokens fails (since we cannot be sure of side effects) */\n require(seizeError == uint(Error.NO_ERROR), \"token seizure failed\");\n\n /* We emit a LiquidateBorrow event */\n emit LiquidateBorrow(liquidator, borrower, actualRepayAmount, address(vTokenCollateral), seizeTokens);\n\n /* We call the defense and prime accrue interest hook */\n comptroller.liquidateBorrowVerify(\n address(this),\n address(vTokenCollateral),\n liquidator,\n borrower,\n actualRepayAmount,\n seizeTokens\n );\n\n return (uint(Error.NO_ERROR), actualRepayAmount);\n }\n\n /**\n * @notice Transfers collateral tokens (this market) to the liquidator.\n * @dev Called only during an in-kind liquidation, or by liquidateBorrow during the liquidation of another vToken.\n * Its absolutely critical to use msg.sender as the seizer vToken and not a parameter.\n * @param seizerToken The contract seizing the collateral (i.e. borrowed vToken)\n * @param liquidator The account receiving seized collateral\n * @param borrower The account having collateral seized\n * @param seizeTokens The number of vTokens to seize\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function seizeInternal(\n address seizerToken,\n address liquidator,\n address borrower,\n uint seizeTokens\n ) internal returns (uint) {\n /* Fail if seize not allowed */\n uint allowed = comptroller.seizeAllowed(address(this), seizerToken, liquidator, borrower, seizeTokens);\n if (allowed != 0) {\n return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_SEIZE_COMPTROLLER_REJECTION, allowed);\n }\n\n /* Fail if borrower = liquidator */\n if (borrower == liquidator) {\n return fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER);\n }\n\n MathError mathErr;\n uint borrowerTokensNew;\n uint liquidatorTokensNew;\n\n /*\n * We calculate the new borrower and liquidator token balances, failing on underflow/overflow:\n * borrowerTokensNew = accountTokens[borrower] - seizeTokens\n * liquidatorTokensNew = accountTokens[liquidator] + seizeTokens\n */\n (mathErr, borrowerTokensNew) = subUInt(accountTokens[borrower], seizeTokens);\n if (mathErr != MathError.NO_ERROR) {\n return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED, uint(mathErr));\n }\n\n (mathErr, liquidatorTokensNew) = addUInt(accountTokens[liquidator], seizeTokens);\n if (mathErr != MathError.NO_ERROR) {\n return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED, uint(mathErr));\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /* We write the previously calculated values into storage */\n accountTokens[borrower] = borrowerTokensNew;\n accountTokens[liquidator] = liquidatorTokensNew;\n\n /* Emit a Transfer event */\n emit Transfer(borrower, liquidator, seizeTokens);\n\n /* We call the defense and prime accrue interest hook */\n comptroller.seizeVerify(address(this), seizerToken, liquidator, borrower, seizeTokens);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Sets a new reserve factor for the protocol (requires fresh interest accrual)\n * @dev Governance function to set a new reserve factor\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function _setReserveFactorFresh(uint newReserveFactorMantissa) internal returns (uint) {\n // Verify market's block number equals current block number\n if (accrualBlockNumber != block.number) {\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_RESERVE_FACTOR_FRESH_CHECK);\n }\n\n // Check newReserveFactor ≤ maxReserveFactor\n if (newReserveFactorMantissa > reserveFactorMaxMantissa) {\n return fail(Error.BAD_INPUT, FailureInfo.SET_RESERVE_FACTOR_BOUNDS_CHECK);\n }\n\n uint oldReserveFactorMantissa = reserveFactorMantissa;\n reserveFactorMantissa = newReserveFactorMantissa;\n\n emit NewReserveFactor(oldReserveFactorMantissa, newReserveFactorMantissa);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Accrues interest and adds reserves by transferring from `msg.sender`\n * @param addAmount Amount of addition to reserves\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function _addReservesInternal(uint addAmount) internal nonReentrant returns (uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed.\n return fail(Error(error), FailureInfo.ADD_RESERVES_ACCRUE_INTEREST_FAILED);\n }\n\n // _addReservesFresh emits reserve-addition-specific logs on errors, so we don't need to.\n (error, ) = _addReservesFresh(addAmount);\n return error;\n }\n\n /**\n * @notice Add reserves by transferring from caller\n * @dev Requires fresh interest accrual\n * @param addAmount Amount of addition to reserves\n * @return (uint, uint) An error code (0=success, otherwise a failure (see ErrorReporter.sol for details)) and the actual amount added, net token fees\n */\n function _addReservesFresh(uint addAmount) internal returns (uint, uint) {\n // totalReserves + actualAddAmount\n uint totalReservesNew;\n uint actualAddAmount;\n\n // We fail gracefully unless market's block number equals current block number\n if (accrualBlockNumber != block.number) {\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.ADD_RESERVES_FRESH_CHECK), actualAddAmount);\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /*\n * We call doTransferIn for the caller and the addAmount\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\n * On success, the vToken holds an additional addAmount of cash.\n * doTransferIn reverts if anything goes wrong, since we can't be sure if side effects occurred.\n * it returns the amount actually transferred, in case of a fee.\n */\n\n actualAddAmount = doTransferIn(msg.sender, addAmount);\n\n totalReservesNew = totalReserves + actualAddAmount;\n\n /* Revert on overflow */\n require(totalReservesNew >= totalReserves, \"add reserves unexpected overflow\");\n\n // Store reserves[n+1] = reserves[n] + actualAddAmount\n totalReserves = totalReservesNew;\n\n /* Emit NewReserves(admin, actualAddAmount, reserves[n+1]) */\n emit ReservesAdded(msg.sender, actualAddAmount, totalReservesNew);\n\n /* Return (NO_ERROR, actualAddAmount) */\n return (uint(Error.NO_ERROR), actualAddAmount);\n }\n\n /**\n * @notice Reduces reserves by transferring to protocol share reserve contract\n * @dev Requires fresh interest accrual\n * @param reduceAmount Amount of reduction to reserves\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function _reduceReservesFresh(uint reduceAmount) internal virtual returns (uint) {\n if (reduceAmount == 0) {\n return uint(Error.NO_ERROR);\n }\n\n // We fail gracefully unless market's block number equals current block number\n if (accrualBlockNumber != block.number) {\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDUCE_RESERVES_FRESH_CHECK);\n }\n\n // Fail gracefully if protocol has insufficient underlying cash\n if (getCashPrior() < reduceAmount) {\n return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDUCE_RESERVES_CASH_NOT_AVAILABLE);\n }\n\n // Check reduceAmount ≤ reserves[n] (totalReserves)\n if (reduceAmount > totalReserves) {\n return fail(Error.BAD_INPUT, FailureInfo.REDUCE_RESERVES_VALIDATION);\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n // Store reserves[n+1] = reserves[n] - reduceAmount\n totalReserves = totalReserves - reduceAmount;\n\n // doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\n doTransferOut(protocolShareReserve, reduceAmount);\n\n IProtocolShareReserve(protocolShareReserve).updateAssetsState(\n address(comptroller),\n underlying,\n IProtocolShareReserve.IncomeType.SPREAD\n );\n\n emit ReservesReduced(protocolShareReserve, reduceAmount, totalReserves);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice updates the interest rate model (requires fresh interest accrual)\n * @dev Governance function to update the interest rate model\n * @param newInterestRateModel the new interest rate model to use\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function _setInterestRateModelFresh(InterestRateModelV8 newInterestRateModel) internal returns (uint) {\n // We fail gracefully unless market's block number equals current block number\n if (accrualBlockNumber != block.number) {\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_INTEREST_RATE_MODEL_FRESH_CHECK);\n }\n\n // Ensure invoke newInterestRateModel.isInterestRateModel() returns true\n require(newInterestRateModel.isInterestRateModel(), \"marker method returned false\");\n\n // Emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel)\n emit NewMarketInterestRateModel(interestRateModel, newInterestRateModel);\n\n // Set the interest rate model to newInterestRateModel\n interestRateModel = newInterestRateModel;\n\n return uint(Error.NO_ERROR);\n }\n\n /*** Safe Token ***/\n\n /**\n * @dev Performs a transfer in, reverting upon failure. Returns the amount actually transferred to the protocol, in case of a fee.\n * This may revert due to insufficient balance or insufficient allowance.\n */\n function doTransferIn(address from, uint amount) internal virtual returns (uint);\n\n /**\n * @dev Performs a transfer out, ideally returning an explanatory error code upon failure rather than reverting.\n * If caller has not called checked protocol's balance, may revert due to insufficient cash held in the contract.\n * If caller has checked protocol's balance, and verified it is >= amount, this should not revert in normal conditions.\n */\n function doTransferOut(address payable to, uint amount) internal virtual;\n\n /**\n * @notice Return the borrow balance of account based on stored data\n * @param account The address whose balance should be calculated\n * @return Tuple of error code and the calculated balance or 0 if error code is non-zero\n */\n function borrowBalanceStoredInternal(address account) internal view returns (MathError, uint) {\n /* Note: we do not assert that the market is up to date */\n MathError mathErr;\n uint principalTimesIndex;\n uint result;\n\n /* Get borrowBalance and borrowIndex */\n BorrowSnapshot storage borrowSnapshot = accountBorrows[account];\n\n /* If borrowBalance = 0 then borrowIndex is likely also 0.\n * Rather than failing the calculation with a division by 0, we immediately return 0 in this case.\n */\n if (borrowSnapshot.principal == 0) {\n return (MathError.NO_ERROR, 0);\n }\n\n /* Calculate new borrow balance using the interest index:\n * recentBorrowBalance = borrower.borrowBalance * market.borrowIndex / borrower.borrowIndex\n */\n (mathErr, principalTimesIndex) = mulUInt(borrowSnapshot.principal, borrowIndex);\n if (mathErr != MathError.NO_ERROR) {\n return (mathErr, 0);\n }\n\n (mathErr, result) = divUInt(principalTimesIndex, borrowSnapshot.interestIndex);\n if (mathErr != MathError.NO_ERROR) {\n return (mathErr, 0);\n }\n\n return (MathError.NO_ERROR, result);\n }\n\n /**\n * @notice Calculates the exchange rate from the underlying to the vToken\n * @dev This function does not accrue interest before calculating the exchange rate\n * @return Tuple of error code and calculated exchange rate scaled by 1e18\n */\n function exchangeRateStoredInternal() internal view virtual returns (MathError, uint) {\n uint _totalSupply = totalSupply;\n if (_totalSupply == 0) {\n /*\n * If there are no tokens minted:\n * exchangeRate = initialExchangeRate\n */\n return (MathError.NO_ERROR, initialExchangeRateMantissa);\n } else {\n /*\n * Otherwise:\n * exchangeRate = (totalCash + totalBorrows + flashLoanAmount - totalReserves) / totalSupply\n */\n uint totalCash = _getCashPriorWithFlashLoan();\n uint cashPlusBorrowsMinusReserves;\n Exp memory exchangeRate;\n MathError mathErr;\n\n (mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(totalCash, totalBorrows, totalReserves);\n if (mathErr != MathError.NO_ERROR) {\n return (mathErr, 0);\n }\n\n (mathErr, exchangeRate) = getExp(cashPlusBorrowsMinusReserves, _totalSupply);\n if (mathErr != MathError.NO_ERROR) {\n return (mathErr, 0);\n }\n\n return (MathError.NO_ERROR, exchangeRate.mantissa);\n }\n }\n\n /**\n * @notice Gets balance of this contract including active flash loans\n * @return The quantity of underlying owned by this contract plus active flash loan amount\n */\n function _getCashPriorWithFlashLoan() internal view returns (uint) {\n return getCashPrior() + flashLoanAmount;\n }\n\n function ensureAllowed(string memory functionSig) private view {\n require(\n IAccessControlManagerV8(accessControlManager).isAllowedToCall(msg.sender, functionSig),\n \"access denied\"\n );\n }\n\n function ensureAdmin(address caller_) private view {\n require(caller_ == admin, \"Unauthorized\");\n }\n\n function ensureNoMathError(MathError mErr) private pure {\n require(mErr == MathError.NO_ERROR, \"math error\");\n }\n\n function ensureNonZeroAddress(address address_) private pure {\n require(address_ != address(0), \"zero address\");\n }\n\n /*** Safe Token ***/\n\n /**\n * @notice Gets balance of this contract in terms of the underlying\n * @dev This excludes the value of the current message, if any\n * @return The quantity of underlying owned by this contract\n */\n function getCashPrior() internal view virtual returns (uint);\n}\n" + }, + "contracts/Tokens/VTokens/VTokenInterfaces.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\nimport { ComptrollerInterface } from \"../../Comptroller/ComptrollerInterface.sol\";\nimport { InterestRateModelV8 } from \"../../InterestRateModels/InterestRateModelV8.sol\";\n\ncontract VTokenStorageBase {\n /**\n * @notice Container for borrow balance information\n * @member principal Total balance (with accrued interest), after applying the most recent balance-changing action\n * @member interestIndex Global borrowIndex as of the most recent balance-changing action\n */\n struct BorrowSnapshot {\n uint principal;\n uint interestIndex;\n }\n\n /**\n * @dev Guard variable for re-entrancy checks\n */\n bool internal _notEntered;\n\n /**\n * @notice EIP-20 token name for this token\n */\n string public name;\n\n /**\n * @notice EIP-20 token symbol for this token\n */\n string public symbol;\n\n /**\n * @notice EIP-20 token decimals for this token\n */\n uint8 public decimals;\n\n /**\n * @notice Maximum borrow rate that can ever be applied (.0005% / block)\n */\n\n uint internal constant borrowRateMaxMantissa = 0.0005e16;\n\n /**\n * @notice Maximum fraction of interest that can be set aside for reserves\n */\n uint internal constant reserveFactorMaxMantissa = 1e18;\n\n /**\n * @notice Administrator for this contract\n */\n address payable public admin;\n\n /**\n * @notice Pending administrator for this contract\n */\n address payable public pendingAdmin;\n\n /**\n * @notice Contract which oversees inter-vToken operations\n */\n ComptrollerInterface public comptroller;\n\n /**\n * @notice Model which tells what the current interest rate should be\n */\n InterestRateModelV8 public interestRateModel;\n\n /**\n * @notice Initial exchange rate used when minting the first VTokens (used when totalSupply = 0)\n */\n uint internal initialExchangeRateMantissa;\n\n /**\n * @notice Fraction of interest currently set aside for reserves\n */\n uint public reserveFactorMantissa;\n\n /**\n * @notice Block number that interest was last accrued at\n */\n uint public accrualBlockNumber;\n\n /**\n * @notice Accumulator of the total earned interest rate since the opening of the market\n */\n uint public borrowIndex;\n\n /**\n * @notice Total amount of outstanding borrows of the underlying in this market\n */\n uint public totalBorrows;\n\n /**\n * @notice Total amount of reserves of the underlying held in this market\n */\n uint public totalReserves;\n\n /**\n * @notice Total number of tokens in circulation\n */\n uint public totalSupply;\n\n /**\n * @notice Official record of token balances for each account\n */\n mapping(address => uint) internal accountTokens;\n\n /**\n * @notice Approved token transfer amounts on behalf of others\n */\n mapping(address => mapping(address => uint)) internal transferAllowances;\n\n /**\n * @notice Mapping of account addresses to outstanding borrow balances\n */\n mapping(address => BorrowSnapshot) internal accountBorrows;\n\n /**\n * @notice Underlying asset for this VToken\n */\n address public underlying;\n\n /**\n * @notice Implementation address for this contract\n */\n address public implementation;\n\n /**\n * @notice delta block after which reserves will be reduced\n */\n uint public reduceReservesBlockDelta;\n\n /**\n * @notice last block number at which reserves were reduced\n */\n uint public reduceReservesBlockNumber;\n\n /**\n * @notice address of protocol share reserve contract\n */\n address payable public protocolShareReserve;\n\n /**\n * @notice address of accessControlManager\n */\n\n address public accessControlManager;\n}\n\ncontract VTokenStorage is VTokenStorageBase {\n /**\n * @notice flashLoan is enabled for this market or not\n */\n bool public isFlashLoanEnabled;\n\n /**\n * @notice total fee percentage collected on flashLoan (scaled by 1e18)\n */\n uint256 public flashLoanFeeMantissa;\n\n /**\n * @notice fee percentage of flashLoan that goes to protocol (scaled by 1e18)\n */\n uint256 public flashLoanProtocolShareMantissa;\n\n /**\n * @notice Amount of flashLoan taken by the receiver\n * @dev This is used to track the amount of flashLoan taken to correctly calculate the exchange rate\n * during the flashLoan process. It is added to the total cash when calculating the exchange rate.\n */\n uint256 public flashLoanAmount;\n\n /**\n * @notice Tracked internal cash balance, immune to direct token transfers (donation attacks)\n * @dev Updated only via doTransferIn/doTransferOut. Must be initialized via sweepTokenAndSync() after upgrade.\n */\n uint256 public internalCash;\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[45] private __gap;\n}\n\nabstract contract VTokenInterface is VTokenStorage {\n /**\n * @notice Indicator that this is a vToken contract (for inspection)\n */\n bool public constant isVToken = true;\n\n /*** Market Events ***/\n\n /**\n * @notice Event emitted when interest is accrued\n */\n event AccrueInterest(uint cashPrior, uint interestAccumulated, uint borrowIndex, uint totalBorrows);\n\n /**\n * @notice Event emitted when tokens are minted\n */\n event Mint(address minter, uint mintAmount, uint mintTokens, uint256 totalSupply);\n\n /**\n * @notice Event emitted when tokens are minted behalf by payer to receiver\n */\n event MintBehalf(address payer, address receiver, uint mintAmount, uint mintTokens, uint256 totalSupply);\n\n /**\n * @notice Event emitted when tokens are redeemed\n */\n event Redeem(address redeemer, uint redeemAmount, uint redeemTokens, uint256 totalSupply);\n\n /**\n * @notice Event emitted when tokens are redeemed and fee is transferred\n */\n event RedeemFee(address redeemer, uint feeAmount, uint redeemTokens);\n\n /**\n * @notice Event emitted when underlying is borrowed\n */\n event Borrow(address borrower, uint borrowAmount, uint accountBorrows, uint totalBorrows);\n\n /**\n * @notice Event emitted when a borrow is repaid\n */\n event RepayBorrow(address payer, address borrower, uint repayAmount, uint accountBorrows, uint totalBorrows);\n\n /**\n * @notice Event emitted when a borrow is liquidated\n */\n event LiquidateBorrow(\n address liquidator,\n address borrower,\n uint repayAmount,\n address vTokenCollateral,\n uint seizeTokens\n );\n\n /*** Admin Events ***/\n\n /**\n * @notice Event emitted when pendingAdmin is changed\n */\n event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);\n\n /**\n * @notice Event emitted when pendingAdmin is accepted, which means admin has been updated\n */\n event NewAdmin(address oldAdmin, address newAdmin);\n\n /**\n * @notice Event emitted when comptroller is changed\n */\n event NewComptroller(ComptrollerInterface oldComptroller, ComptrollerInterface newComptroller);\n\n /**\n * @notice Event emitted when interestRateModel is changed\n */\n event NewMarketInterestRateModel(\n InterestRateModelV8 oldInterestRateModel,\n InterestRateModelV8 newInterestRateModel\n );\n\n /**\n * @notice Event emitted when the reserve factor is changed\n */\n event NewReserveFactor(uint oldReserveFactorMantissa, uint newReserveFactorMantissa);\n\n /**\n * @notice Event emitted when the reserves are added\n */\n event ReservesAdded(address benefactor, uint addAmount, uint newTotalReserves);\n\n /**\n * @notice Event emitted when the reserves are reduced\n */\n event ReservesReduced(address protocolShareReserve, uint reduceAmount, uint newTotalReserves);\n\n /**\n * @notice EIP20 Transfer event\n */\n event Transfer(address indexed from, address indexed to, uint amount);\n\n /**\n * @notice EIP20 Approval event\n */\n event Approval(address indexed owner, address indexed spender, uint amount);\n\n /**\n * @notice Event emitted when block delta for reduce reserves get updated\n */\n event NewReduceReservesBlockDelta(uint256 oldReduceReservesBlockDelta, uint256 newReduceReservesBlockDelta);\n\n /**\n * @notice Event emitted when address of ProtocolShareReserve contract get updated\n */\n event NewProtocolShareReserve(address indexed oldProtocolShareReserve, address indexed newProtocolShareReserve);\n\n /**\n * @notice Emitted when access control address is changed by admin\n */\n event NewAccessControlManager(address oldAccessControlAddress, address newAccessControlAddress);\n\n /**\n * @notice Event emitted when flashLoanEnabled status is changed\n */\n event FlashLoanStatusChanged(bool previousStatus, bool newStatus);\n\n /**\n * @notice Event emitted when asset is transferred to receiver\n */\n event TransferOutUnderlyingFlashLoan(address asset, address receiver, uint256 amount);\n\n /**\n * @notice Event emitted when asset is transferred from sender and verified\n */\n event TransferInUnderlyingFlashLoan(\n address indexed asset,\n address indexed sender,\n uint256 amount,\n uint256 totalFee,\n uint256 protocolFee\n );\n\n /**\n * @notice Event emitted when internalCash is synced with actual token balance\n */\n event CashSynced(uint256 oldInternalCash, uint256 newInternalCash);\n\n /**\n * @notice Event emitted when excess tokens are swept by admin\n */\n event TokenSwept(address indexed recipient, uint256 amount);\n\n /**\n * @notice Event emitted when flashLoan fee mantissa is updated\n */\n event FlashLoanFeeUpdated(\n uint256 oldFlashLoanFeeMantissa,\n uint256 newFlashLoanFeeMantissa,\n uint256 oldFlashLoanProtocolShare,\n uint256 newFlashLoanProtocolShare\n );\n\n // @notice Thrown when comptroller is not valid\n error InvalidComptroller();\n\n // @notice Thrown when there is already an active flashLoan\n error FlashLoanAlreadyActive();\n\n /// @notice Thrown when flash loan fee exceeds maximum allowed\n error FlashLoanFeeTooHigh(uint256 fee, uint256 maxFee);\n\n /// @notice Thrown when flash loan fee protocol share exceeds maximum allowed\n error FlashLoanProtocolShareTooHigh(uint256 fee, uint256 maxFee);\n\n // @notice Thrown when the vToken does not have enough cash to lend\n error InsufficientCash();\n\n /// @notice Thrown when the repayment amount is insufficient to cover the total fee\n error InsufficientRepayment(uint256 actualAmount, uint256 requiredTotalFee);\n\n /*** User Interface ***/\n\n function transfer(address dst, uint amount) external virtual returns (bool);\n\n function transferFrom(address src, address dst, uint amount) external virtual returns (bool);\n\n function approve(address spender, uint amount) external virtual returns (bool);\n\n function balanceOfUnderlying(address owner) external virtual returns (uint);\n\n function totalBorrowsCurrent() external virtual returns (uint);\n\n function borrowBalanceCurrent(address account) external virtual returns (uint);\n\n function seize(address liquidator, address borrower, uint seizeTokens) external virtual returns (uint);\n\n /*** Admin Function ***/\n function _setPendingAdmin(address payable newPendingAdmin) external virtual returns (uint);\n\n /*** Admin Function ***/\n function _acceptAdmin() external virtual returns (uint);\n\n /*** Admin Function ***/\n function _setReserveFactor(uint newReserveFactorMantissa) external virtual returns (uint);\n\n /*** Admin Function ***/\n function _reduceReserves(uint reduceAmount) external virtual returns (uint);\n\n function balanceOf(address owner) external view virtual returns (uint);\n\n function allowance(address owner, address spender) external view virtual returns (uint);\n\n function getAccountSnapshot(address account) external view virtual returns (uint, uint, uint, uint);\n\n function borrowRatePerBlock() external view virtual returns (uint);\n\n function supplyRatePerBlock() external view virtual returns (uint);\n\n function getCash() external view virtual returns (uint);\n\n function exchangeRateCurrent() public virtual returns (uint);\n\n function accrueInterest() public virtual returns (uint);\n\n /*** Admin Function ***/\n function _setComptroller(ComptrollerInterface newComptroller) public virtual returns (uint);\n\n /*** Admin Function ***/\n function _setInterestRateModel(InterestRateModelV8 newInterestRateModel) public virtual returns (uint);\n\n function borrowBalanceStored(address account) public view virtual returns (uint);\n\n function exchangeRateStored() public view virtual returns (uint);\n}\n\ninterface VBep20Interface {\n /*** User Interface ***/\n\n function mint(uint mintAmount) external returns (uint);\n\n function mintBehalf(address receiver, uint mintAmount) external returns (uint);\n\n function redeem(uint redeemTokens) external returns (uint);\n\n function redeemUnderlying(uint redeemAmount) external returns (uint);\n\n function borrow(uint borrowAmount) external returns (uint);\n\n function repayBorrow(uint repayAmount) external returns (uint);\n\n function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint);\n\n function liquidateBorrow(\n address borrower,\n uint repayAmount,\n VTokenInterface vTokenCollateral\n ) external returns (uint);\n\n /*** Admin Functions ***/\n\n function _addReserves(uint addAmount) external returns (uint);\n}\n\ninterface VDelegatorInterface {\n /**\n * @notice Emitted when implementation is changed\n */\n event NewImplementation(address oldImplementation, address newImplementation);\n\n /**\n * @notice Called by the admin to update the implementation of the delegator\n * @param implementation_ The address of the new implementation for delegation\n * @param allowResign Flag to indicate whether to call _resignImplementation on the old implementation\n * @param becomeImplementationData The encoded bytes data to be passed to _becomeImplementation\n */\n function _setImplementation(\n address implementation_,\n bool allowResign,\n bytes memory becomeImplementationData\n ) external;\n}\n\ninterface VDelegateInterface {\n /**\n * @notice Called by the delegator on a delegate to initialize it for duty\n * @dev Should revert if any issues arise which make it unfit for delegation\n * @param data The encoded bytes data for any initialization\n */\n function _becomeImplementation(bytes memory data) external;\n\n /**\n * @notice Called by the delegator on a delegate to forfeit its responsibility\n */\n function _resignImplementation() external;\n}\n" + }, + "contracts/Utils/CarefulMath.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n/**\n * @title Careful Math\n * @author Venus\n * @notice Derived from OpenZeppelin's SafeMath library\n * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol\n */\ncontract CarefulMath {\n /**\n * @dev Possible error codes that we can return\n */\n enum MathError {\n NO_ERROR,\n DIVISION_BY_ZERO,\n INTEGER_OVERFLOW,\n INTEGER_UNDERFLOW\n }\n\n /**\n * @dev Multiplies two numbers, returns an error on overflow.\n */\n function mulUInt(uint a, uint b) internal pure returns (MathError, uint) {\n if (a == 0) {\n return (MathError.NO_ERROR, 0);\n }\n\n uint c;\n unchecked {\n c = a * b;\n }\n\n if (c / a != b) {\n return (MathError.INTEGER_OVERFLOW, 0);\n } else {\n return (MathError.NO_ERROR, c);\n }\n }\n\n /**\n * @dev Integer division of two numbers, truncating the quotient.\n */\n function divUInt(uint a, uint b) internal pure returns (MathError, uint) {\n if (b == 0) {\n return (MathError.DIVISION_BY_ZERO, 0);\n }\n\n return (MathError.NO_ERROR, a / b);\n }\n\n /**\n * @dev Subtracts two numbers, returns an error on overflow (i.e. if subtrahend is greater than minuend).\n */\n function subUInt(uint a, uint b) internal pure returns (MathError, uint) {\n if (b <= a) {\n unchecked {\n return (MathError.NO_ERROR, a - b);\n }\n } else {\n return (MathError.INTEGER_UNDERFLOW, 0);\n }\n }\n\n /**\n * @dev Adds two numbers, returns an error on overflow.\n */\n function addUInt(uint a, uint b) internal pure returns (MathError, uint) {\n uint c;\n unchecked {\n c = a + b;\n }\n\n if (c >= a) {\n return (MathError.NO_ERROR, c);\n } else {\n return (MathError.INTEGER_OVERFLOW, 0);\n }\n }\n\n /**\n * @dev add a and b and then subtract c\n */\n function addThenSubUInt(uint a, uint b, uint c) internal pure returns (MathError, uint) {\n (MathError err0, uint sum) = addUInt(a, b);\n\n if (err0 != MathError.NO_ERROR) {\n return (err0, 0);\n }\n\n return subUInt(sum, c);\n }\n}\n" + }, + "contracts/Utils/ErrorReporter.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { WeightFunction } from \"../Comptroller/Diamond/interfaces/IFacetBase.sol\";\n\ncontract ComptrollerErrorReporter {\n /// @notice Thrown when You are already in the selected pool.\n error AlreadyInSelectedPool();\n\n /// @notice Thrown when One or more of your assets are not compatible with the selected pool.\n error IncompatibleBorrowedAssets();\n\n /// @notice Thrown when Switching to this pool would fail the liquidity check or lead to liquidation.\n error LiquidityCheckFailed(uint256 errorCode, uint256 shortfall);\n\n /// @notice Thrown when trying to call pool-specific methods on the Core Pool\n error InvalidOperationForCorePool();\n\n /// @notice Thrown when input array lengths do not match\n error ArrayLengthMismatch();\n\n /// @notice Thrown when market trying to add in a pool is not listed in the core pool\n error MarketNotListedInCorePool();\n\n /// @notice Thrown when market is not set in the _poolMarkets mapping\n error MarketConfigNotFound();\n\n /// @notice Thrown when borrowing is not allowed in the selected pool for a given market.\n error BorrowNotAllowedInPool();\n\n /// @notice Thrown when trying to remove a market that is not listed in the given pool.\n error PoolMarketNotFound(uint96 poolId, address vToken);\n\n /// @notice Thrown when a given pool ID does not exist\n error PoolDoesNotExist(uint96 poolId);\n\n /// @notice Thrown when the pool label is empty\n error EmptyPoolLabel();\n\n /// @notice Thrown when a vToken is already listed in the specified pool\n error MarketAlreadyListed(uint96 poolId, address vToken);\n\n /// @notice Thrown when an invalid weighting strategy is provided\n error InvalidWeightingStrategy(WeightFunction strategy);\n\n // @notice Thrown when no assets are requested for flash loan\n error NoAssetsRequested();\n\n // @notice Thrown when invalid flash loan parameters are provided\n error InvalidFlashLoanParams();\n\n // @notice Thrown when flash loan is not enabled on the vToken\n error FlashLoanNotEnabled();\n\n // @notice Thrown when the sender is not authorized to use flashloan onBehalfOf\n error SenderNotAuthorizedForFlashLoan(address sender);\n\n // @notice Thrown when the onBehalfOf didn't approve the contract that receives flashloan\n error NotAnApprovedDelegate();\n\n // @notice Thrown when executeOperation on the receiver contract fails\n error ExecuteFlashLoanFailed();\n\n // @notice Thrown when the requested amount is zero\n error InvalidAmount();\n\n // @notice Thrown when failing to create a debt position in mode 1\n error FailedToCreateDebtPosition();\n\n /// @notice Thrown when attempting to interact with an inactive pool\n error InactivePool(uint96 poolId);\n\n /// @notice Thrown when repayment amount is insufficient to cover the fee\n error NotEnoughRepayment(uint256 repaid, uint256 required);\n\n /// @notice Thrown when the vToken market is not listed\n error MarketNotListed(address vToken);\n\n /// @notice Thrown when flash loans are paused system-wide\n error FlashLoanPausedSystemWide();\n\n /// @notice Thrown when too many assets are requested in a single flash loan\n error TooManyAssetsRequested(uint256 requested, uint256 maximum);\n\n enum Error {\n NO_ERROR,\n UNAUTHORIZED,\n COMPTROLLER_MISMATCH,\n INSUFFICIENT_SHORTFALL,\n INSUFFICIENT_LIQUIDITY,\n INVALID_CLOSE_FACTOR,\n INVALID_COLLATERAL_FACTOR,\n INVALID_LIQUIDATION_INCENTIVE,\n MARKET_NOT_ENTERED, // no longer possible\n MARKET_NOT_LISTED,\n MARKET_ALREADY_LISTED,\n MATH_ERROR,\n NONZERO_BORROW_BALANCE,\n PRICE_ERROR,\n REJECTION,\n SNAPSHOT_ERROR,\n TOO_MANY_ASSETS,\n TOO_MUCH_REPAY,\n INSUFFICIENT_BALANCE_FOR_VAI,\n MARKET_NOT_COLLATERAL,\n INVALID_LIQUIDATION_THRESHOLD\n }\n\n enum FailureInfo {\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\n ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK,\n EXIT_MARKET_BALANCE_OWED,\n EXIT_MARKET_REJECTION,\n SET_CLOSE_FACTOR_OWNER_CHECK,\n SET_CLOSE_FACTOR_VALIDATION,\n SET_COLLATERAL_FACTOR_OWNER_CHECK,\n SET_COLLATERAL_FACTOR_NO_EXISTS,\n SET_COLLATERAL_FACTOR_VALIDATION,\n SET_COLLATERAL_FACTOR_WITHOUT_PRICE,\n SET_IMPLEMENTATION_OWNER_CHECK,\n SET_LIQUIDATION_INCENTIVE_OWNER_CHECK,\n SET_LIQUIDATION_INCENTIVE_VALIDATION,\n SET_MAX_ASSETS_OWNER_CHECK,\n SET_PENDING_ADMIN_OWNER_CHECK,\n SET_PENDING_IMPLEMENTATION_OWNER_CHECK,\n SET_PRICE_ORACLE_OWNER_CHECK,\n SUPPORT_MARKET_EXISTS,\n SUPPORT_MARKET_OWNER_CHECK,\n SET_PAUSE_GUARDIAN_OWNER_CHECK,\n SET_VAI_MINT_RATE_CHECK,\n SET_VAICONTROLLER_OWNER_CHECK,\n SET_MINTED_VAI_REJECTION,\n SET_TREASURY_OWNER_CHECK,\n UNLIST_MARKET_NOT_LISTED,\n SET_LIQUIDATION_THRESHOLD_VALIDATION,\n COLLATERAL_FACTOR_GREATER_THAN_LIQUIDATION_THRESHOLD\n }\n\n /**\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\n **/\n event Failure(uint error, uint info, uint detail);\n\n /**\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\n */\n function fail(Error err, FailureInfo info) internal returns (uint) {\n emit Failure(uint(err), uint(info), 0);\n\n return uint(err);\n }\n\n /**\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\n */\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\n emit Failure(uint(err), uint(info), opaqueError);\n\n return uint(err);\n }\n}\n\ncontract TokenErrorReporter {\n enum Error {\n NO_ERROR,\n UNAUTHORIZED,\n BAD_INPUT,\n COMPTROLLER_REJECTION,\n COMPTROLLER_CALCULATION_ERROR,\n INTEREST_RATE_MODEL_ERROR,\n INVALID_ACCOUNT_PAIR,\n INVALID_CLOSE_AMOUNT_REQUESTED,\n INVALID_COLLATERAL_FACTOR,\n MATH_ERROR,\n MARKET_NOT_FRESH,\n MARKET_NOT_LISTED,\n TOKEN_INSUFFICIENT_ALLOWANCE,\n TOKEN_INSUFFICIENT_BALANCE,\n TOKEN_INSUFFICIENT_CASH,\n TOKEN_TRANSFER_IN_FAILED,\n TOKEN_TRANSFER_OUT_FAILED,\n TOKEN_PRICE_ERROR\n }\n\n /*\n * Note: FailureInfo (but not Error) is kept in alphabetical order\n * This is because FailureInfo grows significantly faster, and\n * the order of Error has some meaning, while the order of FailureInfo\n * is entirely arbitrary.\n */\n enum FailureInfo {\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\n ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED,\n ACCRUE_INTEREST_BORROW_RATE_CALCULATION_FAILED,\n ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED,\n ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED,\n ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED,\n ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED,\n BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\n BORROW_ACCRUE_INTEREST_FAILED,\n BORROW_CASH_NOT_AVAILABLE,\n BORROW_FRESHNESS_CHECK,\n BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED,\n BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED,\n BORROW_MARKET_NOT_LISTED,\n BORROW_COMPTROLLER_REJECTION,\n LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED,\n LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED,\n LIQUIDATE_COLLATERAL_FRESHNESS_CHECK,\n LIQUIDATE_COMPTROLLER_REJECTION,\n LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED,\n LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX,\n LIQUIDATE_CLOSE_AMOUNT_IS_ZERO,\n LIQUIDATE_FRESHNESS_CHECK,\n LIQUIDATE_LIQUIDATOR_IS_BORROWER,\n LIQUIDATE_REPAY_BORROW_FRESH_FAILED,\n LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED,\n LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED,\n LIQUIDATE_SEIZE_COMPTROLLER_REJECTION,\n LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER,\n LIQUIDATE_SEIZE_TOO_MUCH,\n MINT_ACCRUE_INTEREST_FAILED,\n MINT_COMPTROLLER_REJECTION,\n MINT_EXCHANGE_CALCULATION_FAILED,\n MINT_EXCHANGE_RATE_READ_FAILED,\n MINT_FRESHNESS_CHECK,\n MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED,\n MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED,\n MINT_TRANSFER_IN_FAILED,\n MINT_TRANSFER_IN_NOT_POSSIBLE,\n REDEEM_ACCRUE_INTEREST_FAILED,\n REDEEM_COMPTROLLER_REJECTION,\n REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED,\n REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED,\n REDEEM_EXCHANGE_RATE_READ_FAILED,\n REDEEM_FRESHNESS_CHECK,\n REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED,\n REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED,\n REDEEM_TRANSFER_OUT_NOT_POSSIBLE,\n REDUCE_RESERVES_ACCRUE_INTEREST_FAILED,\n REDUCE_RESERVES_ADMIN_CHECK,\n REDUCE_RESERVES_CASH_NOT_AVAILABLE,\n REDUCE_RESERVES_FRESH_CHECK,\n REDUCE_RESERVES_VALIDATION,\n REPAY_BEHALF_ACCRUE_INTEREST_FAILED,\n REPAY_BORROW_ACCRUE_INTEREST_FAILED,\n REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\n REPAY_BORROW_COMPTROLLER_REJECTION,\n REPAY_BORROW_FRESHNESS_CHECK,\n REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED,\n REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED,\n REPAY_BORROW_TRANSFER_IN_NOT_POSSIBLE,\n SET_COLLATERAL_FACTOR_OWNER_CHECK,\n SET_COLLATERAL_FACTOR_VALIDATION,\n SET_COMPTROLLER_OWNER_CHECK,\n SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED,\n SET_INTEREST_RATE_MODEL_FRESH_CHECK,\n SET_INTEREST_RATE_MODEL_OWNER_CHECK,\n SET_MAX_ASSETS_OWNER_CHECK,\n SET_ORACLE_MARKET_NOT_LISTED,\n SET_PENDING_ADMIN_OWNER_CHECK,\n SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED,\n SET_RESERVE_FACTOR_ADMIN_CHECK,\n SET_RESERVE_FACTOR_FRESH_CHECK,\n SET_RESERVE_FACTOR_BOUNDS_CHECK,\n TRANSFER_COMPTROLLER_REJECTION,\n TRANSFER_NOT_ALLOWED,\n TRANSFER_NOT_ENOUGH,\n TRANSFER_TOO_MUCH,\n ADD_RESERVES_ACCRUE_INTEREST_FAILED,\n ADD_RESERVES_FRESH_CHECK,\n ADD_RESERVES_TRANSFER_IN_NOT_POSSIBLE,\n TOKEN_GET_UNDERLYING_PRICE_ERROR,\n REPAY_VAI_COMPTROLLER_REJECTION,\n REPAY_VAI_FRESHNESS_CHECK,\n VAI_MINT_EXCHANGE_CALCULATION_FAILED,\n SFT_MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED\n }\n\n /**\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\n **/\n event Failure(uint error, uint info, uint detail);\n\n /**\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\n */\n function fail(Error err, FailureInfo info) internal returns (uint) {\n emit Failure(uint(err), uint(info), 0);\n\n return uint(err);\n }\n\n /**\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\n */\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\n emit Failure(uint(err), uint(info), opaqueError);\n\n return uint(err);\n }\n}\n\ncontract VAIControllerErrorReporter {\n enum Error {\n NO_ERROR,\n UNAUTHORIZED, // The sender is not authorized to perform this action.\n REJECTION, // The action would violate the comptroller, vaicontroller policy.\n SNAPSHOT_ERROR, // The comptroller could not get the account borrows and exchange rate from the market.\n PRICE_ERROR, // The comptroller could not obtain a required price of an asset.\n MATH_ERROR, // A math calculation error occurred.\n INSUFFICIENT_BALANCE_FOR_VAI // Caller does not have sufficient balance to mint VAI.\n }\n\n enum FailureInfo {\n SET_PENDING_ADMIN_OWNER_CHECK,\n SET_PENDING_IMPLEMENTATION_OWNER_CHECK,\n SET_COMPTROLLER_OWNER_CHECK,\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\n ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK,\n VAI_MINT_REJECTION,\n VAI_BURN_REJECTION,\n VAI_LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED,\n VAI_LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED,\n VAI_LIQUIDATE_COLLATERAL_FRESHNESS_CHECK,\n VAI_LIQUIDATE_COMPTROLLER_REJECTION,\n VAI_LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED,\n VAI_LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX,\n VAI_LIQUIDATE_CLOSE_AMOUNT_IS_ZERO,\n VAI_LIQUIDATE_FRESHNESS_CHECK,\n VAI_LIQUIDATE_LIQUIDATOR_IS_BORROWER,\n VAI_LIQUIDATE_REPAY_BORROW_FRESH_FAILED,\n VAI_LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED,\n VAI_LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED,\n VAI_LIQUIDATE_SEIZE_COMPTROLLER_REJECTION,\n VAI_LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER,\n VAI_LIQUIDATE_SEIZE_TOO_MUCH,\n MINT_FEE_CALCULATION_FAILED,\n SET_TREASURY_OWNER_CHECK\n }\n\n /**\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\n **/\n event Failure(uint error, uint info, uint detail);\n\n /**\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\n */\n function fail(Error err, FailureInfo info) internal returns (uint) {\n emit Failure(uint(err), uint(info), 0);\n\n return uint(err);\n }\n\n /**\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\n */\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\n emit Failure(uint(err), uint(info), opaqueError);\n\n return uint(err);\n }\n}\n" + }, + "contracts/Utils/Exponential.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CarefulMath } from \"./CarefulMath.sol\";\nimport { ExponentialNoError } from \"./ExponentialNoError.sol\";\n\n/**\n * @title Exponential module for storing fixed-precision decimals\n * @author Venus\n * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places.\n * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is:\n * `Exp({mantissa: 5100000000000000000})`.\n */\ncontract Exponential is CarefulMath, ExponentialNoError {\n /**\n * @dev Creates an exponential from numerator and denominator values.\n * Note: Returns an error if (`num` * 10e18) > MAX_INT,\n * or if `denom` is zero.\n */\n function getExp(uint num, uint denom) internal pure returns (MathError, Exp memory) {\n (MathError err0, uint scaledNumerator) = mulUInt(num, expScale);\n if (err0 != MathError.NO_ERROR) {\n return (err0, Exp({ mantissa: 0 }));\n }\n\n (MathError err1, uint rational) = divUInt(scaledNumerator, denom);\n if (err1 != MathError.NO_ERROR) {\n return (err1, Exp({ mantissa: 0 }));\n }\n\n return (MathError.NO_ERROR, Exp({ mantissa: rational }));\n }\n\n /**\n * @dev Adds two exponentials, returning a new exponential.\n */\n function addExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\n (MathError error, uint result) = addUInt(a.mantissa, b.mantissa);\n\n return (error, Exp({ mantissa: result }));\n }\n\n /**\n * @dev Subtracts two exponentials, returning a new exponential.\n */\n function subExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\n (MathError error, uint result) = subUInt(a.mantissa, b.mantissa);\n\n return (error, Exp({ mantissa: result }));\n }\n\n /**\n * @dev Multiply an Exp by a scalar, returning a new Exp.\n */\n function mulScalar(Exp memory a, uint scalar) internal pure returns (MathError, Exp memory) {\n (MathError err0, uint scaledMantissa) = mulUInt(a.mantissa, scalar);\n if (err0 != MathError.NO_ERROR) {\n return (err0, Exp({ mantissa: 0 }));\n }\n\n return (MathError.NO_ERROR, Exp({ mantissa: scaledMantissa }));\n }\n\n /**\n * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer.\n */\n function mulScalarTruncate(Exp memory a, uint scalar) internal pure returns (MathError, uint) {\n (MathError err, Exp memory product) = mulScalar(a, scalar);\n if (err != MathError.NO_ERROR) {\n return (err, 0);\n }\n\n return (MathError.NO_ERROR, truncate(product));\n }\n\n /**\n * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer.\n */\n function mulScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) internal pure returns (MathError, uint) {\n (MathError err, Exp memory product) = mulScalar(a, scalar);\n if (err != MathError.NO_ERROR) {\n return (err, 0);\n }\n\n return addUInt(truncate(product), addend);\n }\n\n /**\n * @dev Divide an Exp by a scalar, returning a new Exp.\n */\n function divScalar(Exp memory a, uint scalar) internal pure returns (MathError, Exp memory) {\n (MathError err0, uint descaledMantissa) = divUInt(a.mantissa, scalar);\n if (err0 != MathError.NO_ERROR) {\n return (err0, Exp({ mantissa: 0 }));\n }\n\n return (MathError.NO_ERROR, Exp({ mantissa: descaledMantissa }));\n }\n\n /**\n * @dev Divide a scalar by an Exp, returning a new Exp.\n */\n function divScalarByExp(uint scalar, Exp memory divisor) internal pure returns (MathError, Exp memory) {\n /*\n We are doing this as:\n getExp(mulUInt(expScale, scalar), divisor.mantissa)\n\n How it works:\n Exp = a / b;\n Scalar = s;\n `s / (a / b)` = `b * s / a` and since for an Exp `a = mantissa, b = expScale`\n */\n (MathError err0, uint numerator) = mulUInt(expScale, scalar);\n if (err0 != MathError.NO_ERROR) {\n return (err0, Exp({ mantissa: 0 }));\n }\n return getExp(numerator, divisor.mantissa);\n }\n\n /**\n * @dev Divide a scalar by an Exp, then truncate to return an unsigned integer.\n */\n function divScalarByExpTruncate(uint scalar, Exp memory divisor) internal pure returns (MathError, uint) {\n (MathError err, Exp memory fraction) = divScalarByExp(scalar, divisor);\n if (err != MathError.NO_ERROR) {\n return (err, 0);\n }\n\n return (MathError.NO_ERROR, truncate(fraction));\n }\n\n /**\n * @dev Multiplies two exponentials, returning a new exponential.\n */\n function mulExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\n (MathError err0, uint doubleScaledProduct) = mulUInt(a.mantissa, b.mantissa);\n if (err0 != MathError.NO_ERROR) {\n return (err0, Exp({ mantissa: 0 }));\n }\n\n // We add half the scale before dividing so that we get rounding instead of truncation.\n // See \"Listing 6\" and text above it at https://accu.org/index.php/journals/1717\n // Without this change, a result like 6.6...e-19 will be truncated to 0 instead of being rounded to 1e-18.\n (MathError err1, uint doubleScaledProductWithHalfScale) = addUInt(halfExpScale, doubleScaledProduct);\n if (err1 != MathError.NO_ERROR) {\n return (err1, Exp({ mantissa: 0 }));\n }\n\n (MathError err2, uint product) = divUInt(doubleScaledProductWithHalfScale, expScale);\n // The only error `div` can return is MathError.DIVISION_BY_ZERO but we control `expScale` and it is not zero.\n assert(err2 == MathError.NO_ERROR);\n\n return (MathError.NO_ERROR, Exp({ mantissa: product }));\n }\n\n /**\n * @dev Multiplies two exponentials given their mantissas, returning a new exponential.\n */\n function mulExp(uint a, uint b) internal pure returns (MathError, Exp memory) {\n return mulExp(Exp({ mantissa: a }), Exp({ mantissa: b }));\n }\n\n /**\n * @dev Multiplies three exponentials, returning a new exponential.\n */\n function mulExp3(Exp memory a, Exp memory b, Exp memory c) internal pure returns (MathError, Exp memory) {\n (MathError err, Exp memory ab) = mulExp(a, b);\n if (err != MathError.NO_ERROR) {\n return (err, ab);\n }\n return mulExp(ab, c);\n }\n\n /**\n * @dev Divides two exponentials, returning a new exponential.\n * (a/scale) / (b/scale) = (a/scale) * (scale/b) = a/b,\n * which we can scale as an Exp by calling getExp(a.mantissa, b.mantissa)\n */\n function divExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\n return getExp(a.mantissa, b.mantissa);\n }\n}\n" + }, + "contracts/Utils/ExponentialNoError.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n/**\n * @title Exponential module for storing fixed-precision decimals\n * @author Compound\n * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places.\n * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is:\n * `Exp({mantissa: 5100000000000000000})`.\n */\ncontract ExponentialNoError {\n uint internal constant expScale = 1e18;\n uint internal constant doubleScale = 1e36;\n uint internal constant halfExpScale = expScale / 2;\n uint internal constant mantissaOne = expScale;\n\n struct Exp {\n uint mantissa;\n }\n\n struct Double {\n uint mantissa;\n }\n\n /**\n * @dev Truncates the given exp to a whole number value.\n * For example, truncate(Exp{mantissa: 15 * expScale}) = 15\n */\n function truncate(Exp memory exp) internal pure returns (uint) {\n // Note: We are not using careful math here as we're performing a division that cannot fail\n return exp.mantissa / expScale;\n }\n\n /**\n * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer.\n */\n function mul_ScalarTruncate(Exp memory a, uint scalar) internal pure returns (uint) {\n Exp memory product = mul_(a, scalar);\n return truncate(product);\n }\n\n /**\n * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer.\n */\n function mul_ScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) internal pure returns (uint) {\n Exp memory product = mul_(a, scalar);\n return add_(truncate(product), addend);\n }\n\n /**\n * @dev Checks if first Exp is less than second Exp.\n */\n function lessThanExp(Exp memory left, Exp memory right) internal pure returns (bool) {\n return left.mantissa < right.mantissa;\n }\n\n /**\n * @dev Checks if left Exp <= right Exp.\n */\n function lessThanOrEqualExp(Exp memory left, Exp memory right) internal pure returns (bool) {\n return left.mantissa <= right.mantissa;\n }\n\n /**\n * @dev Checks if left Exp > right Exp.\n */\n function greaterThanExp(Exp memory left, Exp memory right) internal pure returns (bool) {\n return left.mantissa > right.mantissa;\n }\n\n /**\n * @dev returns true if Exp is exactly zero\n */\n function isZeroExp(Exp memory value) internal pure returns (bool) {\n return value.mantissa == 0;\n }\n\n function safe224(uint n, string memory errorMessage) internal pure returns (uint224) {\n require(n < 2 ** 224, errorMessage);\n return uint224(n);\n }\n\n function safe32(uint n, string memory errorMessage) internal pure returns (uint32) {\n require(n < 2 ** 32, errorMessage);\n return uint32(n);\n }\n\n function add_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\n return Exp({ mantissa: add_(a.mantissa, b.mantissa) });\n }\n\n function add_(Double memory a, Double memory b) internal pure returns (Double memory) {\n return Double({ mantissa: add_(a.mantissa, b.mantissa) });\n }\n\n function add_(uint a, uint b) internal pure returns (uint) {\n return add_(a, b, \"addition overflow\");\n }\n\n function add_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\n uint c = a + b;\n require(c >= a, errorMessage);\n return c;\n }\n\n function sub_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\n return Exp({ mantissa: sub_(a.mantissa, b.mantissa) });\n }\n\n function sub_(Double memory a, Double memory b) internal pure returns (Double memory) {\n return Double({ mantissa: sub_(a.mantissa, b.mantissa) });\n }\n\n function sub_(uint a, uint b) internal pure returns (uint) {\n return sub_(a, b, \"subtraction underflow\");\n }\n\n function sub_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\n require(b <= a, errorMessage);\n return a - b;\n }\n\n function mul_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\n return Exp({ mantissa: mul_(a.mantissa, b.mantissa) / expScale });\n }\n\n function mul_(Exp memory a, uint b) internal pure returns (Exp memory) {\n return Exp({ mantissa: mul_(a.mantissa, b) });\n }\n\n function mul_(uint a, Exp memory b) internal pure returns (uint) {\n return mul_(a, b.mantissa) / expScale;\n }\n\n function mul_(Double memory a, Double memory b) internal pure returns (Double memory) {\n return Double({ mantissa: mul_(a.mantissa, b.mantissa) / doubleScale });\n }\n\n function mul_(Double memory a, uint b) internal pure returns (Double memory) {\n return Double({ mantissa: mul_(a.mantissa, b) });\n }\n\n function mul_(uint a, Double memory b) internal pure returns (uint) {\n return mul_(a, b.mantissa) / doubleScale;\n }\n\n function mul_(uint a, uint b) internal pure returns (uint) {\n return mul_(a, b, \"multiplication overflow\");\n }\n\n function mul_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\n if (a == 0 || b == 0) {\n return 0;\n }\n uint c = a * b;\n require(c / a == b, errorMessage);\n return c;\n }\n\n function div_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\n return Exp({ mantissa: div_(mul_(a.mantissa, expScale), b.mantissa) });\n }\n\n function div_(Exp memory a, uint b) internal pure returns (Exp memory) {\n return Exp({ mantissa: div_(a.mantissa, b) });\n }\n\n function div_(uint a, Exp memory b) internal pure returns (uint) {\n return div_(mul_(a, expScale), b.mantissa);\n }\n\n function div_(Double memory a, Double memory b) internal pure returns (Double memory) {\n return Double({ mantissa: div_(mul_(a.mantissa, doubleScale), b.mantissa) });\n }\n\n function div_(Double memory a, uint b) internal pure returns (Double memory) {\n return Double({ mantissa: div_(a.mantissa, b) });\n }\n\n function div_(uint a, Double memory b) internal pure returns (uint) {\n return div_(mul_(a, doubleScale), b.mantissa);\n }\n\n function div_(uint a, uint b) internal pure returns (uint) {\n return div_(a, b, \"divide by zero\");\n }\n\n function div_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\n require(b > 0, errorMessage);\n return a / b;\n }\n\n function fraction(uint a, uint b) internal pure returns (Double memory) {\n return Double({ mantissa: div_(mul_(a, doubleScale), b) });\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "cancun", + "outputSelection": { + "*": { + "*": [ + "storageLayout", + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "evm.gasEstimates" + ], + "": ["ast"] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} From 2812b9d689754496441c843629f568aef383a4ff Mon Sep 17 00:00:00 2001 From: Debugger022 <104391977+Debugger022@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:09:03 +0000 Subject: [PATCH 35/78] feat: updating deployment files --- deployments/bscmainnet.json | 1763 +++++++++++++++++++++++++ deployments/bscmainnet_addresses.json | 3 + 2 files changed, 1766 insertions(+) diff --git a/deployments/bscmainnet.json b/deployments/bscmainnet.json index f8c14bf18..b3b0621a0 100644 --- a/deployments/bscmainnet.json +++ b/deployments/bscmainnet.json @@ -2122,6 +2122,1769 @@ } ] }, + "BStockLiquidator": { + "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "changeAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "initiator", + "type": "address" + } + ], + "name": "BadInitiator", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nowTs", + "type": "uint256" + } + ], + "name": "DeadlineExpired", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "got", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minOut", + "type": "uint256" + } + ], + "name": "InsufficientOut", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidIntermediate", + "type": "error" + }, + { + "inputs": [], + "name": "NativeTransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "NotOperator", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyComptroller", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errCode", + "type": "uint256" + } + ], + "name": "RedeemFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "RouterNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "SwapFailed", + "type": "error" + }, + { + "inputs": [], + "name": "WrongFlashAsset", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroMinOut", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vBStock", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vDebt", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "seizedBStock", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "debtOut", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "flash", + "type": "bool" + } + ], + "name": "Liquidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "OperatorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "RouterSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Swept", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SweptNative", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "comptroller", + "outputs": [ + { + "internalType": "contract IComptroller", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "premiums", + "type": "uint256[]" + }, + { + "internalType": "address", + "name": "initiator", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "bytes", + "name": "param", + "type": "bytes" + } + ], + "name": "executeOperation", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "uint256[]", + "name": "repayAmounts", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vDebt", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vBStock", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "minOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router2", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata2", + "type": "bytes" + }, + { + "internalType": "address", + "name": "intermediateToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct IBStockLiquidator.LiquidationParams", + "name": "params", + "type": "tuple" + } + ], + "name": "flashLiquidate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "initialOwner", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isRouter", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vDebt", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vBStock", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "minOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router2", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata2", + "type": "bytes" + }, + { + "internalType": "address", + "name": "intermediateToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct IBStockLiquidator.LiquidationParams", + "name": "params", + "type": "tuple" + } + ], + "name": "liquidate", + "outputs": [ + { + "internalType": "uint256", + "name": "debtOut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "setOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "setRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "sweep", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "sweepNative", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vBNB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vWBNB", + "outputs": [ + { + "internalType": "contract IVToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "wbnb", + "outputs": [ + { + "internalType": "contract IWBNB", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + } + ] + }, + "BStockLiquidator_Implementation": { + "address": "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", + "abi": [ + { + "inputs": [ + { + "internalType": "contract IComptroller", + "name": "comptroller_", + "type": "address" + }, + { + "internalType": "address", + "name": "vBNB_", + "type": "address" + }, + { + "internalType": "contract IVToken", + "name": "vWBNB_", + "type": "address" + }, + { + "internalType": "contract IWBNB", + "name": "wbnb_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "initiator", + "type": "address" + } + ], + "name": "BadInitiator", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nowTs", + "type": "uint256" + } + ], + "name": "DeadlineExpired", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "got", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minOut", + "type": "uint256" + } + ], + "name": "InsufficientOut", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidIntermediate", + "type": "error" + }, + { + "inputs": [], + "name": "NativeTransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "NotOperator", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyComptroller", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errCode", + "type": "uint256" + } + ], + "name": "RedeemFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "RouterNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "SwapFailed", + "type": "error" + }, + { + "inputs": [], + "name": "WrongFlashAsset", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroMinOut", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vBStock", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vDebt", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "seizedBStock", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "debtOut", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "flash", + "type": "bool" + } + ], + "name": "Liquidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "OperatorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "RouterSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Swept", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SweptNative", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "comptroller", + "outputs": [ + { + "internalType": "contract IComptroller", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "premiums", + "type": "uint256[]" + }, + { + "internalType": "address", + "name": "initiator", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "bytes", + "name": "param", + "type": "bytes" + } + ], + "name": "executeOperation", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "uint256[]", + "name": "repayAmounts", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vDebt", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vBStock", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "minOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router2", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata2", + "type": "bytes" + }, + { + "internalType": "address", + "name": "intermediateToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct IBStockLiquidator.LiquidationParams", + "name": "params", + "type": "tuple" + } + ], + "name": "flashLiquidate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "initialOwner", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isRouter", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vDebt", + "type": "address" + }, + { + "internalType": "contract IVBep20", + "name": "vBStock", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "minOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router2", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCalldata2", + "type": "bytes" + }, + { + "internalType": "address", + "name": "intermediateToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct IBStockLiquidator.LiquidationParams", + "name": "params", + "type": "tuple" + } + ], + "name": "liquidate", + "outputs": [ + { + "internalType": "uint256", + "name": "debtOut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "setOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "setRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "sweep", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "sweepNative", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vBNB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vWBNB", + "outputs": [ + { + "internalType": "contract IVToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "wbnb", + "outputs": [ + { + "internalType": "contract IWBNB", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] + }, + "BStockLiquidator_Proxy": { + "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "changeAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] + }, "BTCB": { "address": "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c", "abi": [ diff --git a/deployments/bscmainnet_addresses.json b/deployments/bscmainnet_addresses.json index e7b98765b..9d686cdcc 100644 --- a/deployments/bscmainnet_addresses.json +++ b/deployments/bscmainnet_addresses.json @@ -6,6 +6,9 @@ "ADA": "0x3EE2200Efb3400fAbB9AacF31297cBdD1d435D47", "BCH": "0x8fF795a6F4D97E7887C79beA79aba5cc76444aDf", "BETH": "0x250632378E573c6Be1AC2f97Fcdf00515d0Aa91B", + "BStockLiquidator": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "BStockLiquidator_Implementation": "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", + "BStockLiquidator_Proxy": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", "BTCB": "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c", "BUSD": "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", "CAKE": "0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82", From 2115a9bd750b2d2d181c75b8d22b275d752627d2 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 3 Jul 2026 17:26:01 +0530 Subject: [PATCH 36/78] test(bstock): run fork suite against deployed bscmainnet BStockLiquidator --- tests/hardhat/Fork/BStockLiquidatorFork.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts index e6df1cee1..df35c2cd3 100644 --- a/tests/hardhat/Fork/BStockLiquidatorFork.ts +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -47,7 +47,6 @@ import { buildSingleHopMock, buildTwoHopMockThenPcs, deployFundedMockNative, - deployLiq, findBalanceSlot, listBStockMarket, makeUnderwaterBorrower, @@ -57,7 +56,10 @@ import { } from "./helpers/bstock"; import { FORK_MAINNET, forking, initMainnetUser } from "./utils"; -const FORK_BLOCK = Number(process.env.FORK_BSTOCK_BLOCK || "107565173"); +const FORK_BLOCK = Number(process.env.FORK_BSTOCK_BLOCK || "107820000"); + +// Live BStockLiquidator proxy on bscmainnet (deployments/bscmainnet/BStockLiquidator.json, deployed at block 107817335). +const DEPLOYED_LIQ = "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1"; const VUSDT = "0xfD5840Cd36d94D7229439859C0112a4185BC0255"; const VCAKE = "0x86aC3974e2BD0d60825230fa6F355fF11409df5c"; @@ -110,7 +112,12 @@ const test = () => { ({ mock, usdtSlot } = await deployFundedMockNative(owner, P_HEALTHY)); slotCache[TOK.USDT] = usdtSlot; - liq = await deployLiq(owner); + // Attach to the deployed bscmainnet proxy and take ownership on the fork (Ownable2Step) + // so the suite exercises the real on-chain instance instead of a fresh deployment. + liq = await ethers.getContractAt("BStockLiquidator", DEPLOYED_LIQ); + const liqOwner = await initMainnetUser(await liq.owner(), parseEther("10")); + await liq.connect(liqOwner).transferOwnership(owner.address); + await liq.connect(owner).acceptOwnership(); await liq.connect(owner).setRouter(mock.address, true); // hop-1 (Native RFQ mock) await liq.connect(owner).setRouter(A.PCS_ROUTER, true); // hop-2 (real PancakeSwap) From 0f94350f524a5a07096a447a72c26cd39ea9f57a Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 10 Jul 2026 16:34:36 +0530 Subject: [PATCH 37/78] feat(bstock): support a separate router spender for split-settlement swaps Some aggregators (e.g. Liquid Mesh) pull the input token through a settlement contract distinct from the call target. Add a routerSpender mapping so _swap approves the puller, defaulting to the router itself so Native is unchanged. Gap shrinks 50->49; storage-safe. --- contracts/BStock/BStockLiquidator.sol | 53 ++++-- contracts/BStock/IBStockLiquidator.sol | 14 +- contracts/test/BStockLiquidationMocks.sol | 44 +++++ tests/hardhat/BStockLiquidatorLiquidMesh.ts | 183 ++++++++++++++++++++ 4 files changed, 277 insertions(+), 17 deletions(-) create mode 100644 tests/hardhat/BStockLiquidatorLiquidMesh.ts diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index 0e67b123b..354ec6d18 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -24,12 +24,14 @@ import { IBStockLiquidator } from "./IBStockLiquidator.sol"; * * In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken, * redeems it to the raw bStock, and sells that bStock to the debt asset in one or two hops. Hop 1 - * always sells bStock through the Native RFQ router using a pre-fetched, MM-signed firm-quote - * `txRequest`. For USDT debt that single hop lands the debt asset directly. For non-USDT debt an - * OPTIONAL hop 2 (Native quotes bStock->USDT only) converts the USDT to the debt asset through a - * second allowlisted router (an AMM/aggregator). Because seize and sell happen in the same tx there - * is no price-drift window, and the realized debt-asset amount must clear `minOut` or the whole call - * reverts — the protocol never ends up holding the RFQ-only asset or the intermediate. + * sells bStock (to USDT) through an allowlisted RFQ router using a pre-fetched, off-chain-signed + * `swapCalldata` — Native firm-quote or Liquid Mesh (see `routerSpender`) or any future allowlisted + * source; the contract is router-agnostic and just forwards the opaque blob. For USDT debt that single + * hop lands the debt asset directly. For non-USDT debt an OPTIONAL hop 2 (RFQ sources quote bStock->USDT + * only) converts the USDT to the debt asset through a second allowlisted router (an AMM/aggregator). + * Because seize and sell happen in the same tx there is no price-drift window, and the realized + * debt-asset amount must clear `minOut` or the whole call reverts — the protocol never ends up holding + * the RFQ-only asset or the intermediate. * * Two funding modes share the same core (`_liquidate`): * - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance. @@ -55,9 +57,9 @@ import { IBStockLiquidator } from "./IBStockLiquidator.sol"; * - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator). * - BOTH swap targets (`router` and, when set, `router2`) must be allowlisted (`isRouter`) — defends * the low-level `router.call(swapCalldata)` on each hop. - * - the approval to each router is the exact amount being sold on that hop, reset to 0 afterwards - * (bStock on hop 1; the measured intermediate balance delta on hop 2, so pre-existing inventory - * is never exposed). + * - the approval for each hop is the exact amount being sold, granted to the router's configured spender + * (the router itself when unset — see `routerSpender`) and reset to 0 afterwards (bStock on hop 1; the + * measured intermediate balance delta on hop 2, so pre-existing inventory is never exposed). * - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started). * - the realized debt-asset amount must clear `minOut` or the tx reverts. * @@ -103,8 +105,15 @@ contract BStockLiquidator is /// @notice Routers allowed as the swap target (defends the low-level call). mapping(address => bool) public isRouter; + /// @notice Optional token-approval target (spender) per router, for aggregators whose settlement + /// contract that pulls the input token differs from the call target (e.g. Liquid Mesh, where + /// the router is the call target but a separate spender pulls the token). When unset + /// (address(0)), the approval defaults to the router itself — the Native behaviour, where the + /// call target IS the puller — so existing routers need no spender entry. + mapping(address => address) public routerSpender; + /// @dev Reserved storage to allow new state variables in future upgrades without layout clashes. - uint256[50] private __gap; + uint256[49] private __gap; modifier onlyOperator() { if (msg.sender != owner() && !isOperator[msg.sender]) revert NotOperator(); @@ -163,6 +172,15 @@ contract BStockLiquidator is emit RouterSet(router, allowed); } + /// @inheritdoc IBStockLiquidator + function setRouterSpender(address router, address spender) external override onlyOwner { + ensureNonzeroAddress(router); + // `spender == address(0)` is allowed and clears the entry, reverting the router to + // approve-the-call-target (Native) behaviour. + routerSpender[router] = spender; + emit RouterSpenderSet(router, spender); + } + /// @inheritdoc IBStockLiquidator function sweep(address token, address to, uint256 amount) external override onlyOwner { ensureNonzeroAddress(token); @@ -301,14 +319,19 @@ contract BStockLiquidator is if (router2 != address(0) && !isRouter[router2]) revert RouterNotAllowed(router2); } - /// @dev One swap hop: approve the exact `amount` to the allowlisted `router`, forward the opaque - /// calldata via a low-level call, then reset the approval to 0. The approval caps what the - /// router can pull; if the calldata sells less, the remainder stays as inventory. + /// @dev One swap hop: approve the exact `amount` to the router's configured spender, forward the + /// opaque calldata via a low-level call to the allowlisted `router`, then reset the approval to + /// 0. The spender defaults to the router itself when unset (Native, where the call target is the + /// puller); aggregators with a separate settlement/pull contract (e.g. Liquid Mesh) set it via + /// `setRouterSpender`. The approval caps what the spender can pull; if the calldata sells less, + /// the remainder stays as inventory. function _swap(IERC20Upgradeable token, address router, bytes memory data, uint256 amount) private { - token.forceApprove(router, amount); + address spender = routerSpender[router]; + if (spender == address(0)) spender = router; + token.forceApprove(spender, amount); (bool ok, ) = router.call(data); if (!ok) revert SwapFailed(); - token.forceApprove(router, 0); // never leave a standing approval + token.forceApprove(spender, 0); // never leave a standing approval } /** diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol index 8397e34a8..c6e44053e 100644 --- a/contracts/BStock/IBStockLiquidator.sol +++ b/contracts/BStock/IBStockLiquidator.sol @@ -23,8 +23,8 @@ interface IBStockLiquidator { IVBep20 vDebt; // borrowed market to repay (e.g. vUSDT) IVBep20 vBStock; // bStock collateral market to seize (e.g. vTSLAB) uint256 repayAmount; // debt underlying to repay (its own decimals) - address router; // hop-1 router = Native firm-quote txRequest.target (must be allowlisted) - bytes swapCalldata; // hop-1 calldata (MM-signed Native order): bStock -> intermediate (or -> debt if single-hop) + 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 @@ -38,6 +38,9 @@ interface IBStockLiquidator { /// @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. @@ -109,6 +112,13 @@ interface IBStockLiquidator { /// @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. + /// @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. diff --git a/contracts/test/BStockLiquidationMocks.sol b/contracts/test/BStockLiquidationMocks.sol index b227f1e47..db1897f93 100644 --- a/contracts/test/BStockLiquidationMocks.sol +++ b/contracts/test/BStockLiquidationMocks.sol @@ -252,6 +252,50 @@ contract MockReentrantRouter { } } +/// @dev Stand-in for an aggregator (e.g. Liquid Mesh) whose settlement pulls the input token through a +/// SEPARATE spender contract, distinct from the call target. On Liquid Mesh you approve the spender +/// `0x8157…` but call the router `0x3d90…`. `MockSpender.pull` does the `transferFrom`, so the input +/// allowance must sit on the SPENDER, not the router — exactly the case `routerSpender` handles. +contract MockSpender { + /// @dev Pull `amountIn` of `tokenIn` from `from` to `to`. `from` must have approved THIS contract. + function pull(address tokenIn, address from, address to, uint256 amountIn) external { + require(ERC20(tokenIn).transferFrom(from, to, amountIn), "spender pull failed"); + } +} + +/// @dev The call target that pairs with `MockSpender`: it pulls the caller's input token VIA the spender +/// (so the caller must approve the spender, NOT this router) and pays the output at `rate`. Mirrors a +/// Liquid Mesh swap: `router.call(swapCalldata)` where settlement pulls via a separate spender. +/// Pre-fund it with `tokenOut`. +contract MockSplitRouter { + address public immutable spender; + uint256 public rate = 1e18; // tokenOut per tokenIn, 18-dec fixed point + + constructor(address spender_) { + spender = spender_; + } + + function setRate(uint256 r) external { + rate = r; + } + + function swap(address tokenIn, uint256 amountIn, address tokenOut, address to) external { + // Pull via the spender — reverts with "spender pull failed" if the caller approved this router + // instead of the spender (the SafeTransferFromFailed analog of Liquid Mesh's build-time sim). + MockSpender(spender).pull(tokenIn, msg.sender, address(this), amountIn); + require(ERC20(tokenOut).transfer(to, (amountIn * rate) / 1e18), "out transfer failed"); + } + + /// @dev Pulls the caller's ENTIRE tokenIn balance VIA THE SPENDER (what the liquidator holds after + /// redeem) and pays tokenOut at `rate`. Mirrors MockNativeRouter.swapAll but through the split + /// spender, so tests need not match the exact seized amount. + function swapAll(address tokenIn, address tokenOut, address to) external { + uint256 amountIn = ERC20(tokenIn).balanceOf(msg.sender); + MockSpender(spender).pull(tokenIn, msg.sender, address(this), amountIn); + require(ERC20(tokenOut).transfer(to, (amountIn * rate) / 1e18), "out transfer failed"); + } +} + /// @dev Stand-in for the pool-wide Venus Liquidator (`liquidatorContract`). Pulls the repay from the /// caller and credits the seized collateral (repay * incentive) to the caller, minus a treasury cut. contract MockVenusLiquidator { diff --git a/tests/hardhat/BStockLiquidatorLiquidMesh.ts b/tests/hardhat/BStockLiquidatorLiquidMesh.ts new file mode 100644 index 000000000..e4d542f21 --- /dev/null +++ b/tests/hardhat/BStockLiquidatorLiquidMesh.ts @@ -0,0 +1,183 @@ +import { SnapshotRestorer, takeSnapshot } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { BigNumber, Contract } from "ethers"; +import { ethers, upgrades } from "hardhat"; + +// Proves the Liquid-Mesh integration semantics for BStockLiquidator. +// +// Liquid Mesh differs from Native in ONE way that matters here: it pulls the input token through a +// SEPARATE spender contract (approve `0x8157…`) while you call the router (`0x3d90…`). This suite uses +// MockSplitRouter (call target) + MockSpender (does the transferFrom) to reproduce that split and prove: +// +// 1. WITHOUT the spender mapping the swap reverts — approving the router (old behaviour) leaves the +// spender with no allowance, so its transferFrom fails. This is why the contract change is needed. +// 2. WITH `setRouterSpender`, the seize -> redeem -> sell runs ATOMICALLY in one tx: the contract never +// pre-holds the bStock; it acquires it mid-tx, approves the spender, calls the router, and clears +// minOut. No standing allowance is left on the spender. +// 3. The Liquid-Mesh build-time simulation: simulating the same swap from an address that does NOT yet +// hold + approve the input reverts (the on-chain analog of Liquid Mesh's `41320 SafeTransferFromFailed` +// pre-trade simulation). It also proves the returned calldata is balance-AGNOSTIC to execute — the +// identical call succeeds once the balance is present. So the calldata itself is fine for our atomic +// seize-then-sell (it holds the token mid-tx, test 2); only Liquid Mesh's OFF-CHAIN build-time +// simulation would block fetching it pre-seize. +// +// RESOLVED off-chain (2026-07): `POST /swap` accepts `disableSimulate:true`, which skips that build-time +// `transferFrom` simulation and returns executable calldata for a zero-balance caller. Verified live. So +// the atomic path works end-to-end: build the blob pre-seize with `disableSimulate:true`, and the contract +// (this suite) executes it mid-tx. On-chain safety is unchanged — the order carries an `expiryTimestamp` +// deadline and the contract still enforces `minOut`. See scripts/bstock/lib/liquidmesh.ts. + +const U = (n: string) => ethers.utils.parseUnits(n, 18); +const ZERO = ethers.constants.AddressZero; +const INCENTIVE = U("1.1"); +const VBNB = ethers.utils.getAddress("0x0000000000000000000000000000000000000b0b"); + +describe("BStockLiquidator + Liquid Mesh (separate spender)", () => { + let owner: any, borrower: any, stranger: any; + let usdt: Contract, bStock: Contract; + let comptroller: Contract, vBStock: Contract, vDebt: Contract, venusLiq: Contract; + let wbnb: Contract, vWBNB: Contract; + let spender: Contract, lmRouter: Contract; + let liq: Contract; + + const REPAY = U("5000"); + const SEIZED = REPAY.mul(INCENTIVE).div(U("1")); // 5500 bStock at 1:1 redeem + const OUT = SEIZED; // router rate 1:1 -> 5500 USDT + const MIN_OUT = U("5400"); + + async function deployLiquidator(comptrollerAddr: string) { + const Factory = await ethers.getContractFactory("BStockLiquidator"); + return upgrades.deployProxy(Factory, [owner.address], { + constructorArgs: [comptrollerAddr, VBNB, vWBNB.address, wbnb.address], + unsafeAllow: ["constructor", "state-variable-immutable"], + }); + } + + async function deploy() { + [owner, , borrower, stranger] = await ethers.getSigners(); + + const ERC20 = await ethers.getContractFactory("MockMintableERC20"); + usdt = await ERC20.deploy("Tether", "USDT", 18); + bStock = await ERC20.deploy("Tesla bStock", "TSLAB", 18); + + comptroller = await (await ethers.getContractFactory("MockComptrollerLite")).deploy(); + vBStock = await ( + await ethers.getContractFactory("MockVTokenCollateral") + ).deploy(bStock.address, comptroller.address); + vDebt = await (await ethers.getContractFactory("MockVTokenDebt")).deploy(usdt.address, comptroller.address); + + // Liquid-Mesh-style split: the router is the CALL TARGET, the spender does the transferFrom. + spender = await (await ethers.getContractFactory("MockSpender")).deploy(); + lmRouter = await (await ethers.getContractFactory("MockSplitRouter")).deploy(spender.address); + + wbnb = await (await ethers.getContractFactory("WBNB")).deploy(); + vWBNB = await (await ethers.getContractFactory("MockVTokenDebt")).deploy(wbnb.address, comptroller.address); + + venusLiq = await (await ethers.getContractFactory("MockVenusLiquidator")).deploy(); + await comptroller.setLiquidatorContract(venusLiq.address); + await venusLiq.setVBnb(VBNB); + + liq = await deployLiquidator(comptroller.address); + await liq.connect(owner).setRouter(lmRouter.address, true); + + // Liquidity: collateral market holds bStock to pay redeem; the LM router holds USDT to pay the swap. + await bStock.mint(vBStock.address, SEIZED); + await usdt.mint(lmRouter.address, OUT); + + await comptroller.setShortfall(U("800")); + // Fund the liquidator's own inventory to cover the repay (inventory mode). + await usdt.mint(liq.address, REPAY); + } + + // swap(tokenIn, amountIn, tokenOut, to) against the LM-style router. + function swapCalldata(amountIn: BigNumber, to: string) { + return lmRouter.interface.encodeFunctionData("swap", [bStock.address, amountIn, usdt.address, to]); + } + + function params(over: Partial = {}) { + return { + borrower: borrower.address, + vDebt: vDebt.address, + vBStock: vBStock.address, + repayAmount: REPAY, + router: lmRouter.address, + swapCalldata: swapCalldata(SEIZED, liq.address), + minOut: MIN_OUT, + router2: ZERO, + swapCalldata2: "0x", + intermediateToken: ZERO, + deadline: ethers.constants.MaxUint256, + ...over, + }; + } + + let pristine: SnapshotRestorer; + before(async () => { + pristine = await takeSnapshot(); + }); + after(async () => { + await pristine.restore(); + }); + beforeEach(deploy); + + it("REVERTS without a configured spender — approving the call target leaves the puller unapproved", async () => { + // No setRouterSpender: `_swap` approves the router itself, but MockSpender.pull does the transferFrom + // and has no allowance -> "spender pull failed" -> the low-level call fails -> SwapFailed. + expect(await liq.routerSpender(lmRouter.address)).to.equal(ZERO); + await expect(liq.connect(owner).liquidate(params())).to.be.revertedWithCustomError(liq, "SwapFailed"); + }); + + it("SUCCEEDS atomically once the spender is set — seizes, redeems, and sells a token it never pre-held", async () => { + await expect(liq.connect(owner).setRouterSpender(lmRouter.address, spender.address)) + .to.emit(liq, "RouterSpenderSet") + .withArgs(lmRouter.address, spender.address); + + // The contract holds ZERO bStock before the call — it is acquired mid-tx. + expect(await bStock.balanceOf(liq.address)).to.equal(0); + + await expect(liq.connect(owner).liquidate(params())).to.emit(liq, "Liquidated"); + + // Proceeds landed (USDT out >= minOut), and no standing allowance is left on the spender. + expect(await usdt.balanceOf(liq.address)).to.be.gte(MIN_OUT); + expect(await bStock.allowance(liq.address, spender.address)).to.equal(0); + expect(await bStock.allowance(liq.address, lmRouter.address)).to.equal(0); + // The router pulled the input via the spender. + expect(await bStock.balanceOf(lmRouter.address)).to.equal(SEIZED); + }); + + it("build-time simulation of the swap reverts before the token is held (the Liquid Mesh `SafeTransferFromFailed` gate)", async () => { + // Liquid Mesh SIMULATES the swap when building calldata: by default it does transferFrom(userAddress, …) + // against CURRENT state and refuses to return calldata unless the taker already holds + approved the input. + // Reproduce that here: `stranger` (like the liquidator before it seizes) holds no bStock. Even with the + // spender configured, simulating the router swap from that state reverts. In production this is bypassed + // off-chain with `disableSimulate:true` on `POST /swap`; the block below proves the returned calldata is + // balance-agnostic to EXECUTE, so once the token is in hand (mid-tx, test above) the same call succeeds. + await liq.connect(owner).setRouterSpender(lmRouter.address, spender.address); + + expect(await bStock.balanceOf(stranger.address)).to.equal(0); + await bStock.connect(stranger).approve(spender.address, SEIZED); // approval alone is not enough + // Reverts at the spender's transferFrom for lack of balance — the on-chain analog of Liquid Mesh's + // build-time `SafeTransferFromFailed`. + await expect( + lmRouter.connect(stranger).callStatic.swap(bStock.address, SEIZED, usdt.address, stranger.address), + ).to.be.revertedWith("ERC20: transfer amount exceeds balance"); + + // Give `stranger` the balance (what the liquidator only has MID-TX) and the identical call now succeeds + // — proving the returned calldata is balance-agnostic to EXECUTE; only the off-chain build gate blocks. + await bStock.mint(stranger.address, SEIZED); + await expect(lmRouter.connect(stranger).callStatic.swap(bStock.address, SEIZED, usdt.address, stranger.address)).to + .not.be.reverted; + }); + + it("clears the spender entry when reset to address(0) — reverting to approve-the-router behaviour", async () => { + await liq.connect(owner).setRouterSpender(lmRouter.address, spender.address); + await liq.connect(owner).setRouterSpender(lmRouter.address, ZERO); + expect(await liq.routerSpender(lmRouter.address)).to.equal(ZERO); + // Back to old behaviour -> the split router reverts again. + await expect(liq.connect(owner).liquidate(params())).to.be.revertedWithCustomError(liq, "SwapFailed"); + }); + + it("setRouterSpender is owner-only", async () => { + await expect(liq.connect(stranger).setRouterSpender(lmRouter.address, spender.address)).to.be.reverted; + }); +}); From f5e3d091713de84d16833ff0e9fa2094eb1b90e3 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 10 Jul 2026 16:35:37 +0530 Subject: [PATCH 38/78] feat(bstock): add Liquid Mesh as a hop-1 liquidation source Price Native and Liquid Mesh per liquidation and settle through the higher-out source. The LM client uses disableSimulate to build swap calldata for the not-yet-holding contract, keeping seize->sell atomic; sources sit behind a registry so future ones need no contract change --- scripts/bstock/README.md | 72 ++++++-- scripts/bstock/atomic-liquidate.ts | 141 +++++++++++---- scripts/bstock/lib/liquidmesh.ts | 193 +++++++++++++++++++++ scripts/bstock/lib/sources.ts | 149 ++++++++++++++++ scripts/bstock/verify-lm-fork.ts | 179 +++++++++++++++++++ tests/hardhat/BStockAtomicLiquidate.ts | 152 +++++++++++++++- tests/hardhat/Fork/BStockLiquidatorFork.ts | 62 ++++++- 7 files changed, 891 insertions(+), 57 deletions(-) create mode 100644 scripts/bstock/lib/liquidmesh.ts create mode 100644 scripts/bstock/lib/sources.ts create mode 100644 scripts/bstock/verify-lm-fork.ts diff --git a/scripts/bstock/README.md b/scripts/bstock/README.md index 314523398..d4da3d6fd 100644 --- a/scripts/bstock/README.md +++ b/scripts/bstock/README.md @@ -5,7 +5,7 @@ one shared goal: repay a borrower's debt, seize their bStock, and offload it. | Script | Path | What it does | Chain writes? | | ----------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| **Atomic** | `atomic-liquidate.ts` | Primary path. Drives the on-chain `BStockLiquidator` — repay, seize, redeem, and sell bStock in ONE tx via a Native RFQ quote (+ optional AMM hop). | Yes | +| **Atomic** | `atomic-liquidate.ts` | Primary path. Drives the on-chain `BStockLiquidator` — repay, seize, redeem, and sell bStock in ONE tx via a Native or Liquid Mesh RFQ quote (+ optional AMM hop). | Yes | | **Safe fallback** | `safe-fallback.ts` | Backstop when Native is unavailable (API down / halt / weekend / thin depth). Emits a Safe{Wallet} batch JSON; signers repay from Safe funds and ship raw bStock to Binance. | No — writes JSON | | **Native smoke** | `native-smoke.ts` | Pre-flight check. Prints the Native orderbook + a live firm-quote (price, spread, TTL). No chain interaction. | No | @@ -15,14 +15,44 @@ Pick **Atomic** first. Fall back to **Safe** only if the Native quote path fails ## Prerequisites -- **Native API key** (`NATIVE_API_KEY`) for anything that fetches a quote. Never commit it. +- **Native API key** (`NATIVE_API_KEY`) for anything that fetches a Native quote. Never commit it. +- **Liquid Mesh credentials** (only if `SOURCE=liquidmesh` or `SOURCE=auto`): `LM_API_KEY` and the + Ed25519 private-key seed `LM_PRIVATE_KEY_SEED` (base64url). Never commit them. - **Deployed `BStockLiquidator`** (see `deploy/019-deploy-bstock-liquidator.ts`). Its owner must have run: - - `setRouter(router, true)` for every router the swap will touch (Native RFQ router, and the AMM - router for non-USDT debt). The scripts **abort** if a router isn't allowlisted. + - `setRouter(router, true)` for every router the swap will touch (the Native RFQ router and/or the + Liquid Mesh router, plus the AMM router for non-USDT debt). The scripts **abort** if a router isn't + allowlisted. + - `setRouterSpender(LM_ROUTER, LM_SPENDER)` **if using Liquid Mesh** — it pulls the input token through a + separate spender contract, distinct from the call target. Without it the swap reverts `SwapFailed`. + (Native needs no spender entry — its call target is the puller.) - `setOperator(caller, true)` for the account that submits `liquidate` / `flashLiquidate`. - **Funding** for `MODE=inventory`: the liquidator must hold ≥ `REPAY_AMOUNT` of the debt asset. `MODE=flash` borrows instead (no pre-funding). +### Hop-1 source registry (Native, Liquid Mesh, …) + +Hop-1 sources live in a registry — `scripts/bstock/lib/sources.ts` — that the script is generic over: it +prices every selected source and takes the higher out. `SOURCE=auto` (default) uses every source whose +creds are present; `SOURCE=native,liquidmesh` (or a single name) restricts to a subset. + +Both current sources quote bStock→USDT. Liquid Mesh re-serves the same `rfq_native` book plus `rfq_neptune` +(and AMM pools for thin names), so at common sizes it matches or marginally beats Native and reaches deeper +on some tails — but neither is uniformly deeper. + +**Adding a source** (no redeploy): implement the small `QuoteSource` interface in `lib/sources.ts` +(`available()` + `getQuote()` returning a price and a lazy `build()`), push it into the `SOURCES` array, +and on-chain run `setRouter(newRouter, true)` (+ `setRouterSpender` if it pulls via a separate contract). +The selection/comparison logic and the contract are unchanged — the contract is already source-agnostic. + +Two Liquid-Mesh mechanics, both handled automatically: + +- **Separate spender** — approve `LM_SPENDER` (`0x8157…`), call `LM_ROUTER` (`0x3d90…`). Handled on-chain + by `routerSpender` (run `setRouterSpender` once, above). +- **Build-time simulation** — `POST /swap` normally simulates `transferFrom` and refuses calldata unless + the caller already holds the input. The liquidator holds zero bStock until mid-tx, so the client passes + `disableSimulate:true` to get executable calldata pre-seize. On-chain safety is unchanged (the order + carries an expiry deadline; the contract enforces `minOut`). + --- ## 1. Native smoke — verify the quote path @@ -43,8 +73,9 @@ NATIVE_API_KEY=... LIQUIDATOR=0x.. BORROWER=0x.. VBSTOCK=0x.. VDEBT=0x.. REPAY_A npx hardhat run scripts/bstock/atomic-liquidate.ts --network bscmainnet ``` -Flow: precompute the exact seize → fetch a Native firm-quote (bStock→USDT) with `from_address = the -contract` → for non-USDT debt, append an AMM hop (USDT→debt) → call `liquidate`/`flashLiquidate`. +Flow: precompute the exact seize → fetch the hop-1 quote (bStock→USDT) from the best of Native / Liquid +Mesh with the taker = the contract → for non-USDT debt, append an AMM hop (USDT→debt) → +call `liquidate`/`flashLiquidate`. **Always dry-run first** (`DRY_RUN=1`) — it `callStatic`s the settle with no send. @@ -53,19 +84,22 @@ contract` → for non-USDT debt, append an AMM hop (USDT→debt) → call `liqui ### Env -| Var | Req | Default | Notes | -| ---------------- | --- | ----------- | ------------------------------------------------------------------------ | -| `LIQUIDATOR` | ✓ | | Deployed `BStockLiquidator` address | -| `BORROWER` | ✓ | | Account to liquidate (must have shortfall) | -| `VBSTOCK` | ✓ | | bStock collateral market (e.g. vTSLAB) | -| `VDEBT` | ✓ | | Borrowed market to repay (e.g. vUSDT) | -| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | -| `MODE` | | `inventory` | `inventory` (own funds) or `flash` (Venus flash-loan) | -| `DRY_RUN` | | | `1` → callStatic only, sends nothing | -| `SLIPPAGE` | | `0.5` | Native slippage % | -| `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) | -| `AMM_PROVIDER` | | `kyberswap` | Hop-2 route for non-USDT debt: `kyberswap` / `openocean` / `pcsv2` | -| `WBNB_ADDR` | | BSC WBNB | Only for a vBNB debt market (native BNB auto-detected; contract unwraps) | +| Var | Req | Default | Notes | +| --------------------- | --- | ----------- | ------------------------------------------------------------------------ | +| `LIQUIDATOR` | ✓ | | Deployed `BStockLiquidator` address | +| `BORROWER` | ✓ | | Account to liquidate (must have shortfall) | +| `VBSTOCK` | ✓ | | bStock collateral market (e.g. vTSLAB) | +| `VDEBT` | ✓ | | Borrowed market to repay (e.g. vUSDT) | +| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | +| `MODE` | | `inventory` | `inventory` (own funds) or `flash` (Venus flash-loan) | +| `SOURCE` | | `auto` | Hop-1 source: `auto` (price both, take higher) / `native` / `liquidmesh` | +| `LM_API_KEY` | | | Liquid Mesh API key (required for `liquidmesh`/`auto`) | +| `LM_PRIVATE_KEY_SEED` | | | Liquid Mesh Ed25519 seed, base64url (required for `liquidmesh`/`auto`) | +| `DRY_RUN` | | | `1` → callStatic only, sends nothing | +| `SLIPPAGE` | | `0.5` | Native/LM slippage % | +| `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) | +| `AMM_PROVIDER` | | `kyberswap` | Hop-2 route for non-USDT debt: `kyberswap` / `openocean` / `pcsv2` | +| `WBNB_ADDR` | | BSC WBNB | Only for a vBNB debt market (native BNB auto-detected; contract unwraps) | _Fork/local testing:_ `MOCK_NATIVE="router:calldata"` (hop 1), `MOCK_AMM` (hop 2), `MOCK_OUT` (final debt out), `IMPERSONATE=0x..` to run as an operator. diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 7208677c7..411b8c049 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -2,17 +2,25 @@ * bStock ATOMIC liquidation — drives the on-chain `BStockLiquidator` contract. * * One tx: the contract repays the borrow, seizes + redeems the bStock, and sells it to the debt - * asset in one or two hops. Hop 1 is always a pre-fetched Native firm-quote (bStock -> USDT). For a - * USDT debt market that single hop is the debt asset; for a non-USDT debt market a second hop - * (USDT -> debt via an allowlisted AMM/aggregator, see lib/amm.ts) is appended. This script does the - * OFF-CHAIN half (precompute the seize, fetch the quotes with `from_address = the contract`) and then - * calls `liquidate` (inventory mode) or `flashLiquidate` (Venus flash-loan mode). + * asset in one or two hops. Hop 1 is a pre-fetched best-of quote (bStock -> USDT) from the source + * registry (Native RFQ / Liquid Mesh / future sources — see lib/sources.ts). For a USDT debt market that + * single hop is the debt asset; for a non-USDT debt market a second hop (USDT -> debt via an allowlisted + * AMM/aggregator, see lib/amm.ts) is appended. This script does the OFF-CHAIN half (precompute the seize, + * fetch the quotes with the taker = the contract) and then calls `liquidate` (inventory mode) or + * `flashLiquidate` (Venus flash-loan mode). * * 1. Comptroller.liquidateCalculateSeizeTokens(vDebt, vBStock, repay) -> seize vTokens * 2. seizeTokens * vBStock.exchangeRateStored() / 1e18 -> raw bStock (floor) - * 3. Native firm-quote (bStock -> USDT) [+ AMM quote USDT -> debt] -> router(s) + calldata + out + * 3. hop-1 quote (bStock -> USDT) from Native and/or Liquid Mesh, best -> router + calldata + out + * [+ AMM quote USDT -> debt for non-USDT debt] -> hop-2 router + calldata * 4. BStockLiquidator.liquidate / flashLiquidate(params) -> atomic settle * + * Hop-1 sources come from a registry (lib/sources.ts); `SOURCE` selects them (auto = every available + * source, or a comma-separated subset). auto prices all and takes the higher out. Liquid Mesh returns ONE + * router calldata blob (multi-source split baked in) and requires `disableSimulate:true` at build time + * (the contract holds no bStock until mid-tx) plus a one-time `setRouterSpender(LM_ROUTER, LM_SPENDER)` on + * the liquidator (LM pulls via a separate spender). Adding a source = one adapter in lib/sources.ts. + * * IMPORTANT: for a two-hop run the settle tx MUST be submitted through Venus's PRIVATE RPC, never a * public one — hop 2 is a public-mempool AMM swap and `minOut` only bounds loss, not sandwich-induced * reverts. Prefer MODE=flash for two-hop so a revert only burns gas, not locked inventory. @@ -28,7 +36,12 @@ * VDEBT (req) borrowed market to repay (e.g. vUSDT) * REPAY_AMOUNT (req) repay in DEBT underlying, human units * MODE "inventory" (default) | "flash" - * SLIPPAGE Native slippage %, default 0.5 + * SOURCE hop-1 liquidity source(s) from the registry (lib/sources.ts): "auto" (default, price + * every AVAILABLE source and take the higher out) or a comma-separated subset by name + * (e.g. "native", "liquidmesh", "native,liquidmesh"). Liquid Mesh needs LM_API_KEY + + * LM_PRIVATE_KEY_SEED, and the liquidator must have `setRouterSpender(LM_ROUTER, + * LM_SPENDER)` set (separate puller). New sources = one adapter in lib/sources.ts. + * SLIPPAGE Native/LM slippage %, default 0.5 * MIN_OUT_BUFFER extra haircut on minOut beyond slippage, default 0.5 (%) * SEIZE_BUFFER haircut on the QUOTED seize so a small oracle uptick can't make the router pull more * bStock than was seized (which reverts). Default 0.1 (%); unsold remainder is sweepable. @@ -46,7 +59,8 @@ import { BigNumber, Contract, Signer } from "ethers"; import { ethers } from "hardhat"; import { BSC_WBNB, getAmmSwap } from "./lib/amm"; -import { BSC_USDT, getFirmQuote, quoteDeadline } from "./lib/native"; +import { BSC_USDT } from "./lib/native"; +import { QuoteArgs, selectedSources } from "./lib/sources"; const PARAMS_TUPLE = "(address borrower,address vDebt,address vBStock,uint256 repayAmount,address router,bytes swapCalldata,uint256 minOut,address router2,bytes swapCalldata2,address intermediateToken,uint256 deadline)"; @@ -80,18 +94,67 @@ function env(name: string, required = true): string { return v || ""; } +interface Hop1 { + source: string; + router: string; + calldata: string; + out: BigNumber; // USDT out of hop 1 (base units) + deadline: BigNumber; // unix seconds — the quote's on-chain expiry +} + +/** + * Fetch the hop-1 (bStock -> USDT) swap from the configured source(s) and return the executable route. + * Generic over the `SOURCES` registry (lib/sources.ts): `SOURCE=auto` (default) prices EVERY available + * source and takes the higher out; `SOURCE=native,liquidmesh,…` restricts to a subset. A source that + * errors at quote time drops out; only the WINNER's `build()` runs, so a losing source that needs a + * second round-trip to build (e.g. Liquid Mesh `/swap`) never pays it. Adding a source = one adapter in + * lib/sources.ts; this function is unchanged. + */ +async function pickHop1Source(args: QuoteArgs): Promise { + const sources = selectedSources(); + if (sources.length === 0) { + throw new Error( + "no hop-1 source available — provide NATIVE_API_KEY and/or LM_API_KEY+LM_PRIVATE_KEY_SEED, or set SOURCE", + ); + } + + // Price every selected source in parallel; a source that errors (API down / no route) drops out. + const settled = await Promise.allSettled(sources.map(async s => ({ name: s.name, quote: await s.getQuote(args) }))); + const ok = settled.flatMap(r => (r.status === "fulfilled" ? [r.value] : [])); + const failed = settled.flatMap((r, i) => (r.status === "rejected" ? [`${sources[i].name}=[${r.reason}]`] : [])); + if (ok.length === 0) throw new Error(`all hop-1 sources failed: ${failed.join(" ")}`); + if (sources.length > 1) { + const line = ok.map(o => `${o.name}=${ethers.utils.formatUnits(o.quote.out, 18)}`).join(" "); + console.log(`hop-1 compare: ${line} USDT${failed.length ? ` (failed: ${failed.join(" ")})` : ""}`); + } + + // Winner = highest USDT out. Only its build() runs. + const winner = ok.reduce((best, cur) => (cur.quote.out.gt(best.quote.out) ? cur : best)); + const built = await winner.quote.build(); + return { + source: winner.name, + router: built.router, + calldata: built.calldata, + out: winner.quote.out, + deadline: built.deadline, + }; +} + export async function atomicLiquidate(signer: Signer) { const dryRun = process.env.DRY_RUN === "1"; const mode = (process.env.MODE || "inventory").toLowerCase(); - const slippage = Number(process.env.SLIPPAGE || "0.5"); - const minOutBufferPct = Number(process.env.MIN_OUT_BUFFER || "0.5"); - const seizeBufferPct = Number(process.env.SEIZE_BUFFER || "0.1"); - // Bound the haircut: a garbage or oversized value would silently under-quote (and in flash mode - // starve the principal + premium repay). The on-chain InsufficientOut still backstops it, but fail - // loudly here instead of after burning a Native quote. - if (!Number.isFinite(seizeBufferPct) || seizeBufferPct < 0 || seizeBufferPct >= 100) { - throw new Error(`SEIZE_BUFFER must be a percent in [0, 100), got "${process.env.SEIZE_BUFFER}"`); - } + // Percent knobs must all be in [0, 100): a value >= 100 makes the `(100 - pct)` factor below negative, + // producing a negative BigNumber that fails opaquely at ABI-encode time instead of with a legible error. + const pct = (name: string, dflt: string): number => { + const v = Number(process.env[name] || dflt); + if (!Number.isFinite(v) || v < 0 || v >= 100) { + throw new Error(`${name} must be a percent in [0, 100), got "${process.env[name]}"`); + } + return v; + }; + const slippage = pct("SLIPPAGE", "0.5"); + const minOutBufferPct = pct("MIN_OUT_BUFFER", "0.5"); + const seizeBufferPct = pct("SEIZE_BUFFER", "0.1"); const liquidator = new Contract(env("LIQUIDATOR"), LIQUIDATOR_ABI, signer); const borrower = ethers.utils.getAddress(env("BORROWER")); @@ -174,12 +237,12 @@ export async function atomicLiquidate(signer: Signer) { const seizedHumanQuote = ethers.utils.formatUnits(seizedForQuote, bStockDec); // 3. Build the swap, taker = the LIQUIDATOR CONTRACT (so it can submit + receive the debt asset). - // The contract measures proceeds in the DEBT asset and enforces `minOut` in it. Native only quotes - // bStock->USDT on BSC (every bStock pair in the live orderbook is *<->USDT), so: - // - USDT debt -> single hop: Native bStock->USDT is already the debt asset. - // - other debt -> two hops: Native bStock->USDT (hop 1), then USDT->debt via an allowlisted - // AMM/aggregator (hop 2, see lib/amm.ts). `minOut` is in the debt asset - // across the whole chain; `amountOut` below is the FINAL debt out. + // The contract measures proceeds in the DEBT asset and enforces `minOut` in it. The RFQ sources only + // quote bStock->USDT on BSC (every bStock pair in the live orderbooks is *<->USDT), so: + // - USDT debt -> single hop: the hop-1 bStock->USDT is already the debt asset. + // - other debt -> two hops: bStock->USDT (hop 1, from the source registry), then USDT->debt via an + // allowlisted AMM/aggregator (hop 2, see lib/amm.ts). `minOut` is in the + // debt asset across the whole chain; `amountOut` below is the FINAL debt out. const nativeOut = ethers.utils.getAddress(process.env.USDT_ADDR || BSC_USDT); const twoHop = debt.address.toLowerCase() !== nativeOut.toLowerCase(); @@ -206,20 +269,26 @@ export async function atomicLiquidate(signer: Signer) { intermediateToken = nativeOut; } } else { - // Hop 1: Native RFQ always outputs USDT (bStock pairs only with USDT on BSC). - const q = await getFirmQuote({ - fromAddress: liquidator.address, + // Hop 1: bStock -> USDT (bStock pairs only with USDT on BSC). Sources come from the registry + // (lib/sources.ts); `SOURCE` selects them — "auto" (default) prices every available source and takes + // the higher out, or a comma-separated subset (e.g. "native,liquidmesh"). Liquid Mesh re-serves the + // same `rfq_native` book plus extra makers, matching or marginally beating Native and going deeper on + // some tails. + const hop1 = await pickHop1Source({ + taker: liquidator.address, tokenIn: bStock.address, - tokenOut: nativeOut, - amount: seizedHumanQuote, + usdtOut: nativeOut, + humanAmount: seizedHumanQuote, + weiAmount: seizedForQuote, slippage, }); - const ttl = quoteDeadline(q) - Math.floor(Date.now() / 1000); - if (ttl <= 0) throw new Error("quote already expired — refetch"); - deadline = BigNumber.from(quoteDeadline(q)); // settle tx reverts on-chain past the quote's expiry - router = q.txRequest.target; - swapCalldata = q.txRequest.calldata; - const midOut = BigNumber.from(q.amountOut); // USDT out of hop 1 + const ttl = hop1.deadline.toNumber() - Math.floor(Date.now() / 1000); + if (ttl <= 0) throw new Error(`${hop1.source} quote already expired — refetch`); + deadline = hop1.deadline; // settle tx reverts on-chain past the quote's expiry + router = hop1.router; + swapCalldata = hop1.calldata; + const midOut = hop1.out; // USDT out of hop 1 + console.log(`hop-1 source: ${hop1.source} (out=${ethers.utils.formatUnits(midOut, 18)} USDT)`); if (twoHop) { // Hop 2: convert the hop-1 USDT to the (non-USDT) debt asset via an allowlisted AMM/aggregator. @@ -238,13 +307,13 @@ export async function atomicLiquidate(signer: Signer) { intermediateToken = nativeOut; amountOut = BigNumber.from(amm.expectedOut); console.log( - `Native: ${seizedHumanQuote} ${bStockSym} -> ${ethers.utils.formatUnits(midOut, 18)} USDT (TTL ${ttl}s, ${router}); ` + + `${hop1.source}: ${seizedHumanQuote} ${bStockSym} -> ${ethers.utils.formatUnits(midOut, 18)} USDT (TTL ${ttl}s, ${router}); ` + `AMM: -> ${ethers.utils.formatUnits(amountOut, debtDec)} ${debtSym} (${router2})`, ); } else { amountOut = midOut; console.log( - `Native quote: ${seizedHumanQuote} ${bStockSym} -> ${ethers.utils.formatUnits(amountOut, debtDec)} ${debtSym} (TTL ${ttl}s, router ${router})`, + `${hop1.source} quote: ${seizedHumanQuote} ${bStockSym} -> ${ethers.utils.formatUnits(amountOut, debtDec)} ${debtSym} (TTL ${ttl}s, router ${router})`, ); } } diff --git a/scripts/bstock/lib/liquidmesh.ts b/scripts/bstock/lib/liquidmesh.ts new file mode 100644 index 000000000..00586222a --- /dev/null +++ b/scripts/bstock/lib/liquidmesh.ts @@ -0,0 +1,193 @@ +/** + * Liquid Mesh Swap API client — BSC DEX + RFQ aggregator, an ALTERNATIVE hop-1 source to Native. + * + * Like Native, Liquid Mesh quotes bStock <-> USDT (it re-serves the same `rfq_native` book plus + * `rfq_neptune` and, for thin names, AMM pools), so it drops into the existing atomic flow as a + * swap-in replacement for the Native hop-1 quote. The aggregator returns ONE router calldata blob + * (the multi-source split lives inside it); the liquidator forwards it in `_swap` and all sources + * settle in the single liquidation tx. + * + * TWO differences from Native, both already handled by the contract: + * 1. Split spender — Liquid Mesh pulls the input token through a SEPARATE settlement contract + * (approve the SPENDER `0x8157…`, but CALL the router `0x3d90…`). The contract's `routerSpender` + * mapping covers this; the operator must run `setRouterSpender(LM_ROUTER, LM_SPENDER)` once. + * 2. Build-time simulation — `POST /swap` normally simulates `transferFrom(userAddress, …)` off-chain + * and refuses to return calldata unless the caller ALREADY holds the input. Our liquidator holds + * zero bStock until mid-tx, so we pass `disableSimulate: true` (a documented request flag) to skip + * that off-chain pre-check and get executable calldata pre-seize. On-chain safety is unchanged: the + * returned order still carries an `expiryTimestamp` deadline and the contract enforces `minOut`. + * + * Auth: every request carries `LM-API-KEY: ` and `Authorization: Bearer `, where the JWT is + * Ed25519-signed (`alg EdDSA`) over `sha256(timestamp + METHOD + PATH + BODY)` with a short TTL. Provide + * the key via `LM_API_KEY` and the Ed25519 private-key SEED (base64url) via `LM_PRIVATE_KEY_SEED`. Never + * commit either. + * + * Endpoints (base `https://api.liquidmesh.io/v1/bsc`): + * GET /quote price + route split (no calldata; needs no balance) + * POST /swap executable calldata (`data.callMsg {from,to,value,data}`) — pass disableSimulate:true + */ +import { createHash, createPrivateKey, sign as edSign } from "crypto"; + +// The JWT is signed over the FULL request path, so the network prefix must be part of `path` (not folded +// into the host) — otherwise the signed path omits `/v1/bsc` and the server rejects the token (401). +const DEFAULT_HOST = "https://api.liquidmesh.io"; +const NETWORK_PREFIX = "/v1/bsc"; +export const LM_BSC_USDT = "0x55d398326f99059fF775485246999027B3197955"; +/** Liquid Mesh BSC router (the swap call target). */ +export const LM_ROUTER = "0x3d90f66B534Dd8482b181e24655A9e8265316BE9"; +/** Liquid Mesh BSC spender (the token-approval / transferFrom target) — set via `setRouterSpender`. */ +export const LM_SPENDER = "0x8157a9d65807521FBB8db8f37EEEcEfDD247E9B1"; + +export interface LmDex { + dex: string; // e.g. "rfq_native", "rfq_neptune" + weight: number; // bps of the split (out of 10000) + extraInfo?: string; +} +export interface LmRoutePlan { + subRouters: Array<{ fromToken?: string; toToken?: string; dexes: LmDex[] }>; +} +export interface LmQuote { + inputAmount: string; // wei + outputAmount: string; // wei + priceImpactPct?: string; + midPrice?: string; + estimatedGas?: number; + routePlans: LmRoutePlan[]; +} +export interface LmCallMsg { + from: string; + to: string; // the router (LM_ROUTER) + value: string; // hex + data: string; // executable calldata blob +} +export interface LmSwap { + chainId: string; + callMsg: LmCallMsg; + orderId: string; + expiryTimestamp: number; // unix seconds — the on-chain deadline +} + +export interface LmQuoteParams { + userAddress: string; // taker; pass the liquidator CONTRACT so proceeds route back to it + tokenIn: string; + tokenOut: string; + amountWei: string; // base units (wei), NOT human + chainId?: string; // default "56" +} +export interface LmSwapParams extends LmQuoteParams { + minOutWei: string; // floor for the built order (outputAmount in the swap body) + routePlans: LmRoutePlan[]; // pass the quote's routePlans so the blob matches the compared price + slippageBps?: number; // default 50 + disableSimulate?: boolean; // default true — skip LM's off-chain transferFrom pre-check (see header) +} + +function host(): string { + return process.env.LM_API_HOST || DEFAULT_HOST; +} + +function apiKey(): string { + const key = process.env.LM_API_KEY; + if (!key) throw new Error("LM_API_KEY env var is required (Liquid Mesh API key). Do not hardcode it."); + return key; +} + +/** Load the Ed25519 signing key from the base64url seed in `LM_PRIVATE_KEY_SEED`. */ +function signingKey() { + const seedB64url = process.env.LM_PRIVATE_KEY_SEED; + if (!seedB64url) { + throw new Error( + "LM_PRIVATE_KEY_SEED env var is required (Ed25519 private-key seed, base64url). Do not hardcode it.", + ); + } + const seed = Buffer.from(seedB64url.replace(/-/g, "+").replace(/_/g, "/"), "base64"); + // PKCS#8 DER prefix for an Ed25519 raw 32-byte seed. + const der = Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), seed]); + return createPrivateKey({ key: der, format: "der", type: "pkcs8" }); +} + +const b64url = (b: Buffer) => b.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); + +/** + * Build the Ed25519 JWT for one request. The signed message is `sha256(ts + METHOD + PATH + BODY)`; + * the token is short-lived (LM enforces a tight TTL), so mint one per request immediately before it. + */ +function jwt(method: string, path: string, body = ""): string { + const ts = Date.now(); + const message = createHash("sha256").update(`${ts}${method}${path}${body}`).digest("hex"); + const iat = Math.floor(ts / 1000); + const header = b64url(Buffer.from(JSON.stringify({ typ: "JWT", alg: "EdDSA" }))); + const payload = b64url(Buffer.from(JSON.stringify({ tim: ts, message, iss: apiKey(), iat, exp: iat + 2 }))); + const signingInput = `${header}.${payload}`; + return `${signingInput}.${b64url(edSign(null, Buffer.from(signingInput), signingKey()))}`; +} + +function headers(method: string, path: string, body = ""): Record { + return { + "LM-API-KEY": apiKey(), + Authorization: `Bearer ${jwt(method, path, body)}`, + "Content-Type": "application/json", + }; +} + +/** + * Indicative price + route split (no calldata). Needs no balance, so it is safe to call for the + * winner-selection comparison against Native. + */ +export async function getQuote(p: LmQuoteParams): Promise { + const chainId = p.chainId || "56"; + const path = + `${NETWORK_PREFIX}/quote?chainId=${chainId}&inputToken=${p.tokenIn}&outputToken=${p.tokenOut}` + + `&amount=${p.amountWei}&userAddress=${p.userAddress}`; + const res = await fetch(`${host()}${path}`, { headers: headers("GET", path) }); + const text = await res.text(); + let body: any; + try { + body = JSON.parse(text); + } catch { + throw new Error(`Liquid Mesh /quote non-JSON (HTTP ${res.status}): ${text.slice(0, 200)}`); + } + if (body.code !== 0 || !body.data) { + throw new Error( + `Liquid Mesh /quote error (HTTP ${res.status}) ${body.code}: ${body.msg} (${p.tokenIn}->${p.tokenOut})`, + ); + } + return body.data as LmQuote; +} + +/** + * Executable swap. Passes `disableSimulate: true` so LM skips the off-chain `transferFrom` pre-check and + * returns calldata even though the caller (the liquidator) does not yet hold the input token — it will + * hold it mid-tx after the seize. The returned `callMsg.data` is the blob to forward in `_swap`. + */ +export async function buildSwap(p: LmSwapParams): Promise { + const chainId = p.chainId || "56"; + const slippageBps = p.slippageBps ?? 50; + const path = `${NETWORK_PREFIX}/swap`; + const body = JSON.stringify({ + userAddress: p.userAddress, + slippageBps, + disableSimulate: p.disableSimulate ?? true, // liquidator holds zero input until mid-tx; skip LM's off-chain balance sim + swapInfo: { + chainId, + inputToken: p.tokenIn, + inputAmount: p.amountWei, + outputToken: p.tokenOut, + outputAmount: p.minOutWei, + slippageBps, + routePlans: p.routePlans, + }, + }); + const res = await fetch(`${host()}${path}`, { method: "POST", headers: headers("POST", path, body), body }); + const text = await res.text(); + let j: any; + try { + j = JSON.parse(text); + } catch { + throw new Error(`Liquid Mesh /swap non-JSON (HTTP ${res.status}): ${text.slice(0, 200)}`); + } + if (j.code !== 0 || !j.data || !j.data.callMsg) { + throw new Error(`Liquid Mesh /swap error (HTTP ${res.status}) ${j.code}: ${j.msg}`); + } + const d = j.data; + return { chainId: d.chainId, callMsg: d.callMsg, orderId: d.orderId, expiryTimestamp: Number(d.expiryTimestamp) }; +} diff --git a/scripts/bstock/lib/sources.ts b/scripts/bstock/lib/sources.ts new file mode 100644 index 000000000..993a23580 --- /dev/null +++ b/scripts/bstock/lib/sources.ts @@ -0,0 +1,149 @@ +/** + * Hop-1 liquidity-source REGISTRY (bStock -> USDT). + * + * Each source (Native RFQ, Liquid Mesh, and any future aggregator) implements the small `QuoteSource` + * interface below and is listed in `SOURCES`. The selection logic in atomic-liquidate.ts is generic over + * this array — it prices every selected+available source and takes the higher out — so ADDING A SOURCE is + * a localized change: write one adapter (the source's own auth/quote/calldata shape can't be config-only) + * and push it here. No change to the comparison logic, and NO on-chain change or redeploy — on-chain the + * contract is already source-agnostic (allowlist the router with `setRouter` + `setRouterSpender`). + * + * `SOURCE` env selects which are active: "auto" (default) = every available source; or a comma-separated + * subset by `name` (e.g. "native,liquidmesh"). A source whose required creds are absent reports + * `available() = false` and is skipped. + */ +import { BigNumber } from "ethers"; +import { ethers } from "hardhat"; + +import { buildSwap as lmBuildSwap, getQuote as lmGetQuote } from "./liquidmesh"; +import { getFirmQuote, quoteDeadline } from "./native"; + +/** Inputs shared by every source's hop-1 quote (bStock -> USDT). */ +export interface QuoteArgs { + taker: string; // the liquidator contract (msg.sender at execution; proceeds recipient) + tokenIn: string; // bStock + usdtOut: string; // USDT (hop-1 output) + humanAmount: string; // seize amount, human units (Native takes human) + weiAmount: BigNumber; // seize amount, base units (Liquid Mesh takes wei) + slippage: number; // percent, e.g. 0.5 +} + +/** The executable route a source resolves to once it wins the comparison. */ +export interface BuiltRoute { + router: string; // swap call target (allowlist this on the contract) + calldata: string; // opaque blob forwarded by `_swap` + deadline: BigNumber; // unix seconds — the quote's on-chain expiry +} + +/** + * A priced quote. `out` drives the winner comparison; `build()` produces the executable calldata and is + * only called for the winner — so a source that needs a second round-trip to build (e.g. Liquid Mesh's + * `/swap`) does not pay it when it loses. Native already holds the calldata from its quote, so its + * `build()` just returns it (no extra network call). + */ +export interface SourceQuote { + out: BigNumber; + build: () => Promise; +} + +export interface QuoteSource { + name: string; + /** True when the source's required env/creds are present; false ones are skipped in "auto". */ + available: () => boolean; + /** Fetch a price + a lazy builder for the executable route. */ + getQuote: (a: QuoteArgs) => Promise; +} + +// --------------------------------------------------------------------------- // +// Native RFQ // +// --------------------------------------------------------------------------- // + +const nativeSource: QuoteSource = { + name: "native", + available: () => !!process.env.NATIVE_API_KEY, + async getQuote(a) { + const q = await getFirmQuote({ + fromAddress: a.taker, + tokenIn: a.tokenIn, + tokenOut: a.usdtOut, + amount: a.humanAmount, + slippage: a.slippage, + }); + return { + out: BigNumber.from(q.amountOut), + // Firm-quote already carries the executable txRequest — build is immediate, no re-fetch. + build: async () => ({ + router: q.txRequest.target, + calldata: q.txRequest.calldata, + deadline: BigNumber.from(quoteDeadline(q)), + }), + }; + }, +}; + +// --------------------------------------------------------------------------- // +// Liquid Mesh // +// --------------------------------------------------------------------------- // + +const liquidMeshSource: QuoteSource = { + name: "liquidmesh", + available: () => !!process.env.LM_API_KEY && !!process.env.LM_PRIVATE_KEY_SEED, + async getQuote(a) { + const quote = await lmGetQuote({ + userAddress: a.taker, + tokenIn: a.tokenIn, + tokenOut: a.usdtOut, + amountWei: a.weiAmount.toString(), + }); + const out = BigNumber.from(quote.outputAmount); + return { + out, + // `/swap` (disableSimulate:true) is only fetched if Liquid Mesh wins — avoids a wasted order build. + build: async () => { + // The order's own floor (`outputAmount`); LM also applies `slippageBps`, so the effective on-chain + // floor may be slightly looser than this. That is fine — the CONTRACT's `minOut` is the real guard; + // this floor only bounds how far LM itself will let the fill slip. + const minFloor = out.mul(Math.round((100 - a.slippage) * 100)).div(10000); + const swap = await lmBuildSwap({ + userAddress: a.taker, + tokenIn: a.tokenIn, + tokenOut: a.usdtOut, + amountWei: a.weiAmount.toString(), + minOutWei: minFloor.toString(), + routePlans: quote.routePlans, + slippageBps: Math.round(a.slippage * 100), + }); + return { + // Use the router LM actually wants called (`callMsg.to`); it is `LM_ROUTER` today, but relying on + // the response keeps us correct if LM rotates it. The on-chain `isRouter` allowlist still gates it. + router: ethers.utils.getAddress(swap.callMsg.to), + calldata: swap.callMsg.data, + deadline: BigNumber.from(swap.expiryTimestamp), + }; + }, + }; + }, +}; + +// --------------------------------------------------------------------------- // +// Registry // +// --------------------------------------------------------------------------- // + +/** All known hop-1 sources. Add a new aggregator by pushing its adapter here — nothing else changes. */ +export const SOURCES: QuoteSource[] = [nativeSource, liquidMeshSource]; + +/** Resolve the `SOURCE` env to the active source list: "auto" = every available source, else a subset. */ +export function selectedSources(): QuoteSource[] { + const raw = (process.env.SOURCE || "auto").toLowerCase().trim(); + if (raw === "auto") return SOURCES.filter(s => s.available()); + const names = raw.split(",").map(s => s.trim()); + const picked = SOURCES.filter(s => names.includes(s.name)); + const unknown = names.filter(n => !SOURCES.some(s => s.name === n)); + if (unknown.length) + throw new Error(`unknown SOURCE(s): ${unknown.join(", ")}. Known: ${SOURCES.map(s => s.name).join(", ")}`); + const unavailable = picked.filter(s => !s.available()); + if (unavailable.length) { + throw new Error(`SOURCE(s) missing required creds: ${unavailable.map(s => s.name).join(", ")}`); + } + return picked; +} diff --git a/scripts/bstock/verify-lm-fork.ts b/scripts/bstock/verify-lm-fork.ts new file mode 100644 index 000000000..c9065077c --- /dev/null +++ b/scripts/bstock/verify-lm-fork.ts @@ -0,0 +1,179 @@ +/** + * FORK VERIFICATION — proves a Liquid Mesh `disableSimulate:true` swap blob actually EXECUTES on-chain. + * + * Liquid Mesh returns calldata WITHOUT simulating it (that is the whole point of `disableSimulate`), so + * the one thing untested by the mocked unit suite is: does that blob really fill against the real router? + * This script answers it end-to-end against a bscmainnet fork at (near) head: + * + * 1. hardhat_reset the fork to LATEST — the RFQ maker signatures reference live on-chain inventory/nonces, + * so the fork block must match head or settlement reverts. + * 2. Live `GET /quote` + `POST /swap` (disableSimulate:true) for a small bStock -> USDT (lib/liquidmesh.ts). + * 3. Replicate the contract's `_swap` EXACTLY: give a taker the real bStock, approve the LM SPENDER (not + * the router), then `router.call(callMsg.data)`. Assert USDT actually landed. + * + * This is a one-shot, on-demand check (needs live LM creds + an archive RPC + a recent block), NOT a CI + * fixture. Run: + * source ~/.liquidmesh/keys.env + * LM_API_KEY=$API_KEY LM_PRIVATE_KEY_SEED=$PRIVATE_KEY_BASE64_SEED FORKED_NETWORK=bscmainnet \ + * AMOUNT=3 npx hardhat run scripts/bstock/verify-lm-fork.ts + * + * REQUIRED hardhat config for this diagnostic (the RFQ orders are EIP-712 signed with chainId 56 and the + * head block needs modern opcodes, neither of which the default fork network provides). Temporarily set, + * in `hardhat.config.ts` `isFork()`, on the returned hardhat-network object: + * chainId: 56, + * chains: { 56: { hardforkHistory: { berlin: 0, london: 13000000, shanghai: 40000000, cancun: 48000000 } } } + * WITHOUT `chainId: 56` the maker signature fails `InvalidSignature` (fork runs as 31337) — a fork artifact, + * not a real defect. The guard below aborts early if chainId != 56 so the failure is legible. + * + * Env: + * BSTOCK bStock token to sell (default TSLAB 0x5b19…292f) + * AMOUNT human units to sell (default "10") + * TAKER address that executes the swap (default: a real holder found via Transfer logs) + */ +import { BigNumber } from "ethers"; +import { ethers, network } from "hardhat"; + +import { LM_BSC_USDT, LM_SPENDER, buildSwap, getQuote } from "./lib/liquidmesh"; + +const ERC20_ABI = [ + "function decimals() view returns (uint8)", + "function symbol() view returns (string)", + "function balanceOf(address) view returns (uint256)", + "function approve(address,uint256) returns (bool)", +]; + +const DEFAULT_BSTOCK = "0x5b1910eAaD6450E50f816082Aa078C41F10C292f"; // TSLAB + +// Find a real, currently-holding address by scanning recent Transfer logs for recipients whose LIVE +// balance covers `amount`. A legitimate holder is (for a transfer-restricted bStock) already allowlisted, +// so it can move the token to the LM settlement — exactly what the liquidator does with seized bStock. +async function findHolder(token: string, amount: BigNumber, headBlock: number): Promise { + const erc20 = new ethers.Contract(token, ERC20_ABI, ethers.provider); + const transferTopic = ethers.utils.id("Transfer(address,address,uint256)"); + const SPAN = 2000; + for (let from = headBlock - SPAN; from > headBlock - SPAN * 8; from -= SPAN) { + const logs = await ethers.provider.getLogs({ + address: token, + topics: [transferTopic], + fromBlock: from, + toBlock: from + SPAN - 1, + }); + // Candidate recipients (topic[2]), newest first. + const cands: string[] = []; + for (let i = logs.length - 1; i >= 0; i--) { + const to = ethers.utils.getAddress("0x" + logs[i].topics[2].slice(26)); + if (to !== ethers.constants.AddressZero && !cands.includes(to)) cands.push(to); + } + for (const c of cands) { + try { + if ((await erc20.balanceOf(c)).gte(amount)) return c; + } catch { + /* skip */ + } + } + } + throw new Error(`no ${token} holder with balance >= ${amount} found in recent Transfer logs`); +} + +async function main() { + const rpc = process.env.ARCHIVE_NODE_bscmainnet; + if (!rpc) throw new Error("ARCHIVE_NODE_bscmainnet must be set (fork RPC)"); + + // 1. Fork at LATEST so the RFQ maker state matches head. + await network.provider.request({ method: "hardhat_reset", params: [{ forking: { jsonRpcUrl: rpc } }] }); + const block = await ethers.provider.getBlock("latest"); + console.log( + `forked bscmainnet @ block ${block.number} (ts ${block.timestamp}, ${new Date(block.timestamp * 1000).toISOString()})`, + ); + + // The RFQ maker orders are EIP-712 signed with chainId 56; the default fork network runs as 31337, which + // makes `ecrecover` compute a different digest and revert `InvalidSignature`. Fail loudly, not cryptically. + const { chainId } = await ethers.provider.getNetwork(); + if (chainId !== 56) { + throw new Error( + `fork chainId is ${chainId}, must be 56 — set chainId:56 in hardhat.config isFork() (see header). ` + + `Otherwise the RFQ signature fails InvalidSignature as a fork artifact.`, + ); + } + + const bStockAddr = ethers.utils.getAddress(process.env.BSTOCK || DEFAULT_BSTOCK); + const bStock = new ethers.Contract(bStockAddr, ERC20_ABI, ethers.provider); + const usdt = new ethers.Contract(LM_BSC_USDT, ERC20_ABI, ethers.provider); + const [dec, sym] = await Promise.all([bStock.decimals(), bStock.symbol()]); + const amount = ethers.utils.parseUnits(process.env.AMOUNT || "10", dec); + + // bStock (ERC-8056) uses non-standard storage and is likely transfer-restricted (allowlist), so we + // can't mint to an arbitrary taker. Instead use a REAL, already-allowlisted holder as the taker: scan + // recent Transfer logs, pick a recipient whose live balance covers `amount`. This also faithfully + // models the liquidator, which only ever sells bStock it legitimately holds (seized + redeemed). + const taker = process.env.TAKER + ? ethers.utils.getAddress(process.env.TAKER) + : await findHolder(bStockAddr, amount, block.number); + console.log( + `taker (real ${sym} holder): ${taker} balance=${ethers.utils.formatUnits(await bStock.balanceOf(taker), dec)}`, + ); + + // 2. Live LM quote + swap(disableSimulate) — taker as userAddress (not embedded in calldata; router + // keys off msg.sender, so the built blob is executable by whoever sends it). + const quote = await getQuote({ + userAddress: taker, + tokenIn: bStockAddr, + tokenOut: LM_BSC_USDT, + amountWei: amount.toString(), + }); + const expectedOut = BigNumber.from(quote.outputAmount); + console.log( + `LM quote: ${process.env.AMOUNT || "10"} ${sym} -> ${ethers.utils.formatUnits(expectedOut, 18)} USDT ` + + `[${quote.routePlans?.[0]?.subRouters?.[0]?.dexes?.map(d => `${d.dex}:${(d.weight / 100).toFixed(0)}%`).join(" ")}]`, + ); + const minOut = expectedOut.mul(95).div(100); + const swap = await buildSwap({ + userAddress: taker, + tokenIn: bStockAddr, + tokenOut: LM_BSC_USDT, + amountWei: amount.toString(), + minOutWei: minOut.toString(), + routePlans: quote.routePlans, + slippageBps: 50, + }); + console.log( + `LM /swap (disableSimulate): router=${swap.callMsg.to} dataLen=${swap.callMsg.data.length} expiry=${swap.expiryTimestamp}`, + ); + + // 3. Replicate `_swap`: impersonate the (real) holder, approve the SPENDER, call the router. + const { impersonateAccount, setBalance } = await import("@nomicfoundation/hardhat-network-helpers"); + await impersonateAccount(taker); + await setBalance(taker, ethers.utils.parseEther("10")); + const takerSigner = await ethers.getSigner(taker); + + await bStock.connect(takerSigner).approve(LM_SPENDER, amount); + console.log(` approved spender ${LM_SPENDER} for ${ethers.utils.formatUnits(amount, dec)} ${sym}`); + + const usdtBefore: BigNumber = await usdt.balanceOf(taker); + console.log(` executing router.call(callMsg.data) ...`); + const tx = await takerSigner.sendTransaction({ + to: swap.callMsg.to, + data: swap.callMsg.data, + value: swap.callMsg.value, + }); + const rcpt = await tx.wait(); + const usdtAfter: BigNumber = await usdt.balanceOf(taker); + const got = usdtAfter.sub(usdtBefore); + + console.log(`\n gas ${rcpt.gasUsed.toString()}`); + console.log( + ` USDT received: ${ethers.utils.formatUnits(got, 18)} (minOut ${ethers.utils.formatUnits(minOut, 18)})`, + ); + if (got.lt(minOut)) throw new Error(`FAIL: received ${got} < minOut ${minOut}`); + console.log(`\n✅ PASS — Liquid Mesh disableSimulate calldata EXECUTED on-chain and delivered USDT >= minOut.`); + console.log( + ` Proves the atomic path: the same call succeeds inside the liquidator's _swap (approve spender -> router.call).`, + ); +} + +main() + .then(() => process.exit(0)) + .catch(e => { + console.error("\n❌", e.message || e); + process.exit(1); + }); diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index 4559d1490..b51719451 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -1,6 +1,6 @@ import { setBalance } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; -import { Contract } from "ethers"; +import { BigNumberish, Contract } from "ethers"; import { ethers, upgrades } from "hardhat"; import { atomicLiquidate } from "../../scripts/bstock/atomic-liquidate"; @@ -38,6 +38,9 @@ const SCRIPT_ENV = [ "MIN_OUT_BUFFER", "SEIZE_BUFFER", "NATIVE_API_KEY", + "SOURCE", + "LM_API_KEY", + "LM_PRIVATE_KEY_SEED", ] as const; describe("bStock atomic liquidation script", () => { @@ -273,4 +276,151 @@ describe("bStock atomic liquidation script", () => { expect(await ethers.provider.getBalance(liq.address)).to.equal(0); // no native BNB retained expect(await usdt.balanceOf(liq.address)).to.equal(0); // intermediate consumed }); + + // ------------------------------------------------------------------------ // + // Liquidity-source registry (lib/sources.ts): Native vs Liquid Mesh // + // ------------------------------------------------------------------------ // + // These exercise the REAL hop-1 source path (no MOCK_NATIVE): `fetch` is stubbed to answer the Native + // firm-quote, the Liquid Mesh `/quote`, and the Liquid Mesh `/swap` (disableSimulate) so the winner + // selection, the LM JWT/build round-trip, and the split-spender settle all run for real on-chain. + describe("liquidity-source registry", () => { + // Any 32-byte Ed25519 seed signs a valid JWT locally (the LM server is never hit — fetch is stubbed). + const LM_SEED = Buffer.alloc(32, 7).toString("base64url"); + let spender: Contract, lmRouter: Contract; + + // A response object exposing both `.json()` (Native lib) and `.text()` (Liquid Mesh lib). + const resp = (obj: any) => ({ status: 200, json: async () => obj, text: async () => JSON.stringify(obj) }); + + // Stub Native firm-quote + LM /quote + LM /swap. `lmCalldata` is executed on-chain against `lmRouter`. + function stubFetch(opts: { nativeOut: BigNumberish; lmOut: BigNumberish; lmCalldata: string }) { + const real = globalThis.fetch; + globalThis.fetch = (async (url: unknown, init?: any) => { + const u = String(url); + const method = (init?.method || "GET").toUpperCase(); + if (u.includes("native.org")) { + return resp({ + success: true, + recipient: liq.address, + amountIn: "0", + amountOut: opts.nativeOut.toString(), + orders: [{ deadlineTimestamp: Math.floor(Date.now() / 1000) + 3600 }], + txRequest: { target: router.address, calldata: swapAllCalldata(liq.address), value: "0" }, + }); + } + if (u.includes("liquidmesh.io") && u.includes("/quote")) { + return resp({ + code: 0, + msg: "OK", + data: { + inputAmount: "0", + outputAmount: opts.lmOut.toString(), + routePlans: [{ subRouters: [{ dexes: [{ dex: "rfq_native", weight: 10000 }] }] }], + }, + }); + } + if (u.includes("liquidmesh.io") && method === "POST") { + // /swap (disableSimulate): calldata blob + expiry, target = the LM split router. + return resp({ + code: 0, + msg: "OK", + data: { + chainId: "56", + callMsg: { from: liq.address, to: lmRouter.address, value: "0x0", data: opts.lmCalldata }, + orderId: "1", + // Generous expiry: the hardhat chain clock can run ahead of wall-clock after prior tests, so a + // short TTL would trip DeadlineExpired. (Real LM orders are short-lived; the contract enforces it.) + expiryTimestamp: Math.floor(Date.now() / 1000) + 3600, + }, + }); + } + throw new Error(`unexpected fetch: ${method} ${u}`); + }) as unknown as typeof fetch; + return real; + } + + // Deploy the Liquid-Mesh-style split router (call target 0x…/spender 0x… differ), allowlist it, wire + // the spender, and pre-fund it with USDT so its swap can pay out. + beforeEach(async () => { + spender = await (await ethers.getContractFactory("MockSpender")).deploy(); + lmRouter = await (await ethers.getContractFactory("MockSplitRouter")).deploy(spender.address); + await liq.connect(owner).setRouter(lmRouter.address, true); + await liq.connect(owner).setRouterSpender(lmRouter.address, spender.address); + await usdt.mint(lmRouter.address, OUT); // LM router pays USDT + }); + + function lmSwapAll() { + return lmRouter.interface.encodeFunctionData("swapAll", [bStock.address, usdt.address, liq.address]); + } + + function lmEnv(over: Record = {}) { + setEnv({ + MOCK_NATIVE: "", // force the real source path + MOCK_OUT: "", + NATIVE_API_KEY: "test-native", + LM_API_KEY: "test-lm", + LM_PRIVATE_KEY_SEED: LM_SEED, + ...over, + }); + } + + it("SOURCE=auto: picks Liquid Mesh when it out-quotes Native, settles via the split spender", async () => { + await usdt.mint(liq.address, REPAY); // inventory + const real = stubFetch({ nativeOut: U("5400"), lmOut: OUT, lmCalldata: lmSwapAll() }); // LM (5500) > Native (5400) + try { + lmEnv({ SOURCE: "auto" }); + await atomicLiquidate(owner); + } finally { + globalThis.fetch = real; + } + + // Sold through the LM split router (its spender pulled the bStock), proceeds retained. + expect(await bStock.balanceOf(lmRouter.address)).to.equal(SEIZED); + expect(await bStock.balanceOf(router.address)).to.equal(0); // Native router untouched + expect(await usdt.balanceOf(liq.address)).to.equal(OUT); + expect(await bStock.allowance(liq.address, spender.address)).to.equal(0); // no standing approval + }); + + it("SOURCE=auto: picks Native when it out-quotes Liquid Mesh", async () => { + await usdt.mint(liq.address, REPAY); + const real = stubFetch({ nativeOut: OUT, lmOut: U("5400"), lmCalldata: lmSwapAll() }); // Native (5500) > LM (5400) + try { + lmEnv({ SOURCE: "auto" }); + await atomicLiquidate(owner); + } finally { + globalThis.fetch = real; + } + + expect(await bStock.balanceOf(router.address)).to.equal(SEIZED); // Native router sold it + expect(await bStock.balanceOf(lmRouter.address)).to.equal(0); + expect(await usdt.balanceOf(liq.address)).to.equal(OUT); + }); + + it("SOURCE=liquidmesh: forces Liquid Mesh even when Native quotes higher", async () => { + await usdt.mint(liq.address, REPAY); + const real = stubFetch({ nativeOut: U("9000"), lmOut: OUT, lmCalldata: lmSwapAll() }); // Native higher, but forced LM + try { + lmEnv({ SOURCE: "liquidmesh" }); + await atomicLiquidate(owner); + } finally { + globalThis.fetch = real; + } + + expect(await bStock.balanceOf(lmRouter.address)).to.equal(SEIZED); // LM used despite lower quote + expect(await usdt.balanceOf(liq.address)).to.equal(OUT); + }); + + it("rejects an unknown SOURCE name", async () => { + await usdt.mint(liq.address, REPAY); + lmEnv({ SOURCE: "nope" }); + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/unknown SOURCE/i); + expect(await usdt.balanceOf(liq.address)).to.equal(REPAY); // untouched + }); + + it("rejects SOURCE=liquidmesh when the LM creds are absent", async () => { + await usdt.mint(liq.address, REPAY); + lmEnv({ SOURCE: "liquidmesh", LM_API_KEY: "", LM_PRIVATE_KEY_SEED: "" }); + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/missing required creds/i); + expect(await usdt.balanceOf(liq.address)).to.equal(REPAY); // untouched + }); + }); }); diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts index df35c2cd3..80b4440ee 100644 --- a/tests/hardhat/Fork/BStockLiquidatorFork.ts +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -30,7 +30,7 @@ import { expect } from "chai"; import { BigNumber, Contract } from "ethers"; import { parseEther, parseUnits } from "ethers/lib/utils"; import fc from "fast-check"; -import { ethers } from "hardhat"; +import { ethers, upgrades } from "hardhat"; import { A, @@ -172,6 +172,66 @@ const test = () => { expect(await new ethers.Contract(TOK.USDT, ERC20_ABI, owner).balanceOf(liq.address)).to.be.gte(params.minOut); }); + it("single-hop USDT debt via a Liquid-Mesh-style split router (separate spender): settles atomically", async () => { + // Liquid Mesh pulls the input token through a SEPARATE spender, distinct from the call target, so + // the contract must approve the spender (not the router) via `setRouterSpender`. The DEPLOYED proxy + // (`DEPLOYED_LIQ`) predates `routerSpender`, so this scenario deploys a FRESH proxy on the current + // implementation and drives it against the SAME real gate + bStock market to prove the split path + // settles a real underwater position and leaves no standing approval. + const Factory = await ethers.getContractFactory("BStockLiquidator"); + const liqLm = await upgrades.deployProxy(Factory, [owner.address], { + constructorArgs: [A.COMPTROLLER, A.VBNB, A.VWBNB, TOK.WBNB], + unsafeAllow: ["constructor", "state-variable-immutable"], + }); + + const spender = await (await ethers.getContractFactory("MockSpender")).deploy(); + const lmRouter = await (await ethers.getContractFactory("MockSplitRouter")).deploy(spender.address); + await lmRouter.setRate(P_CRASH); // models the crashed-price bStock->USDT quote + await fund(TOK.USDT, lmRouter.address, parseUnits("1000000", 18)); // the LM router pays USDT + await liqLm.connect(owner).setRouter(lmRouter.address, true); + await liqLm.connect(owner).setRouterSpender(lmRouter.address, spender.address); + + const borrower = ethers.utils.getAddress("0x00000000000000000000000000000000ca110009"); + await makeUnderwaterBorrower({ + mkt, + vDebt: VUSDT, + borrower, + collateralBStock: parseUnits("100", 18), + borrowAmount: parseUnits("5000", 18), + crashPriceTo: P_CRASH, + }); + + const REPAY = parseUnits("2000", 18); + await fund(TOK.USDT, liqLm.address, REPAY); // inventory + const params = { + borrower, + vDebt: VUSDT, + vBStock: mkt.vBStock.address, + repayAmount: REPAY, + router: lmRouter.address, + swapCalldata: lmRouter.interface.encodeFunctionData("swapAll", [mkt.bStock.address, TOK.USDT, liqLm.address]), + minOut: parseUnits("1900", 18), + router2: ZERO, + swapCalldata2: "0x", + intermediateToken: ZERO, + deadline: ethers.constants.MaxUint256, + }; + + const borrowBefore = await new ethers.Contract(VUSDT, VTOKEN_ABI, owner).borrowBalanceStored(borrower); + const out = await liqLm.connect(owner).callStatic.liquidate(params); + expect(out).to.be.gte(params.minOut); + await liqLm.connect(owner).liquidate(params); + await assertSettledFor(liqLm, mkt, VUSDT, borrower, borrowBefore); + // The split router pulled the bStock via its spender, and no standing allowance is left behind. + const bStock = new ethers.Contract( + mkt.bStock.address, + [...ERC20_ABI, "function allowance(address,address) view returns (uint256)"], + owner, + ); + expect(await bStock.balanceOf(lmRouter.address)).to.be.gt(0); + expect(await bStock.allowance(liqLm.address, spender.address)).to.equal(0); + }); + it("two-hop CAKE debt, inventory mode: bStock->USDT (mock) -> CAKE (real PCS) at live slippage", async () => { await makeUnderwaterBorrower({ mkt, From 03bd5da60569b607adc26de51c91b4b37ef06219 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 12:59:28 +0530 Subject: [PATCH 39/78] fix: [L02] --- contracts/BStock/BStockLiquidator.sol | 14 ++++++-- contracts/BStock/IBStockLiquidator.sol | 7 ++++ tests/hardhat/BStockLiquidator.ts | 47 ++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index 0e67b123b..c58a603fd 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -29,7 +29,9 @@ import { IBStockLiquidator } from "./IBStockLiquidator.sol"; * OPTIONAL hop 2 (Native quotes bStock->USDT only) converts the USDT to the debt asset through a * second allowlisted router (an AMM/aggregator). Because seize and sell happen in the same tx there * is no price-drift window, and the realized debt-asset amount must clear `minOut` or the whole call - * reverts — the protocol never ends up holding the RFQ-only asset or the intermediate. + * reverts. On a full fill the protocol ends up holding only the debt asset; if a hop's router fills + * less than approved (e.g. a partial RFQ fill) any residual input token stays in the contract, is + * surfaced via `PartialSwapLeftover`, and is recoverable via `sweep`. * * Two funding modes share the same core (`_liquidate`): * - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance. @@ -303,12 +305,20 @@ contract BStockLiquidator is /// @dev One swap hop: approve the exact `amount` to the allowlisted `router`, forward the opaque /// calldata via a low-level call, then reset the approval to 0. The approval caps what the - /// router can pull; if the calldata sells less, the remainder stays as inventory. + /// router can pull; if the calldata sells less (e.g. a partially-filled RFQ quote), the + /// unconsumed remainder stays in the contract and is surfaced via `PartialSwapLeftover` so + /// operations can recover it with `sweep` — `minOut` still bounds the realized debt-asset + /// proceeds regardless. function _swap(IERC20Upgradeable token, address router, bytes memory data, uint256 amount) private { + uint256 balBefore = token.balanceOf(address(this)); token.forceApprove(router, amount); (bool ok, ) = router.call(data); if (!ok) revert SwapFailed(); token.forceApprove(router, 0); // never leave a standing approval + // `token` is the hop's INPUT (sold, never received), so its balance can only fall by what the + // router pulled. A shortfall means the router filled less than approved; emit the residual. + uint256 spent = balBefore - token.balanceOf(address(this)); + if (spent < amount) emit PartialSwapLeftover(address(token), amount - spent); } /** diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol index 8397e34a8..ce2d95933 100644 --- a/contracts/BStock/IBStockLiquidator.sol +++ b/contracts/BStock/IBStockLiquidator.sol @@ -62,6 +62,13 @@ interface IBStockLiquidator { /// @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(); diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index 51a91d768..fa04dafcb 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -187,6 +187,38 @@ describe("BStockLiquidator (atomic)", () => { }); }); + // A hop's router pulls the FIXED amountIn baked into the signed calldata, while the contract approves + // the actual measured seize. The off-chain SEIZE_BUFFER haircut signs for slightly LESS than the seize + // (so an upward price tick can't make the pull exceed the approval and revert), which means a small + // residual of the input token routinely stays in the contract. `PartialSwapLeftover` surfaces it. + describe("partial swap fill (PartialSwapLeftover)", () => { + const LEFTOVER = U("50"); // seize 5500, sign for 5450 -> 50 bStock left behind + + it("emits PartialSwapLeftover and strands the residual, which is then sweepable", async () => { + await usdt.mint(liq.address, REPAY); + // Contract approves the full measured seize (SEIZED); the signed calldata pulls SEIZED - LEFTOVER. + const pulled = SEIZED.sub(LEFTOVER); // 5450 -> proceeds 5450 USDT, clears the 5400 minOut + const p = params({ swapCalldata: swapCalldata(pulled, liq.address) }); + + await expect(liq.connect(owner).liquidate(p)) + .to.emit(liq, "PartialSwapLeftover") + .withArgs(bStock.address, LEFTOVER); + + // Residual bStock is retained (not silently lost) and recoverable via sweep. + expect(await bStock.balanceOf(liq.address)).to.equal(LEFTOVER); + await expect(liq.connect(owner).sweep(bStock.address, owner.address, LEFTOVER)) + .to.emit(liq, "Swept") + .withArgs(bStock.address, owner.address, LEFTOVER); + expect(await bStock.balanceOf(liq.address)).to.equal(0); + }); + + it("does not emit PartialSwapLeftover when the router consumes the full approval", async () => { + await usdt.mint(liq.address, REPAY); + await expect(liq.connect(owner).liquidate(params())).to.not.emit(liq, "PartialSwapLeftover"); + expect(await bStock.balanceOf(liq.address)).to.equal(0); + }); + }); + describe("flash mode", () => { it("flash-borrows the repay, settles atomically, repays principal + premium", async () => { await usdt.mint(vDebt.address, REPAY); // the vToken lends the principal @@ -340,6 +372,21 @@ describe("BStockLiquidator (atomic)", () => { await expect(liq.connect(owner).liquidate(p)).to.be.revertedWithCustomError(liq, "SwapFailed"); expect(await usdt.balanceOf(liq.address)).to.equal(U("2000")); // reverted, inventory intact }); + + it("emits PartialSwapLeftover for the intermediate when hop 2 pulls less than the hop-1 delta", async () => { + await btcb.mint(liq.address, REPAY); + const leftover = U("50"); + // Hop 1 fills fully (midDelta = SEIZED USDT); hop 2 pulls SEIZED - leftover, stranding intermediate USDT. + const p = twoHopParams({ swapCalldata2: ammSwapCalldata(SEIZED.sub(leftover), liq.address) }); + + await expect(liq.connect(owner).liquidate(p)) + .to.emit(liq, "PartialSwapLeftover") + .withArgs(usdt.address, leftover); + + expect(await usdt.balanceOf(liq.address)).to.equal(leftover); // intermediate residual retained + // Inventory nets to proceeds: start REPAY, repay REPAY, + (SEIZED - leftover) BTCB proceeds. + expect(await btcb.balanceOf(liq.address)).to.equal(OUT.sub(leftover)); // proceeds landed + }); }); // Native BNB debt. vBNB has no underlying() and its borrow is repaid in native BNB, so WBNB is the From ce0b179ee17b190197e652b822673b89a289bf9f Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 13:09:24 +0530 Subject: [PATCH 40/78] fix: [L03] --- contracts/BStock/BStockLiquidator.sol | 7 +++++++ tests/hardhat/BStockLiquidator.ts | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index c58a603fd..73610e258 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -181,6 +181,13 @@ contract BStockLiquidator is emit SweptNative(to, amount); } + /// @notice Disabled. This backstop custodies protocol capital (debt-asset inventory, native BNB) and + /// every admin function (`sweep`, `sweepNative`, `setOperator`, `setRouter`) is `onlyOwner`, + /// so renouncing ownership would permanently strand those funds and brick the contract. The + /// override is a no-op (matching the sibling {Liquidator}) so an accidental call cannot zero + /// the owner. Ownership is still transferable via the two-step `transferOwnership` flow. + function renounceOwnership() public override onlyOwner {} + // --------------------------------------------------------------------- // // INVENTORY mode // // --------------------------------------------------------------------- // diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index fa04dafcb..a18bd4219 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -658,6 +658,15 @@ describe("BStockLiquidator (atomic)", () => { expect(await usdt.balanceOf(owner.address)).to.equal(U("123")); expect(await usdt.balanceOf(liq.address)).to.equal(0); }); + + it("renounceOwnership is disabled (owner cannot brick the fund-custodying contract)", async () => { + await expect(liq.connect(stranger).renounceOwnership()).to.be.revertedWith("Ownable: caller is not the owner"); + // Owner call is a no-op: ownership is retained, admin surface stays live. + await liq.connect(owner).renounceOwnership(); + expect(await liq.owner()).to.equal(owner.address); + await usdt.mint(liq.address, U("1")); + await expect(liq.connect(owner).sweep(usdt.address, owner.address, U("1"))).to.emit(liq, "Swept"); + }); }); describe("deadline boundary", () => { From 355b16b63ab1ab5e3718b49ad8dac8062ca9a6e4 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 13:26:36 +0530 Subject: [PATCH 41/78] fix: [L04] --- scripts/bstock/atomic-liquidate.ts | 13 ++++++++++--- scripts/bstock/safe-fallback.ts | 9 ++++++++- tests/hardhat/Fork/BStockLiquidatorFork.ts | 3 +++ tests/hardhat/Fork/helpers/bstock.ts | 7 +++++-- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 7208677c7..98f5aeba4 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -8,7 +8,7 @@ * OFF-CHAIN half (precompute the seize, fetch the quotes with `from_address = the contract`) and then * calls `liquidate` (inventory mode) or `flashLiquidate` (Venus flash-loan mode). * - * 1. Comptroller.liquidateCalculateSeizeTokens(vDebt, vBStock, repay) -> seize vTokens + * 1. Comptroller.liquidateCalculateSeizeTokens(borrower, vDebt, vBStock, repay) -> seize vTokens * 2. seizeTokens * vBStock.exchangeRateStored() / 1e18 -> raw bStock (floor) * 3. Native firm-quote (bStock -> USDT) [+ AMM quote USDT -> debt] -> router(s) + calldata + out * 4. BStockLiquidator.liquidate / flashLiquidate(params) -> atomic settle @@ -67,7 +67,7 @@ const ERC20_ABI = [ ]; const COMPTROLLER_ABI = [ "function getAccountLiquidity(address) view returns (uint256,uint256,uint256)", - "function liquidateCalculateSeizeTokens(address,address,uint256) view returns (uint256,uint256)", + "function liquidateCalculateSeizeTokens(address,address,address,uint256) view returns (uint256,uint256)", "function treasuryPercent() view returns (uint256)", "function liquidatorContract() view returns (address)", "function getEffectiveLiquidationIncentive(address,address) view returns (uint256)", @@ -124,13 +124,20 @@ export async function atomicLiquidate(signer: Signer) { if (shortfall.eq(0)) throw new Error(`${borrower} has no shortfall — not liquidatable`); console.log(`borrower ${borrower} shortfall=${ethers.utils.formatEther(shortfall)} (USD-scaled)`); - // 1 + 2. precompute the exact seize so the quote amount matches what redeem() yields. + // 1 + 2. precompute the exact seize so the quote amount matches what redeem() yields. Use the + // borrower-aware 4-arg overload (reads the pool the borrower is actually in via + // getEffectiveLiquidationIncentive) — the same version vToken.liquidateBorrowFresh calls on-chain. + // The 3-arg overload always reads Core Pool params and diverges if the borrower has switched pools. const [seizeErr, seizeTokens]: BigNumber[] = await comptroller.liquidateCalculateSeizeTokens( + borrower, vDebt.address, vBStock.address, repay, ); if (!seizeErr.eq(0)) throw new Error(`liquidateCalculateSeizeTokens error ${seizeErr}`); + // A zero seize means the incentive resolved to 0 (e.g. bStock unlisted in the borrower's pool): + // surface it here rather than building a degenerate quote that reverts on-chain. + if (seizeTokens.eq(0)) throw new Error(`liquidateCalculateSeizeTokens returned 0 seize for ${borrower}`); const exchangeRate: BigNumber = await vBStock.exchangeRateStored(); const ONE = BigNumber.from(10).pow(18); diff --git a/scripts/bstock/safe-fallback.ts b/scripts/bstock/safe-fallback.ts index 41827c857..121f72d1d 100644 --- a/scripts/bstock/safe-fallback.ts +++ b/scripts/bstock/safe-fallback.ts @@ -69,7 +69,7 @@ const COMPTROLLER_ABI = [ "function closeFactorMantissa() view returns (uint256)", "function liquidationIncentiveMantissa() view returns (uint256)", "function liquidatorContract() view returns (address)", - "function liquidateCalculateSeizeTokens(address,address,uint256) view returns (uint256,uint256)", + "function liquidateCalculateSeizeTokens(address,address,address,uint256) view returns (uint256,uint256)", "function treasuryPercent() view returns (uint256)", "function getEffectiveLiquidationIncentive(address,address) view returns (uint256)", ]; @@ -138,12 +138,19 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { // --- seize math from on-chain truth --- const ONE = BigNumber.from(10).pow(18); + // Borrower-aware 4-arg overload (reads the borrower's actual pool via getEffectiveLiquidationIncentive), + // matching vToken.liquidateBorrowFresh on-chain. The 3-arg overload always reads Core Pool params and + // diverges if the borrower has switched pools, producing a stale redeem amount in the batch. const [seizeErr, seizeTokens]: BigNumber[] = await comptroller.liquidateCalculateSeizeTokens( + borrower, vDebt.address, vBStock.address, repay, ); if (!seizeErr.eq(0)) throw new Error(`liquidateCalculateSeizeTokens error code ${seizeErr}`); + // A zero seize means the incentive resolved to 0 (e.g. bStock unlisted in the borrower's pool): + // surface it here rather than baking a degenerate redeem amount into the batch. + if (seizeTokens.eq(0)) throw new Error(`liquidateCalculateSeizeTokens returned 0 seize for ${borrower}`); // The Venus Liquidator keeps a treasury cut of the liquidation BONUS (see // Liquidator._splitLiquidationIncentive), so the Safe is credited fewer vTokens than seizeTokens. diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts index df35c2cd3..dddaa3657 100644 --- a/tests/hardhat/Fork/BStockLiquidatorFork.ts +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -194,6 +194,7 @@ const test = () => { [TOK.USDT, TOK.CAKE], liq.address, P_CRASH, + B.CAKE, ); const params = { borrower: B.CAKE, @@ -238,6 +239,7 @@ const test = () => { [TOK.USDT, TOK.WBNB], liq.address, P_CRASH, + B.BNB, ); const params = { borrower: B.BNB, @@ -813,6 +815,7 @@ async function sweepOne( path, liq.address, P_CRASH, + borrower, ); params = { borrower, diff --git a/tests/hardhat/Fork/helpers/bstock.ts b/tests/hardhat/Fork/helpers/bstock.ts index 3fbbae994..0442170ba 100644 --- a/tests/hardhat/Fork/helpers/bstock.ts +++ b/tests/hardhat/Fork/helpers/bstock.ts @@ -62,7 +62,7 @@ const COMPTROLLER_ABI = [ "function getAccountLiquidity(address) view returns (uint256,uint256,uint256)", "function liquidatorContract() view returns (address)", "function closeFactorMantissa() view returns (uint256)", - "function liquidateCalculateSeizeTokens(address,address,uint256) view returns (uint256,uint256)", + "function liquidateCalculateSeizeTokens(address,address,address,uint256) view returns (uint256,uint256)", "function _supportMarket(address) returns (uint256)", "function setCollateralFactor(address,uint256,uint256) returns (uint256)", "function _setMarketSupplyCaps(address[],uint256[])", @@ -360,9 +360,12 @@ export async function buildTwoHopMockThenPcs( pathUsdtToDebt: string[], liqAddr: string, rate: BigNumber, + borrower: string, ): Promise<{ swapCalldata: string; swapCalldata2: string; expectedOut: BigNumber }> { const comptroller = new ethers.Contract(A.COMPTROLLER, COMPTROLLER_ABI, owner); - const [, seizeTokens] = await comptroller.liquidateCalculateSeizeTokens(vDebt, mkt.vBStock.address, repay); + // Borrower-aware 4-arg overload (reads the borrower's actual pool), matching + // vToken.liquidateBorrowFresh on-chain; the 3-arg overload always reads Core Pool params. + const [, seizeTokens] = await comptroller.liquidateCalculateSeizeTokens(borrower, vDebt, mkt.vBStock.address, repay); const xr: BigNumber = await mkt.vBStock.exchangeRateStored(); const grossBStock = seizeTokens.mul(xr).div(ONE); // bStock the seized vTokens redeem to (pre treasury cut) const usdtFromHop1 = grossBStock.mul(rate).div(ONE); // mock output at the quoted rate From a2d2043f4308253709b1992967e6d0b87acebbf2 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 13:33:32 +0530 Subject: [PATCH 42/78] fix: [L05] --- deploy/019-deploy-bstock-liquidator.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deploy/019-deploy-bstock-liquidator.ts b/deploy/019-deploy-bstock-liquidator.ts index 6f831c2bc..921ad19d0 100644 --- a/deploy/019-deploy-bstock-liquidator.ts +++ b/deploy/019-deploy-bstock-liquidator.ts @@ -65,6 +65,11 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // 2. setRouter(ammRouter, true) — hop-2 AMM/aggregator router(s) for non-USDT / BNB debt // 3. setOperator(operatorKey, true) — each liquidation-bot key (or each Safe signer) // These are onlyOwner, so they cannot run here (deployer is not the owner); ship them as a Safe batch. + // + // FLASH mode ONLY (skip if using INVENTORY mode): flashLiquidate calls Comptroller.executeFlashLoan, + // which reverts SenderNotAuthorizedForFlashLoan unless this contract is whitelisted. This is a + // SEPARATE, governance-gated step (NOT owner-callable) that must be scheduled as a VIP: + // 4. Comptroller.setWhiteListFlashLoanAccount(bStockLiquidator, true) — via the Comptroller admin return hre.network.live; // record as executed on a live network to prevent re-execution }; From 4d176db63038922337434104b9d67e4377b7942d Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 13:47:45 +0530 Subject: [PATCH 43/78] fix: [L06] --- contracts/test/BStockLiquidationMocks.sol | 11 ++++++++ scripts/bstock/atomic-liquidate.ts | 24 ++++++++++++++++++ tests/hardhat/BStockAtomicLiquidate.ts | 31 +++++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/contracts/test/BStockLiquidationMocks.sol b/contracts/test/BStockLiquidationMocks.sol index b227f1e47..1eff2881c 100644 --- a/contracts/test/BStockLiquidationMocks.sol +++ b/contracts/test/BStockLiquidationMocks.sol @@ -74,6 +74,17 @@ contract MockComptrollerLite { return (0, (repayAmount * liquidationIncentiveMantissa) / 1e18); } + /// @dev Borrower-aware 4-arg overload (the version vToken.liquidateBorrowFresh and the off-chain + /// scripts call). Same math here — the mock has a single pool — so both overloads agree. + function liquidateCalculateSeizeTokens( + address, + address, + address, + uint256 repayAmount + ) external view returns (uint256, uint256) { + return (0, (repayAmount * liquidationIncentiveMantissa) / 1e18); + } + /// @dev Read by the off-chain script to size the Venus Liquidator's bonus cut. function getEffectiveLiquidationIncentive(address, address) external view returns (uint256) { return liquidationIncentiveMantissa; diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 98f5aeba4..2aa431986 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -92,6 +92,16 @@ export async function atomicLiquidate(signer: Signer) { if (!Number.isFinite(seizeBufferPct) || seizeBufferPct < 0 || seizeBufferPct >= 100) { throw new Error(`SEIZE_BUFFER must be a percent in [0, 100), got "${process.env.SEIZE_BUFFER}"`); } + // Minimum remaining Native-quote TTL (seconds) required just before submitting the settle tx. The + // two-hop AMM round-trip and on-chain reads after the initial TTL check consume wall-clock, so the + // quote is re-verified against this margin to abort + refetch rather than burn gas on an on-chain + // DeadlineExpired revert. + const settleTtlMarginSec = Number(process.env.SETTLE_TTL_MARGIN || "10"); + if (!Number.isFinite(settleTtlMarginSec) || settleTtlMarginSec < 0) { + throw new Error( + `SETTLE_TTL_MARGIN must be a non-negative number of seconds, got "${process.env.SETTLE_TTL_MARGIN}"`, + ); + } const liquidator = new Contract(env("LIQUIDATOR"), LIQUIDATOR_ABI, signer); const borrower = ethers.utils.getAddress(env("BORROWER")); @@ -293,6 +303,20 @@ export async function atomicLiquidate(signer: Signer) { } } + // Re-verify the Native quote's remaining TTL immediately before submission. Everything since the + // initial TTL check — the two-hop AMM round-trip, the isRouter reads, the inventory check — consumes + // real wall-clock, so the quote may have drifted close to (or past) expiry. Abort + refetch here + // rather than relying solely on the on-chain DeadlineExpired backstop and wasting gas. Skipped when + // `deadline` is the sentinel (mock/fork path, which never expires). + if (!deadline.eq(ethers.constants.MaxUint256)) { + const remainingTtl = deadline.toNumber() - Math.floor(Date.now() / 1000); + if (remainingTtl < settleTtlMarginSec) { + throw new Error( + `Native quote TTL ${remainingTtl}s is below the ${settleTtlMarginSec}s safety margin before submit — refetch`, + ); + } + } + // 4. settle. const fn = mode === "flash" ? "flashLiquidate" : "liquidate"; console.log(`mode=${mode} -> ${fn}(...) minOut=${ethers.utils.formatUnits(minOut, debtDec)} ${debtSym}`); diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index 4559d1490..5ff523af0 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -37,6 +37,7 @@ const SCRIPT_ENV = [ "SLIPPAGE", "MIN_OUT_BUFFER", "SEIZE_BUFFER", + "SETTLE_TTL_MARGIN", "NATIVE_API_KEY", ] as const; @@ -165,6 +166,36 @@ describe("bStock atomic liquidation script", () => { await expect(atomicLiquidate(owner)).to.be.rejectedWith(/SEIZE_BUFFER/); }); + it("rejects an out-of-range SETTLE_TTL_MARGIN before quoting", async () => { + setEnv({ SETTLE_TTL_MARGIN: "-5" }); + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/SETTLE_TTL_MARGIN/); + }); + + it("aborts before submit when the Native quote TTL is below the safety margin", async () => { + await usdt.mint(liq.address, REPAY); // inventory so the guard, not a funding error, is what trips + // Real Native quote path (no MOCK_NATIVE): stub fetch to return a quote that expires in ~2s, below + // the default 10s margin, so the pre-submit TTL re-check aborts instead of relying on the on-chain + // DeadlineExpired backstop. + const realFetch = globalThis.fetch; + globalThis.fetch = (async () => ({ + json: async () => ({ + success: true, + recipient: liq.address, + amountIn: "0", + amountOut: OUT.toString(), + orders: [{ deadlineTimestamp: Math.floor(Date.now() / 1000) + 2 }], + txRequest: { target: router.address, calldata: swapAllCalldata(liq.address), value: "0" }, + }), + })) as unknown as typeof fetch; + + try { + setEnv({ MOCK_NATIVE: "", MOCK_OUT: "", NATIVE_API_KEY: "test-key" }); + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/safety margin/); + } finally { + globalThis.fetch = realFetch; + } + }); + it("flash mode: routes through flashLiquidate, repays principal + premium, keeps the rest", async () => { await usdt.mint(vDebt.address, REPAY); // the vToken lends the principal await comptroller.setFlashPremium(U("0.001")); // 0.1% premium From 4617af5461beb4b36409c657c6327b862f2218f4 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 13:50:47 +0530 Subject: [PATCH 44/78] fix: [I01] --- contracts/BStock/BStockLiquidator.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index 73610e258..ffca6961f 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -174,7 +174,9 @@ contract BStockLiquidator is } /// @inheritdoc IBStockLiquidator - function sweepNative(address to, uint256 amount) external override onlyOwner { + /// @dev `nonReentrant` is defense-in-depth only (the function is `onlyOwner`, snapshots no state, and + /// reads no balance after the native `.call`), added for consistency with the liquidation entrypoints. + function sweepNative(address to, uint256 amount) external override onlyOwner nonReentrant { ensureNonzeroAddress(to); (bool ok, ) = to.call{ value: amount }(""); if (!ok) revert NativeTransferFailed(); From d57fc0b15e3e49e5cbbdfb0f95239ccdec4a1b73 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 14:28:24 +0530 Subject: [PATCH 45/78] fix(bstock): harden setRouterSpender against misconfiguration Couple the spender lifecycle to the router allowlist: - setRouterSpender reverts unless the router is currently allowlisted - a non-zero spender must be a deployed contract (it receives a live exact-amount approval during _swap; an EOA is always a misconfig) - setRouter(router, false) clears any configured spender so a stale entry cannot silently reactivate on a later re-allowlist The spender was previously unvalidated: a fat-fingered address would silently receive the hop approval on every swap through that router. --- contracts/BStock/BStockLiquidator.sol | 16 +++++++++-- contracts/BStock/IBStockLiquidator.sol | 9 +++++++ tests/hardhat/BStockLiquidatorLiquidMesh.ts | 30 +++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index 354ec6d18..d6fb6531e 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -169,14 +169,26 @@ contract BStockLiquidator is function setRouter(address router, bool allowed) external override onlyOwner { ensureNonzeroAddress(router); isRouter[router] = allowed; + // De-allowlisting also clears any configured spender: a stale entry must not silently + // reactivate (with a possibly rotated-away spender) if the router is ever re-allowlisted. + if (!allowed && routerSpender[router] != address(0)) { + delete routerSpender[router]; + emit RouterSpenderSet(router, address(0)); + } emit RouterSet(router, allowed); } /// @inheritdoc IBStockLiquidator function setRouterSpender(address router, address spender) external override onlyOwner { - ensureNonzeroAddress(router); + // Couple the spender lifecycle to the allowlist: a spender only ever matters for an + // allowlisted router (`_swap` approves it right before the router call), so requiring + // `isRouter` here catches a fat-fingered router address instead of storing it silently. + if (!isRouter[router]) revert RouterNotAllowed(router); // `spender == address(0)` is allowed and clears the entry, reverting the router to - // approve-the-call-target (Native) behaviour. + // approve-the-call-target (Native) behaviour. A non-zero spender receives a live (exact-amount, + // same-tx) approval during `_swap`, so require it to be a deployed contract — an EOA spender is + // always a misconfiguration. + if (spender != address(0) && spender.code.length == 0) revert SpenderNotContract(spender); routerSpender[router] = spender; emit RouterSpenderSet(router, spender); } diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol index c6e44053e..8b5cd18c0 100644 --- a/contracts/BStock/IBStockLiquidator.sol +++ b/contracts/BStock/IBStockLiquidator.sol @@ -71,6 +71,10 @@ interface IBStockLiquidator { /// @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); @@ -108,6 +112,8 @@ interface IBStockLiquidator { 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; @@ -115,6 +121,9 @@ interface IBStockLiquidator { /// @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; diff --git a/tests/hardhat/BStockLiquidatorLiquidMesh.ts b/tests/hardhat/BStockLiquidatorLiquidMesh.ts index e4d542f21..4226e3b86 100644 --- a/tests/hardhat/BStockLiquidatorLiquidMesh.ts +++ b/tests/hardhat/BStockLiquidatorLiquidMesh.ts @@ -180,4 +180,34 @@ describe("BStockLiquidator + Liquid Mesh (separate spender)", () => { it("setRouterSpender is owner-only", async () => { await expect(liq.connect(stranger).setRouterSpender(lmRouter.address, spender.address)).to.be.reverted; }); + + it("setRouterSpender reverts for a router that is not allowlisted", async () => { + // `spender.address` is a deployed contract but was never `setRouter`'d — the spender lifecycle is + // coupled to the allowlist, so pointing it at a non-allowlisted router is rejected up front. + await expect(liq.connect(owner).setRouterSpender(spender.address, spender.address)) + .to.be.revertedWithCustomError(liq, "RouterNotAllowed") + .withArgs(spender.address); + }); + + it("setRouterSpender reverts when the spender is an EOA", async () => { + // The spender receives a live token approval during `_swap`; an address with no code can never be + // the settlement contract, so it is rejected as a misconfiguration. + await expect(liq.connect(owner).setRouterSpender(lmRouter.address, stranger.address)) + .to.be.revertedWithCustomError(liq, "SpenderNotContract") + .withArgs(stranger.address); + }); + + it("de-allowlisting a router clears its spender entry so it cannot silently reactivate", async () => { + await liq.connect(owner).setRouterSpender(lmRouter.address, spender.address); + + await expect(liq.connect(owner).setRouter(lmRouter.address, false)) + .to.emit(liq, "RouterSpenderSet") + .withArgs(lmRouter.address, ZERO); + expect(await liq.routerSpender(lmRouter.address)).to.equal(ZERO); + + // Re-allowlisting does NOT bring the old spender back — it must be set again explicitly. + await liq.connect(owner).setRouter(lmRouter.address, true); + expect(await liq.routerSpender(lmRouter.address)).to.equal(ZERO); + await expect(liq.connect(owner).liquidate(params())).to.be.revertedWithCustomError(liq, "SwapFailed"); + }); }); From e53cd8a13353a3b314d62db912c5d73758de6e8e Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 14:31:12 +0530 Subject: [PATCH 46/78] fix(bstock): reconcile indicative LM quotes against firm quotes after build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Liquid Mesh's /quote number is indicative — the separately built /swap order only guarantees its own floor — while Native's firm quote executes at exactly its amountOut. Comparing the two raw outs let an optimistic LM quote win the hop-1 selection and then fill worse than Native guaranteed. Tag each SourceQuote as firm/indicative and expose the built order's builtFloor from build(); when an indicative winner's floor undercuts the best firm loser, execute the firm quote instead. --- scripts/bstock/atomic-liquidate.ts | 24 ++++++++++++++++++++++-- scripts/bstock/lib/sources.ts | 15 ++++++++++++++- tests/hardhat/BStockAtomicLiquidate.ts | 18 ++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 411b8c049..5ba28557b 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -129,8 +129,28 @@ async function pickHop1Source(args: QuoteArgs): Promise { } // Winner = highest USDT out. Only its build() runs. - const winner = ok.reduce((best, cur) => (cur.quote.out.gt(best.quote.out) ? cur : best)); - const built = await winner.quote.build(); + let winner = ok.reduce((best, cur) => (cur.quote.out.gt(best.quote.out) ? cur : best)); + let built = await winner.quote.build(); + + // Reconcile an INDICATIVE winner against the best FIRM loser. An indicative `out` (LM `/quote`) is + // not binding — the built order only guarantees `builtFloor` — while a firm quote (Native) executes + // at exactly its `out`. If the winner's real floor undercuts what the firm loser guaranteed, the + // comparison was won on optimism: execute the firm quote instead. + if (!winner.quote.firm) { + const firmLosers = ok.filter(o => o !== winner && o.quote.firm); + if (firmLosers.length > 0) { + const bestFirm = firmLosers.reduce((best, cur) => (cur.quote.out.gt(best.quote.out) ? cur : best)); + if (built.builtFloor.lt(bestFirm.quote.out)) { + console.log( + `hop-1 reconcile: ${winner.name} built floor ${ethers.utils.formatUnits(built.builtFloor, 18)} < ` + + `${bestFirm.name} firm ${ethers.utils.formatUnits(bestFirm.quote.out, 18)} USDT — using ${bestFirm.name}`, + ); + winner = bestFirm; + built = await bestFirm.quote.build(); + } + } + } + return { source: winner.name, router: built.router, diff --git a/scripts/bstock/lib/sources.ts b/scripts/bstock/lib/sources.ts index 993a23580..981f6b7f4 100644 --- a/scripts/bstock/lib/sources.ts +++ b/scripts/bstock/lib/sources.ts @@ -33,6 +33,7 @@ export interface BuiltRoute { router: string; // swap call target (allowlist this on the contract) calldata: string; // opaque blob forwarded by `_swap` deadline: BigNumber; // unix seconds — the quote's on-chain expiry + builtFloor: BigNumber; // the built order's own worst-case out — what the fill is actually bound by } /** @@ -40,9 +41,16 @@ export interface BuiltRoute { * only called for the winner — so a source that needs a second round-trip to build (e.g. Liquid Mesh's * `/swap`) does not pay it when it loses. Native already holds the calldata from its quote, so its * `build()` just returns it (no extra network call). + * + * `firm` says whether `out` is BINDING for the executed fill (Native firm-quote: yes) or merely + * indicative (Liquid Mesh `/quote`: the separately built `/swap` order may fill lower, down to the + * built order's own floor). The winner selection compares `out`s, so an indicative winner is + * re-checked after build against the best firm loser (see pickHop1Source) — otherwise an optimistic + * indicative quote could beat a firm one and then fill worse than the firm one guaranteed. */ export interface SourceQuote { out: BigNumber; + firm: boolean; build: () => Promise; } @@ -69,13 +77,16 @@ const nativeSource: QuoteSource = { amount: a.humanAmount, slippage: a.slippage, }); + const out = BigNumber.from(q.amountOut); return { - out: BigNumber.from(q.amountOut), + out, + firm: true, // MM-signed firm quote: `amountOut` is the executed fill, not an estimate // Firm-quote already carries the executable txRequest — build is immediate, no re-fetch. build: async () => ({ router: q.txRequest.target, calldata: q.txRequest.calldata, deadline: BigNumber.from(quoteDeadline(q)), + builtFloor: out, // firm: the quote IS the floor }), }; }, @@ -98,6 +109,7 @@ const liquidMeshSource: QuoteSource = { const out = BigNumber.from(quote.outputAmount); return { out, + firm: false, // `/quote` is indicative; the built `/swap` order may fill lower, down to `builtFloor` // `/swap` (disableSimulate:true) is only fetched if Liquid Mesh wins — avoids a wasted order build. build: async () => { // The order's own floor (`outputAmount`); LM also applies `slippageBps`, so the effective on-chain @@ -119,6 +131,7 @@ const liquidMeshSource: QuoteSource = { router: ethers.utils.getAddress(swap.callMsg.to), calldata: swap.callMsg.data, deadline: BigNumber.from(swap.expiryTimestamp), + builtFloor: minFloor, // the built order fills no lower than the floor we asked of it }; }, }; diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index b51719451..53fd21067 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -395,6 +395,24 @@ describe("bStock atomic liquidation script", () => { expect(await usdt.balanceOf(liq.address)).to.equal(OUT); }); + it("SOURCE=auto: falls back to the firm Native quote when the LM built floor undercuts it", async () => { + await usdt.mint(liq.address, REPAY); + // LM's INDICATIVE quote (5500) beats Native's FIRM 5490, so LM wins the comparison — but its built + // order only guarantees out*(1-slippage) = 5472.5 (< 5490 firm). The post-build reconcile must + // detect the regression and execute the Native quote instead. + const real = stubFetch({ nativeOut: U("5490"), lmOut: OUT, lmCalldata: lmSwapAll() }); + try { + lmEnv({ SOURCE: "auto" }); // default SLIPPAGE 0.5% + await atomicLiquidate(owner); + } finally { + globalThis.fetch = real; + } + + expect(await bStock.balanceOf(router.address)).to.equal(SEIZED); // Native sold it, not LM + expect(await bStock.balanceOf(lmRouter.address)).to.equal(0); + expect(await usdt.balanceOf(liq.address)).to.equal(OUT); + }); + it("SOURCE=liquidmesh: forces Liquid Mesh even when Native quotes higher", async () => { await usdt.mint(liq.address, REPAY); const real = stubFetch({ nativeOut: U("9000"), lmOut: OUT, lmCalldata: lmSwapAll() }); // Native higher, but forced LM From 9b9c7a5c020ea3d0989a3df45f1f47b1358c0c31 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 14:32:50 +0530 Subject: [PATCH 47/78] fix(bstock): enforce a minimum TTL on built Liquid Mesh orders LM RFQ orders are short-lived; an order already too tight to survive signing + submission + inclusion would only fail on-chain with DeadlineExpired, mid-incident. Abort at build time instead when fewer than LM_MIN_TTL seconds (default 15) remain on the order. --- scripts/bstock/README.md | 1 + scripts/bstock/atomic-liquidate.ts | 2 ++ scripts/bstock/lib/sources.ts | 14 ++++++++++++++ tests/hardhat/BStockAtomicLiquidate.ts | 26 +++++++++++++++++++++++--- 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/scripts/bstock/README.md b/scripts/bstock/README.md index d4da3d6fd..4a00f4023 100644 --- a/scripts/bstock/README.md +++ b/scripts/bstock/README.md @@ -95,6 +95,7 @@ call `liquidate`/`flashLiquidate`. | `SOURCE` | | `auto` | Hop-1 source: `auto` (price both, take higher) / `native` / `liquidmesh` | | `LM_API_KEY` | | | Liquid Mesh API key (required for `liquidmesh`/`auto`) | | `LM_PRIVATE_KEY_SEED` | | | Liquid Mesh Ed25519 seed, base64url (required for `liquidmesh`/`auto`) | +| `LM_MIN_TTL` | | `15` | Min seconds left on the LM order at build time, else abort (LM RFQ orders are short-lived; the on-chain deadline still enforces the real expiry) | | `DRY_RUN` | | | `1` → callStatic only, sends nothing | | `SLIPPAGE` | | `0.5` | Native/LM slippage % | | `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) | diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 5ba28557b..4d15ceb47 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -41,6 +41,8 @@ * (e.g. "native", "liquidmesh", "native,liquidmesh"). Liquid Mesh needs LM_API_KEY + * LM_PRIVATE_KEY_SEED, and the liquidator must have `setRouterSpender(LM_ROUTER, * LM_SPENDER)` set (separate puller). New sources = one adapter in lib/sources.ts. + * LM_MIN_TTL min seconds left on a built Liquid Mesh order, else abort pre-submit (default 15); + * LM RFQ orders are short-lived and an already-tight order would revert on-chain * SLIPPAGE Native/LM slippage %, default 0.5 * MIN_OUT_BUFFER extra haircut on minOut beyond slippage, default 0.5 (%) * SEIZE_BUFFER haircut on the QUOTED seize so a small oracle uptick can't make the router pull more diff --git a/scripts/bstock/lib/sources.ts b/scripts/bstock/lib/sources.ts index 981f6b7f4..d3720fcd8 100644 --- a/scripts/bstock/lib/sources.ts +++ b/scripts/bstock/lib/sources.ts @@ -125,6 +125,20 @@ const liquidMeshSource: QuoteSource = { routePlans: quote.routePlans, slippageBps: Math.round(a.slippage * 100), }); + // LM RFQ orders are short-lived. The contract enforces the expiry on-chain (DeadlineExpired), + // but an order that is ALREADY too tight to survive signing + submission + inclusion should be + // rejected here, before the settle tx is even built — a mid-incident on-chain revert is the + // worst place to discover it. `LM_MIN_TTL` (seconds, default 15) is the required margin. + const minTtl = Number(process.env.LM_MIN_TTL || "15"); + if (!Number.isFinite(minTtl) || minTtl < 0) { + throw new Error(`LM_MIN_TTL must be a non-negative number of seconds, got "${process.env.LM_MIN_TTL}"`); + } + const ttl = swap.expiryTimestamp - Math.floor(Date.now() / 1000); + if (ttl < minTtl) { + throw new Error( + `Liquid Mesh order TTL ${ttl}s < LM_MIN_TTL ${minTtl}s — too tight to submit; refetch immediately before sending`, + ); + } return { // Use the router LM actually wants called (`callMsg.to`); it is `LM_ROUTER` today, but relying on // the response keeps us correct if LM rotates it. The on-chain `isRouter` allowlist still gates it. diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index 53fd21067..8045d1d20 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -291,8 +291,9 @@ describe("bStock atomic liquidation script", () => { // A response object exposing both `.json()` (Native lib) and `.text()` (Liquid Mesh lib). const resp = (obj: any) => ({ status: 200, json: async () => obj, text: async () => JSON.stringify(obj) }); - // Stub Native firm-quote + LM /quote + LM /swap. `lmCalldata` is executed on-chain against `lmRouter`. - function stubFetch(opts: { nativeOut: BigNumberish; lmOut: BigNumberish; lmCalldata: string }) { + // Stub Native firm-quote + LM /quote + LM /swap. `lmCalldata` is executed on-chain against `lmRouter`; + // `lmExpiry` overrides the /swap order expiry (default: generous, see below). + function stubFetch(opts: { nativeOut: BigNumberish; lmOut: BigNumberish; lmCalldata: string; lmExpiry?: number }) { const real = globalThis.fetch; globalThis.fetch = (async (url: unknown, init?: any) => { const u = String(url); @@ -329,7 +330,7 @@ describe("bStock atomic liquidation script", () => { orderId: "1", // Generous expiry: the hardhat chain clock can run ahead of wall-clock after prior tests, so a // short TTL would trip DeadlineExpired. (Real LM orders are short-lived; the contract enforces it.) - expiryTimestamp: Math.floor(Date.now() / 1000) + 3600, + expiryTimestamp: opts.lmExpiry ?? Math.floor(Date.now() / 1000) + 3600, }, }); } @@ -427,6 +428,25 @@ describe("bStock atomic liquidation script", () => { expect(await usdt.balanceOf(liq.address)).to.equal(OUT); }); + it("rejects a built Liquid Mesh order whose TTL is below LM_MIN_TTL", async () => { + await usdt.mint(liq.address, REPAY); + // The built order expires 5s from now — under the 15s default margin. The guard must abort + // BEFORE the settle tx (an already-tight order would otherwise revert DeadlineExpired on-chain). + const real = stubFetch({ + nativeOut: U("5400"), + lmOut: OUT, + lmCalldata: lmSwapAll(), + lmExpiry: Math.floor(Date.now() / 1000) + 5, + }); + try { + lmEnv({ SOURCE: "liquidmesh" }); + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/TTL .*LM_MIN_TTL/); + } finally { + globalThis.fetch = real; + } + expect(await bStock.balanceOf(lmRouter.address)).to.equal(0); // nothing settled + }); + it("rejects an unknown SOURCE name", async () => { await usdt.mint(liq.address, REPAY); lmEnv({ SOURCE: "nope" }); From 5c9cab259171c411dac01a7fad270d31eb92ae04 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 14:34:55 +0530 Subject: [PATCH 48/78] refactor(bstock): type LM API responses and validate the signing seed Replace the two any-typed JSON parses with a typed LmResponse envelope, and reject an LM_PRIVATE_KEY_SEED that does not decode to exactly 32 bytes with a legible error instead of the cryptic createPrivateKey DER failure. --- scripts/bstock/lib/liquidmesh.ts | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/scripts/bstock/lib/liquidmesh.ts b/scripts/bstock/lib/liquidmesh.ts index 00586222a..ea09f3da0 100644 --- a/scripts/bstock/lib/liquidmesh.ts +++ b/scripts/bstock/lib/liquidmesh.ts @@ -81,6 +81,21 @@ export interface LmSwapParams extends LmQuoteParams { disableSimulate?: boolean; // default true — skip LM's off-chain transferFrom pre-check (see header) } +/** Common LM response envelope: `{code, msg, data}` with `code === 0` on success. */ +interface LmResponse { + code: number; + msg?: string; + data?: T; +} + +/** Raw `/swap` payload before normalization (`expiryTimestamp` arrives as number or string). */ +interface LmSwapData { + chainId: string; + callMsg: LmCallMsg; + orderId: string; + expiryTimestamp: number | string; +} + function host(): string { return process.env.LM_API_HOST || DEFAULT_HOST; } @@ -100,6 +115,11 @@ function signingKey() { ); } const seed = Buffer.from(seedB64url.replace(/-/g, "+").replace(/_/g, "/"), "base64"); + // Fail with a legible message instead of the cryptic createPrivateKey DER error a wrong-length + // seed would otherwise produce mid-incident. + if (seed.length !== 32) { + throw new Error(`LM_PRIVATE_KEY_SEED must decode to exactly 32 bytes (Ed25519 seed), got ${seed.length}`); + } // PKCS#8 DER prefix for an Ed25519 raw 32-byte seed. const der = Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), seed]); return createPrivateKey({ key: der, format: "der", type: "pkcs8" }); @@ -140,9 +160,9 @@ export async function getQuote(p: LmQuoteParams): Promise { `&amount=${p.amountWei}&userAddress=${p.userAddress}`; const res = await fetch(`${host()}${path}`, { headers: headers("GET", path) }); const text = await res.text(); - let body: any; + let body: LmResponse; try { - body = JSON.parse(text); + body = JSON.parse(text) as LmResponse; } catch { throw new Error(`Liquid Mesh /quote non-JSON (HTTP ${res.status}): ${text.slice(0, 200)}`); } @@ -151,7 +171,7 @@ export async function getQuote(p: LmQuoteParams): Promise { `Liquid Mesh /quote error (HTTP ${res.status}) ${body.code}: ${body.msg} (${p.tokenIn}->${p.tokenOut})`, ); } - return body.data as LmQuote; + return body.data; } /** @@ -179,9 +199,9 @@ export async function buildSwap(p: LmSwapParams): Promise { }); const res = await fetch(`${host()}${path}`, { method: "POST", headers: headers("POST", path, body), body }); const text = await res.text(); - let j: any; + let j: LmResponse; try { - j = JSON.parse(text); + j = JSON.parse(text) as LmResponse; } catch { throw new Error(`Liquid Mesh /swap non-JSON (HTTP ${res.status}): ${text.slice(0, 200)}`); } From 18fce770efb7df3aecb0daf20f321829f280b579 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 14:41:57 +0530 Subject: [PATCH 49/78] docs(bstock): document hop-1 reconcile and setRouterSpender ordering Runbook lagged two behavior changes: - indicative LM winner is reconciled against the best firm quote after build; operators see an expected `hop-1 reconcile:` source switch - setRouterSpender now requires an allowlisted router and a contract spender; de-allowlisting clears the spender entry --- scripts/bstock/README.md | 42 +++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/scripts/bstock/README.md b/scripts/bstock/README.md index 4a00f4023..fa22c61dd 100644 --- a/scripts/bstock/README.md +++ b/scripts/bstock/README.md @@ -25,6 +25,10 @@ Pick **Atomic** first. Fall back to **Safe** only if the Native quote path fails - `setRouterSpender(LM_ROUTER, LM_SPENDER)` **if using Liquid Mesh** — it pulls the input token through a separate spender contract, distinct from the call target. Without it the swap reverts `SwapFailed`. (Native needs no spender entry — its call target is the puller.) + **Order is enforced on-chain**: the router must already be allowlisted (`setRouter` first) or the call + reverts `RouterNotAllowed`, and a non-zero spender must be a deployed contract (`SpenderNotContract`). + Note `setRouter(router, false)` also clears the router's spender — re-allowlisting later means running + `setRouterSpender` again. - `setOperator(caller, true)` for the account that submits `liquidate` / `flashLiquidate`. - **Funding** for `MODE=inventory`: the liquidator must hold ≥ `REPAY_AMOUNT` of the debt asset. `MODE=flash` borrows instead (no pre-funding). @@ -35,6 +39,12 @@ Hop-1 sources live in a registry — `scripts/bstock/lib/sources.ts` — that th prices every selected source and takes the higher out. `SOURCE=auto` (default) uses every source whose creds are present; `SOURCE=native,liquidmesh` (or a single name) restricts to a subset. +Winner selection has one guard: a Liquid Mesh quote is **indicative** (`/quote`), while Native's is +**firm** (MM-signed — executes at exactly the quoted amount). If Liquid Mesh wins the comparison, the +script builds its order and re-checks the order's own worst-case fill (its floor) against the best firm +quote it beat. If the floor is below what the firm quote guaranteed, the script logs +`hop-1 reconcile: ...` and executes the firm quote instead — an expected source switch, not an error. + Both current sources quote bStock→USDT. Liquid Mesh re-serves the same `rfq_native` book plus `rfq_neptune` (and AMM pools for thin names), so at common sizes it matches or marginally beats Native and reaches deeper on some tails — but neither is uniformly deeper. @@ -84,23 +94,23 @@ call `liquidate`/`flashLiquidate`. ### Env -| Var | Req | Default | Notes | -| --------------------- | --- | ----------- | ------------------------------------------------------------------------ | -| `LIQUIDATOR` | ✓ | | Deployed `BStockLiquidator` address | -| `BORROWER` | ✓ | | Account to liquidate (must have shortfall) | -| `VBSTOCK` | ✓ | | bStock collateral market (e.g. vTSLAB) | -| `VDEBT` | ✓ | | Borrowed market to repay (e.g. vUSDT) | -| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | -| `MODE` | | `inventory` | `inventory` (own funds) or `flash` (Venus flash-loan) | -| `SOURCE` | | `auto` | Hop-1 source: `auto` (price both, take higher) / `native` / `liquidmesh` | -| `LM_API_KEY` | | | Liquid Mesh API key (required for `liquidmesh`/`auto`) | -| `LM_PRIVATE_KEY_SEED` | | | Liquid Mesh Ed25519 seed, base64url (required for `liquidmesh`/`auto`) | +| Var | Req | Default | Notes | +| --------------------- | --- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| `LIQUIDATOR` | ✓ | | Deployed `BStockLiquidator` address | +| `BORROWER` | ✓ | | Account to liquidate (must have shortfall) | +| `VBSTOCK` | ✓ | | bStock collateral market (e.g. vTSLAB) | +| `VDEBT` | ✓ | | Borrowed market to repay (e.g. vUSDT) | +| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | +| `MODE` | | `inventory` | `inventory` (own funds) or `flash` (Venus flash-loan) | +| `SOURCE` | | `auto` | Hop-1 source: `auto` (price both, take higher) / `native` / `liquidmesh` | +| `LM_API_KEY` | | | Liquid Mesh API key (required for `liquidmesh`/`auto`) | +| `LM_PRIVATE_KEY_SEED` | | | Liquid Mesh Ed25519 seed, base64url (required for `liquidmesh`/`auto`) | | `LM_MIN_TTL` | | `15` | Min seconds left on the LM order at build time, else abort (LM RFQ orders are short-lived; the on-chain deadline still enforces the real expiry) | -| `DRY_RUN` | | | `1` → callStatic only, sends nothing | -| `SLIPPAGE` | | `0.5` | Native/LM slippage % | -| `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) | -| `AMM_PROVIDER` | | `kyberswap` | Hop-2 route for non-USDT debt: `kyberswap` / `openocean` / `pcsv2` | -| `WBNB_ADDR` | | BSC WBNB | Only for a vBNB debt market (native BNB auto-detected; contract unwraps) | +| `DRY_RUN` | | | `1` → callStatic only, sends nothing | +| `SLIPPAGE` | | `0.5` | Native/LM slippage % | +| `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) | +| `AMM_PROVIDER` | | `kyberswap` | Hop-2 route for non-USDT debt: `kyberswap` / `openocean` / `pcsv2` | +| `WBNB_ADDR` | | BSC WBNB | Only for a vBNB debt market (native BNB auto-detected; contract unwraps) | _Fork/local testing:_ `MOCK_NATIVE="router:calldata"` (hop 1), `MOCK_AMM` (hop 2), `MOCK_OUT` (final debt out), `IMPERSONATE=0x..` to run as an operator. From 510c9e867609c67df963cb882c16ce0f7ad51715 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 15:01:52 +0530 Subject: [PATCH 50/78] docs(bstock): add script decision flowchart to runbook Zero-context operators had to infer the run order from scattered hints; give them a mermaid decision tree (smoke -> dry-run -> send, Safe fallback branches) plus rules of thumb up front. --- scripts/bstock/README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/scripts/bstock/README.md b/scripts/bstock/README.md index fa22c61dd..bb9242e68 100644 --- a/scripts/bstock/README.md +++ b/scripts/bstock/README.md @@ -11,6 +11,32 @@ one shared goal: repay a borrower's debt, seize their bStock, and offload it. Pick **Atomic** first. Fall back to **Safe** only if the Native quote path fails. +## Which script? — start here + +```mermaid +flowchart TD + A([bStock borrower in shortfall]) --> B["1: native-smoke.ts
read-only, ~10s — is the quote path alive?"] + B --> C{Quote path?} + C -- "Native live" --> D["2: atomic-liquidate.ts
with DRY_RUN=1"] + C -- "Native down,
LM creds present" --> E["2: atomic-liquidate.ts
SOURCE=liquidmesh DRY_RUN=1"] + C -- "both RFQ sources dead
(halt / weekend / thin depth)" --> F["3: safe-fallback.ts
writes Safe batch JSON"] + D --> G{Dry-run passes?} + E --> G + G -- yes --> H["re-run WITHOUT DRY_RUN=1
→ liquidation sent, done"] + G -- "no, and can't fix fast" --> F + F --> I["Safe → Transaction Builder →
load JSON, sign, execute;
raw bStock ships to CEX"] +``` + +Rules of thumb: + +- **Always start with the smoke test** — it is read-only and tells you which branch you are on. +- **Never send without a passing dry-run** (`DRY_RUN=1` `callStatic`s the exact settle). +- **Atomic beats Safe whenever any RFQ source answers**: atomic is one tx at a firm price now; the + Safe path needs signer quorum and holds raw bStock (price risk) until finance offloads it on the CEX. +- **Don't debug mid-incident.** If the atomic dry-run keeps reverting and the cause isn't obvious in + minutes, switch to the Safe fallback instead of burning the liquidation window. +- `verify-lm-fork.ts` is a **dev-time** fork check (proves LM calldata executes) — never part of an incident. + --- ## Prerequisites From 15ac30de4784e4aa4625f33c049bdff22827b17b Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 16:25:33 +0530 Subject: [PATCH 51/78] docs(bstock): fix runbook env table and swap mermaid for SVG --- scripts/bstock/README.md | 50 +++++++--------- scripts/bstock/liquidation-flowchart.svg | 74 ++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 30 deletions(-) create mode 100644 scripts/bstock/liquidation-flowchart.svg diff --git a/scripts/bstock/README.md b/scripts/bstock/README.md index bb9242e68..b25fd02c5 100644 --- a/scripts/bstock/README.md +++ b/scripts/bstock/README.md @@ -13,19 +13,7 @@ Pick **Atomic** first. Fall back to **Safe** only if the Native quote path fails ## Which script? — start here -```mermaid -flowchart TD - A([bStock borrower in shortfall]) --> B["1: native-smoke.ts
read-only, ~10s — is the quote path alive?"] - B --> C{Quote path?} - C -- "Native live" --> D["2: atomic-liquidate.ts
with DRY_RUN=1"] - C -- "Native down,
LM creds present" --> E["2: atomic-liquidate.ts
SOURCE=liquidmesh DRY_RUN=1"] - C -- "both RFQ sources dead
(halt / weekend / thin depth)" --> F["3: safe-fallback.ts
writes Safe batch JSON"] - D --> G{Dry-run passes?} - E --> G - G -- yes --> H["re-run WITHOUT DRY_RUN=1
→ liquidation sent, done"] - G -- "no, and can't fix fast" --> F - F --> I["Safe → Transaction Builder →
load JSON, sign, execute;
raw bStock ships to CEX"] -``` +![bStock liquidation decision flowchart: native-smoke -> atomic-liquidate (dry-run then send) with Liquid Mesh when Native is down, falling back to the Safe multisig batch when both RFQ sources are dead](liquidation-flowchart.svg) Rules of thumb: @@ -120,23 +108,25 @@ call `liquidate`/`flashLiquidate`. ### Env -| Var | Req | Default | Notes | -| --------------------- | --- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| `LIQUIDATOR` | ✓ | | Deployed `BStockLiquidator` address | -| `BORROWER` | ✓ | | Account to liquidate (must have shortfall) | -| `VBSTOCK` | ✓ | | bStock collateral market (e.g. vTSLAB) | -| `VDEBT` | ✓ | | Borrowed market to repay (e.g. vUSDT) | -| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | -| `MODE` | | `inventory` | `inventory` (own funds) or `flash` (Venus flash-loan) | -| `SOURCE` | | `auto` | Hop-1 source: `auto` (price both, take higher) / `native` / `liquidmesh` | -| `LM_API_KEY` | | | Liquid Mesh API key (required for `liquidmesh`/`auto`) | -| `LM_PRIVATE_KEY_SEED` | | | Liquid Mesh Ed25519 seed, base64url (required for `liquidmesh`/`auto`) | -| `LM_MIN_TTL` | | `15` | Min seconds left on the LM order at build time, else abort (LM RFQ orders are short-lived; the on-chain deadline still enforces the real expiry) | -| `DRY_RUN` | | | `1` → callStatic only, sends nothing | -| `SLIPPAGE` | | `0.5` | Native/LM slippage % | -| `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) | -| `AMM_PROVIDER` | | `kyberswap` | Hop-2 route for non-USDT debt: `kyberswap` / `openocean` / `pcsv2` | -| `WBNB_ADDR` | | BSC WBNB | Only for a vBNB debt market (native BNB auto-detected; contract unwraps) | +| Var | Req | Default | Notes | +| --------------------- | --- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `LIQUIDATOR` | ✓ | | Deployed `BStockLiquidator` address | +| `BORROWER` | ✓ | | Account to liquidate (must have shortfall) | +| `VBSTOCK` | ✓ | | bStock collateral market (e.g. vTSLAB) | +| `VDEBT` | ✓ | | Borrowed market to repay (e.g. vUSDT) | +| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | +| `NATIVE_API_KEY` | | | Native Swap API key (required for `native`/`auto`) | +| `MODE` | | `inventory` | `inventory` (own funds) or `flash` (Venus flash-loan) | +| `SOURCE` | | `auto` | Hop-1 source: `auto` (price all available, take higher) / `native` / `liquidmesh` / comma-subset (e.g. `native,liquidmesh`) | +| `LM_API_KEY` | | | Liquid Mesh API key (required for `liquidmesh`/`auto`) | +| `LM_PRIVATE_KEY_SEED` | | | Liquid Mesh Ed25519 seed, base64url (required for `liquidmesh`/`auto`) | +| `LM_MIN_TTL` | | `15` | Min seconds left on the LM order at build time, else abort (LM RFQ orders are short-lived; the on-chain deadline still enforces the real expiry) | +| `DRY_RUN` | | | `1` → callStatic only, sends nothing | +| `SLIPPAGE` | | `0.5` | Native/LM slippage % | +| `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) | +| `SEIZE_BUFFER` | | `0.1` | Haircut on the QUOTED seize (%) so an oracle uptick before inclusion can't make the router pull more bStock than was seized (→ `SwapFailed`); the unsold remainder stays as bStock inventory (sweepable) | +| `AMM_PROVIDER` | | `kyberswap` | Hop-2 route for non-USDT debt: `kyberswap` / `openocean` / `pcsv2` | +| `WBNB_ADDR` | | BSC WBNB | Only for a vBNB debt market (native BNB auto-detected; contract unwraps) | _Fork/local testing:_ `MOCK_NATIVE="router:calldata"` (hop 1), `MOCK_AMM` (hop 2), `MOCK_OUT` (final debt out), `IMPERSONATE=0x..` to run as an operator. diff --git a/scripts/bstock/liquidation-flowchart.svg b/scripts/bstock/liquidation-flowchart.svg new file mode 100644 index 000000000..e9c3bb820 --- /dev/null +++ b/scripts/bstock/liquidation-flowchart.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + RFQ alive + + + + yes + + + no · can't fix fast + + + both RFQ dead + + + + + + bStock borrower in shortfall + + + + 1 · native-smoke.ts + read-only, ~10s — is the quote path alive? + + + + Quote path? + + + + 2 · atomic-liquidate.ts (DRY_RUN=1) + prices Native + Liquid Mesh, settles the winner + SOURCE=liquidmesh if Native is down + + + + Dry-run passes? + + + + re-run WITHOUT DRY_RUN=1 + → liquidation sent ✓ + + + + 3 · safe-fallback.ts + writes Safe batch JSON (no send) + + + + Safe → Transaction Builder + load JSON, review, sign, execute + raw bStock ships to CEX for offload + From aba7007ebd44c8a4bbc214bf4495a617ac1b11df Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 17:57:28 +0530 Subject: [PATCH 52/78] docs(bstock): clarify renounce and swap-underflow invariants --- contracts/BStock/BStockLiquidator.sol | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index ffca6961f..4352e320f 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -186,8 +186,8 @@ contract BStockLiquidator is /// @notice Disabled. This backstop custodies protocol capital (debt-asset inventory, native BNB) and /// every admin function (`sweep`, `sweepNative`, `setOperator`, `setRouter`) is `onlyOwner`, /// so renouncing ownership would permanently strand those funds and brick the contract. The - /// override is a no-op (matching the sibling {Liquidator}) so an accidental call cannot zero - /// the owner. Ownership is still transferable via the two-step `transferOwnership` flow. + /// override is an `onlyOwner` no-op: an accidental owner call cannot zero the owner, and a + /// non-owner call reverts. Ownership is still transferable via the two-step `transferOwnership` flow. function renounceOwnership() public override onlyOwner {} // --------------------------------------------------------------------- // @@ -324,8 +324,10 @@ contract BStockLiquidator is (bool ok, ) = router.call(data); if (!ok) revert SwapFailed(); token.forceApprove(router, 0); // never leave a standing approval - // `token` is the hop's INPUT (sold, never received), so its balance can only fall by what the - // router pulled. A shortfall means the router filled less than approved; emit the residual. + // `token` is the hop's INPUT: the router can pull at most `amount` (the approval, just reset), + // and any refund is a subset of what it pulled, so the balance can only fall (balAfter <= + // balBefore) — the subtraction cannot underflow. A shortfall (spent < amount) means the router + // filled less than approved; emit the residual so it can be swept. uint256 spent = balBefore - token.balanceOf(address(this)); if (spent < amount) emit PartialSwapLeftover(address(token), amount - spent); } From b36c17e45b8d08e03085ce8d418a78585a27101e Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 13 Jul 2026 18:50:12 +0530 Subject: [PATCH 53/78] fix(bstock): consistent LM slippage bps and tolerant SOURCE parsing --- scripts/bstock/lib/sources.ts | 19 +++++++++++++------ tests/hardhat/BStockAtomicLiquidate.ts | 12 ++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/scripts/bstock/lib/sources.ts b/scripts/bstock/lib/sources.ts index d3720fcd8..9af3581b5 100644 --- a/scripts/bstock/lib/sources.ts +++ b/scripts/bstock/lib/sources.ts @@ -112,10 +112,12 @@ const liquidMeshSource: QuoteSource = { firm: false, // `/quote` is indicative; the built `/swap` order may fill lower, down to `builtFloor` // `/swap` (disableSimulate:true) is only fetched if Liquid Mesh wins — avoids a wasted order build. build: async () => { - // The order's own floor (`outputAmount`); LM also applies `slippageBps`, so the effective on-chain - // floor may be slightly looser than this. That is fine — the CONTRACT's `minOut` is the real guard; - // this floor only bounds how far LM itself will let the fill slip. - const minFloor = out.mul(Math.round((100 - a.slippage) * 100)).div(10000); + // Derive the floor and the request's `slippageBps` from ONE integer bps value, so the two can't + // diverge on rounding (e.g. a fractional-bps slippage). The order's own floor (`outputAmount`) is + // `out` haircut by that bps; LM also applies `slippageBps`, so the effective on-chain floor may be + // slightly looser — fine, the CONTRACT's `minOut` is the real guard; this only bounds LM's own slip. + const slippageBps = Math.round(a.slippage * 100); + const minFloor = out.mul(10000 - slippageBps).div(10000); const swap = await lmBuildSwap({ userAddress: a.taker, tokenIn: a.tokenIn, @@ -123,7 +125,7 @@ const liquidMeshSource: QuoteSource = { amountWei: a.weiAmount.toString(), minOutWei: minFloor.toString(), routePlans: quote.routePlans, - slippageBps: Math.round(a.slippage * 100), + slippageBps, }); // LM RFQ orders are short-lived. The contract enforces the expiry on-chain (DeadlineExpired), // but an order that is ALREADY too tight to survive signing + submission + inclusion should be @@ -163,7 +165,12 @@ export const SOURCES: QuoteSource[] = [nativeSource, liquidMeshSource]; export function selectedSources(): QuoteSource[] { const raw = (process.env.SOURCE || "auto").toLowerCase().trim(); if (raw === "auto") return SOURCES.filter(s => s.available()); - const names = raw.split(",").map(s => s.trim()); + // Drop empty segments so a trailing comma / stray whitespace (e.g. `SOURCE=native,`) doesn't surface as + // a confusing `unknown SOURCE(s): ` error. + const names = raw + .split(",") + .map(s => s.trim()) + .filter(Boolean); const picked = SOURCES.filter(s => names.includes(s.name)); const unknown = names.filter(n => !SOURCES.some(s => s.name === n)); if (unknown.length) diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index 8045d1d20..93709c9fa 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -428,6 +428,18 @@ describe("bStock atomic liquidation script", () => { expect(await usdt.balanceOf(liq.address)).to.equal(OUT); }); + it("tolerates a trailing comma / empty segment in SOURCE", async () => { + await usdt.mint(liq.address, REPAY); + const real = stubFetch({ nativeOut: U("5400"), lmOut: OUT, lmCalldata: lmSwapAll() }); + try { + lmEnv({ SOURCE: "liquidmesh," }); // empty tail segment must be dropped, not read as an unknown source + await atomicLiquidate(owner); + } finally { + globalThis.fetch = real; + } + expect(await bStock.balanceOf(lmRouter.address)).to.equal(SEIZED); + }); + it("rejects a built Liquid Mesh order whose TTL is below LM_MIN_TTL", async () => { await usdt.mint(liq.address, REPAY); // The built order expires 5s from now — under the 15s default margin. The guard must abort From ae6b50fd5cd33277a16db954ca18ea7b0d6415c9 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Tue, 14 Jul 2026 11:34:52 +0530 Subject: [PATCH 54/78] fix(bstock): time out hung hop-1 source API calls Promise.allSettled waits for the slowest source, so a hung API blocked the live one. Add fetchWithTimeout (SOURCE_TIMEOUT_MS, default 8s) to the Native and LM clients so a hung source aborts and drops out. --- scripts/bstock/README.md | 1 + scripts/bstock/lib/http.ts | 37 ++++++++++++++++ scripts/bstock/lib/liquidmesh.ts | 10 ++++- scripts/bstock/lib/native.ts | 3 +- tests/hardhat/BStockSourceTimeout.ts | 64 ++++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 scripts/bstock/lib/http.ts create mode 100644 tests/hardhat/BStockSourceTimeout.ts diff --git a/scripts/bstock/README.md b/scripts/bstock/README.md index b25fd02c5..7a29f76e7 100644 --- a/scripts/bstock/README.md +++ b/scripts/bstock/README.md @@ -121,6 +121,7 @@ call `liquidate`/`flashLiquidate`. | `LM_API_KEY` | | | Liquid Mesh API key (required for `liquidmesh`/`auto`) | | `LM_PRIVATE_KEY_SEED` | | | Liquid Mesh Ed25519 seed, base64url (required for `liquidmesh`/`auto`) | | `LM_MIN_TTL` | | `15` | Min seconds left on the LM order at build time, else abort (LM RFQ orders are short-lived; the on-chain deadline still enforces the real expiry) | +| `SOURCE_TIMEOUT_MS` | | `8000` | Per-request timeout (ms) on each hop-1 source API call, so a hung source aborts and drops out of the `auto` race instead of blocking the live one | | `DRY_RUN` | | | `1` → callStatic only, sends nothing | | `SLIPPAGE` | | `0.5` | Native/LM slippage % | | `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) | diff --git a/scripts/bstock/lib/http.ts b/scripts/bstock/lib/http.ts new file mode 100644 index 000000000..4fade5ac3 --- /dev/null +++ b/scripts/bstock/lib/http.ts @@ -0,0 +1,37 @@ +/** + * Shared request timeout for hop-1 liquidity-source API calls. + * + * A source's HTTP call with no timeout can hang "pending" indefinitely (Node's global fetch has no + * practical request timeout). Because atomic-liquidate prices every selected source with + * `Promise.allSettled` — which resolves only when the SLOWEST settles — one hung API would stall a live + * source's answer and block the whole selection mid-incident. `AbortSignal.timeout` aborts the socket so a + * hung source rejects and drops out of the race instead. Override the window with `SOURCE_TIMEOUT_MS` + * (milliseconds, default 8000). + */ + +export function requestTimeoutMs(): number { + const ms = Number(process.env.SOURCE_TIMEOUT_MS || "8000"); + if (!Number.isFinite(ms) || ms <= 0) { + throw new Error( + `SOURCE_TIMEOUT_MS must be a positive number of milliseconds, got "${process.env.SOURCE_TIMEOUT_MS}"`, + ); + } + return ms; +} + +/** + * `fetch` with the shared abort timeout applied. Rethrows an aborted request as a legible + * `${label} timed out …` error (undici surfaces the abort as `TimeoutError`/`AbortError`), so an operator + * sees which source stalled rather than a bare abort stack. + */ +export async function fetchWithTimeout(url: string, init: RequestInit, label: string): Promise { + const ms = requestTimeoutMs(); + try { + return await fetch(url, { ...init, signal: AbortSignal.timeout(ms) }); + } catch (e: any) { + if (e?.name === "TimeoutError" || e?.name === "AbortError") { + throw new Error(`${label} timed out after ${ms}ms (raise SOURCE_TIMEOUT_MS if the API is just slow)`); + } + throw e; + } +} diff --git a/scripts/bstock/lib/liquidmesh.ts b/scripts/bstock/lib/liquidmesh.ts index ea09f3da0..0de0aa7ad 100644 --- a/scripts/bstock/lib/liquidmesh.ts +++ b/scripts/bstock/lib/liquidmesh.ts @@ -28,6 +28,8 @@ */ import { createHash, createPrivateKey, sign as edSign } from "crypto"; +import { fetchWithTimeout } from "./http"; + // The JWT is signed over the FULL request path, so the network prefix must be part of `path` (not folded // into the host) — otherwise the signed path omits `/v1/bsc` and the server rejects the token (401). const DEFAULT_HOST = "https://api.liquidmesh.io"; @@ -158,7 +160,7 @@ export async function getQuote(p: LmQuoteParams): Promise { const path = `${NETWORK_PREFIX}/quote?chainId=${chainId}&inputToken=${p.tokenIn}&outputToken=${p.tokenOut}` + `&amount=${p.amountWei}&userAddress=${p.userAddress}`; - const res = await fetch(`${host()}${path}`, { headers: headers("GET", path) }); + const res = await fetchWithTimeout(`${host()}${path}`, { headers: headers("GET", path) }, "Liquid Mesh /quote"); const text = await res.text(); let body: LmResponse; try { @@ -197,7 +199,11 @@ export async function buildSwap(p: LmSwapParams): Promise { routePlans: p.routePlans, }, }); - const res = await fetch(`${host()}${path}`, { method: "POST", headers: headers("POST", path, body), body }); + const res = await fetchWithTimeout( + `${host()}${path}`, + { method: "POST", headers: headers("POST", path, body), body }, + "Liquid Mesh /swap", + ); const text = await res.text(); let j: LmResponse; try { diff --git a/scripts/bstock/lib/native.ts b/scripts/bstock/lib/native.ts index 49cd1a1d5..831626635 100644 --- a/scripts/bstock/lib/native.ts +++ b/scripts/bstock/lib/native.ts @@ -16,6 +16,7 @@ * * Note: the `/bridge/*` variants are cross-chain and use a different auth scope. */ +import { fetchWithTimeout } from "./http"; const DEFAULT_BASE = "https://v2.api.native.org/swap-api-v2/v1"; @@ -79,7 +80,7 @@ function apiKey(): string { async function get(path: string, params: Record): Promise { const qs = new URLSearchParams(params).toString(); const url = `${baseUrl()}${path}?${qs}`; - const res = await fetch(url, { headers: { api_key: apiKey() } }); + const res = await fetchWithTimeout(url, { headers: { api_key: apiKey() } }, `Native ${path}`); const body = await res.json(); // Native returns 200 with an error envelope on failure; surface both forms. if (body && body.code !== undefined && body.message !== undefined && body.success === undefined) { diff --git a/tests/hardhat/BStockSourceTimeout.ts b/tests/hardhat/BStockSourceTimeout.ts new file mode 100644 index 000000000..b03dcd78a --- /dev/null +++ b/tests/hardhat/BStockSourceTimeout.ts @@ -0,0 +1,64 @@ +import { expect } from "chai"; + +import { fetchWithTimeout, requestTimeoutMs } from "../../scripts/bstock/lib/http"; + +// Guards the hop-1 source request timeout (scripts/bstock/lib/http.ts). atomic-liquidate prices every +// selected source with Promise.allSettled, which resolves only when the SLOWEST settles — so a source +// whose API hangs "pending" (no fetch timeout) would block a live source's answer mid-incident. +// fetchWithTimeout arms AbortSignal.timeout so a hung source aborts and drops out of the race instead. +// No chain, no live API: global fetch is stubbed to model a hang / a fast response. + +describe("bStock hop-1 source request timeout", () => { + const origFetch = global.fetch; + const origEnv = process.env.SOURCE_TIMEOUT_MS; + + afterEach(() => { + global.fetch = origFetch; + if (origEnv === undefined) delete process.env.SOURCE_TIMEOUT_MS; + else process.env.SOURCE_TIMEOUT_MS = origEnv; + }); + + it("aborts a hung request and rejects with a legible, labelled timeout error", async () => { + process.env.SOURCE_TIMEOUT_MS = "30"; + // Model a hung API: never resolve on its own; reject only when the request's abort signal fires, + // with that signal's reason — exactly how undici surfaces an AbortSignal.timeout (a TimeoutError). + global.fetch = ((_url: unknown, init: { signal: AbortSignal }) => + new Promise((_resolve, reject) => { + const { signal } = init; + if (signal.aborted) reject(signal.reason); + else signal.addEventListener("abort", () => reject(signal.reason)); + })) as unknown as typeof fetch; + + await expect(fetchWithTimeout("https://x", {}, "Native /firm-quote")).to.be.rejectedWith( + /Native \/firm-quote timed out after 30ms/, + ); + }); + + it("passes a fast response through untouched, before the timeout fires", async () => { + process.env.SOURCE_TIMEOUT_MS = "1000"; + global.fetch = (async () => new Response("ok")) as unknown as typeof fetch; + + const res = await fetchWithTimeout("https://x", {}, "Liquid Mesh /quote"); + expect(await res.text()).to.equal("ok"); + }); + + it("does not swallow a non-timeout error (e.g. connection refused) as a timeout", async () => { + process.env.SOURCE_TIMEOUT_MS = "1000"; + global.fetch = (async () => { + throw new TypeError("fetch failed"); + }) as unknown as typeof fetch; + + await expect(fetchWithTimeout("https://x", {}, "Liquid Mesh /quote")).to.be.rejectedWith("fetch failed"); + }); + + it("defaults to 8000ms and rejects a non-positive / non-numeric SOURCE_TIMEOUT_MS", () => { + delete process.env.SOURCE_TIMEOUT_MS; + expect(requestTimeoutMs()).to.equal(8000); + + process.env.SOURCE_TIMEOUT_MS = "0"; + expect(() => requestTimeoutMs()).to.throw(/SOURCE_TIMEOUT_MS must be a positive number/); + + process.env.SOURCE_TIMEOUT_MS = "abc"; + expect(() => requestTimeoutMs()).to.throw(/SOURCE_TIMEOUT_MS must be a positive number/); + }); +}); From d8d0e99ee64b0c0f66542c4053be205a58a4550b Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Tue, 14 Jul 2026 19:26:38 +0530 Subject: [PATCH 55/78] fix(bstock): bubble up router revert reason in _swap Surface the swap router's own revert reason instead of an opaque SwapFailed(), so a failed hop reports the actual cause (e.g. insufficient allowance). SwapFailed() is kept only as the empty-returndata fallback. --- contracts/BStock/BStockLiquidator.sol | 13 +++++++++++-- tests/hardhat/BStockLiquidator.ts | 21 ++++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index 4352e320f..c82a68901 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -321,8 +321,17 @@ contract BStockLiquidator is function _swap(IERC20Upgradeable token, address router, bytes memory data, uint256 amount) private { uint256 balBefore = token.balanceOf(address(this)); token.forceApprove(router, amount); - (bool ok, ) = router.call(data); - if (!ok) revert SwapFailed(); + (bool ok, bytes memory returndata) = router.call(data); + if (!ok) { + // Bubble up the router's own revert reason for easier debugging; fall back to SwapFailed() + // only when the call reverted without returndata. + if (returndata.length != 0) { + assembly { + revert(add(returndata, 0x20), mload(returndata)) + } + } + revert SwapFailed(); + } token.forceApprove(router, 0); // never leave a standing approval // `token` is the hop's INPUT: the router can pull at most `amount` (the approval, just reset), // and any refund is a subset of what it pulled, so the balance can only fall (balAfter <= diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index a18bd4219..74acd3d4c 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -367,9 +367,10 @@ describe("BStockLiquidator (atomic)", () => { it("caps the hop-2 approval at the hop-1 delta (a swapAll cannot drain intermediate inventory)", async () => { await btcb.mint(liq.address, REPAY); await usdt.mint(liq.address, U("2000")); // inventory a greedy swapAll would try to take - // swapAll pulls the caller's ENTIRE USDT balance (7500), but the approval is capped at midDelta (5500). + // swapAll pulls the caller's ENTIRE USDT balance (7500), but the approval is capped at midDelta (5500), + // so the router's transferFrom reverts — and _swap bubbles that reason up verbatim. const p = twoHopParams({ swapCalldata2: ammSwapAllCalldata(liq.address) }); - await expect(liq.connect(owner).liquidate(p)).to.be.revertedWithCustomError(liq, "SwapFailed"); + await expect(liq.connect(owner).liquidate(p)).to.be.revertedWith("ERC20: insufficient allowance"); expect(await usdt.balanceOf(liq.address)).to.equal(U("2000")); // reverted, inventory intact }); @@ -580,11 +581,21 @@ describe("BStockLiquidator (atomic)", () => { .withArgs(7); }); - it("reverts SwapFailed when the router call reverts", async () => { - // amountIn exceeds what the contract holds/approves, so the router's transferFrom reverts. + it("bubbles up the router's revert reason when the swap call reverts", async () => { + // amountIn exceeds what the contract holds/approves, so the router's transferFrom reverts — + // and _swap surfaces that reason verbatim instead of an opaque SwapFailed(). await expect( liq.connect(owner).liquidate(params({ swapCalldata: swapCalldata(SEIZED.mul(2), liq.address) })), - ).to.be.revertedWithCustomError(liq, "SwapFailed"); + ).to.be.revertedWith("ERC20: insufficient allowance"); + }); + + it("falls back to SwapFailed when the router reverts without a reason", async () => { + // An unknown selector on the router (no matching function, no fallback) reverts with empty + // returndata, so _swap has no reason to bubble and raises the custom SwapFailed(). + await expect(liq.connect(owner).liquidate(params({ swapCalldata: "0xdeadbeef" }))).to.be.revertedWithCustomError( + liq, + "SwapFailed", + ); }); }); From 929ec4121e652897b42ca14bcdf31d2ac7858c35 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 15 Jul 2026 17:26:59 +0530 Subject: [PATCH 56/78] fix(bstock): restore router spender lookup in _swap --- contracts/BStock/BStockLiquidator.sol | 25 ++++++--- tests/hardhat/BStockAtomicLiquidate.ts | 77 ++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 9 deletions(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index 37338336a..7dc9bd1ab 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -340,15 +340,22 @@ contract BStockLiquidator is if (router2 != address(0) && !isRouter[router2]) revert RouterNotAllowed(router2); } - /// @dev One swap hop: approve the exact `amount` to the allowlisted `router`, forward the opaque - /// calldata via a low-level call, then reset the approval to 0. The approval caps what the - /// router can pull; if the calldata sells less (e.g. a partially-filled RFQ quote), the - /// unconsumed remainder stays in the contract and is surfaced via `PartialSwapLeftover` so - /// operations can recover it with `sweep` — `minOut` still bounds the realized debt-asset - /// proceeds regardless. + /// @dev One swap hop: approve the exact `amount` to the router's configured spender, forward the + /// opaque calldata via a low-level call to the allowlisted `router`, then reset the approval to + /// 0. The spender defaults to the router itself when unset (Native, where the call target is the + /// puller); aggregators with a separate settlement/pull contract (e.g. Liquid Mesh) set it via + /// `setRouterSpender`. The approval caps what the spender can pull; if the calldata sells less + /// (e.g. a partially-filled RFQ quote), the unconsumed remainder stays in the contract and is + /// surfaced via `PartialSwapLeftover` so operations can recover it with `sweep` — `minOut` still + /// bounds the realized debt-asset proceeds regardless. function _swap(IERC20Upgradeable token, address router, bytes memory data, uint256 amount) private { uint256 balBefore = token.balanceOf(address(this)); - token.forceApprove(router, amount); + // Approve the PULLER, which is not always the call target: Liquid Mesh and similar split-settlement + // aggregators pull through a separate spender. Defaults to the router when unset, so Native (whose + // call target is the puller) is unaffected. + address spender = routerSpender[router]; + if (spender == address(0)) spender = router; + token.forceApprove(spender, amount); (bool ok, bytes memory returndata) = router.call(data); if (!ok) { // Bubble up the router's own revert reason for easier debugging; fall back to SwapFailed() @@ -360,8 +367,8 @@ contract BStockLiquidator is } revert SwapFailed(); } - token.forceApprove(router, 0); // never leave a standing approval - // `token` is the hop's INPUT: the router can pull at most `amount` (the approval, just reset), + token.forceApprove(spender, 0); // never leave a standing approval + // `token` is the hop's INPUT: the spender can pull at most `amount` (the approval, just reset), // and any refund is a subset of what it pulled, so the balance can only fall (balAfter <= // balBefore) — the subtraction cannot underflow. A shortfall (spent < amount) means the router // filled less than approved; emit the residual so it can be swept. diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index bdda38d86..422da59f6 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -503,5 +503,82 @@ describe("bStock atomic liquidation script", () => { await expect(atomicLiquidate(owner)).to.be.rejectedWith(/missing required creds/i); expect(await usdt.balanceOf(liq.address)).to.equal(REPAY); // untouched }); + + // A Liquid Mesh `/quote` is INDICATIVE: the built order may fill below it, down to its own floor. The + // hop-2 calldata bakes in a fixed `amountIn` off-chain, while on-chain the contract approves router2 for + // the ACTUAL hop-1 delta. Size hop 2 off the indicative `out` and an underfilling LM leaves the AMM + // pulling more than the approval — hop 2 reverts on allowance. Sizing it off the GUARANTEED floor keeps + // the pull within the approval. Here LM quotes 1:1 (5500 USDT) but its order fills at 0.998 (5489), + // between the floor (5472.5) and the quote — the exact gap the floor exists to absorb. + it("two-hop: sizes the AMM hop off the LM floor, so an LM order that underfills its quote still settles", async () => { + const { btcb, vBtcb, amm } = await deployTwoHop(); + await liq.connect(owner).setRouter(amm.address, true); + await btcb.mint(liq.address, REPAY); // debt inventory for the repay + + await lmRouter.setRate(U("0.998")); // LM fills 0.2% under its own indicative quote + const lmFilled = SEIZED.mul(U("0.998")).div(U("1")); // 5489 USDT actually delivered by hop 1 + const floor = OUT.mul(9950).div(10000); // quote 5500 haircut by SLIPPAGE=0.5% -> 5472.5 + + // Stub LM (/quote + /swap) and the hop-2 KyberSwap round-trip. The AMM calldata is built from the + // `amountIn` the script ASKS for (captured off the /routes query), so the on-chain pull is exactly + // what the script sized the leg at — that is what this test is asserting on. + let ammAmountIn = ""; + const real = globalThis.fetch; + globalThis.fetch = (async (url: unknown, init?: any) => { + const u = String(url); + const method = (init?.method || "GET").toUpperCase(); + if (u.includes("liquidmesh.io") && u.includes("/quote")) { + return resp({ + code: 0, + data: { + inputAmount: "0", + outputAmount: OUT.toString(), // indicative 1:1 — optimistic vs the 0.998 the order fills at + routePlans: [{ subRouters: [{ dexes: [{ dex: "rfq_native", weight: 10000 }] }] }], + }, + }); + } + if (u.includes("liquidmesh.io") && method === "POST") { + return resp({ + code: 0, + data: { + chainId: "56", + callMsg: { from: liq.address, to: lmRouter.address, value: "0x0", data: lmSwapAll() }, + orderId: "1", + expiryTimestamp: Math.floor(Date.now() / 1000) + 3600, + }, + }); + } + if (u.includes("kyberswap") && u.includes("/routes")) { + ammAmountIn = new URL(u).searchParams.get("amountIn") || ""; + return resp({ code: 0, data: { routeSummary: { amountOut: ammAmountIn } } }); + } + if (u.includes("kyberswap") && u.includes("/route/build")) { + // Pull EXACTLY what the script sized this leg at. + return resp({ + code: 0, + data: { + routerAddress: amm.address, + data: amm.interface.encodeFunctionData("swap", [usdt.address, ammAmountIn, btcb.address, liq.address]), + amountOut: ammAmountIn, + }, + }); + } + throw new Error(`unexpected fetch: ${method} ${u}`); + }) as unknown as typeof fetch; + + try { + lmEnv({ SOURCE: "liquidmesh", VDEBT: vBtcb.address }); + await atomicLiquidate(owner); + } finally { + globalThis.fetch = real; + } + + // Sized off the floor, not the indicative quote — the whole point. + expect(ammAmountIn).to.equal(floor.toString()); + // Hop 2 pulled the floor out of the larger real delta, so the surplus stays behind as sweepable + // USDT inventory rather than reverting the settle. + expect(await usdt.balanceOf(liq.address)).to.equal(lmFilled.sub(floor)); + expect(await btcb.balanceOf(liq.address)).to.equal(floor); // AMM pays 1:1 on what it pulled + }); }); }); From 591f6c73c02f548de8d8e384ad83131b4abdf20a Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 15 Jul 2026 17:27:32 +0530 Subject: [PATCH 57/78] fix(bstock): size the hop-2 leg off the LM floor, not the quote MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An LM /quote is indicative, but hop-2 calldata bakes in a fixed amountIn while the contract approves only the real hop-1 delta — an underfill left the AMM pulling past the approval. Sizing off the guaranteed floor keeps the pull covered; the surplus stays sweepable. Native unchanged (floor==out). --- scripts/bstock/atomic-liquidate.ts | 22 +++++++++++++++++---- tests/hardhat/BStockLiquidatorLiquidMesh.ts | 11 ++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 266f8def3..5dc16c1e6 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -100,7 +100,8 @@ interface Hop1 { source: string; router: string; calldata: string; - out: BigNumber; // USDT out of hop 1 (base units) + out: BigNumber; // USDT out of hop 1 (base units) — INDICATIVE for a non-firm source; display only + floor: BigNumber; // the built order's GUARANTEED worst-case USDT out — what downstream legs must assume deadline: BigNumber; // unix seconds — the quote's on-chain expiry } @@ -158,6 +159,7 @@ async function pickHop1Source(args: QuoteArgs): Promise { router: built.router, calldata: built.calldata, out: winner.quote.out, + floor: built.builtFloor, deadline: built.deadline, }; } @@ -323,16 +325,28 @@ export async function atomicLiquidate(signer: Signer) { deadline = hop1.deadline; // settle tx reverts on-chain past the quote's expiry router = hop1.router; swapCalldata = hop1.calldata; - const midOut = hop1.out; // USDT out of hop 1 - console.log(`hop-1 source: ${hop1.source} (out=${ethers.utils.formatUnits(midOut, 18)} USDT)`); + const midOut = hop1.out; // USDT out of hop 1 — indicative for a non-firm source (LM); display only + // What hop 1 is GUARANTEED to deliver. For a firm source (Native) this equals `out`; for an indicative + // one (Liquid Mesh `/quote`) it is the built order's own floor, which is all the fill is bound by. + const midFloor = hop1.floor; + console.log( + `hop-1 source: ${hop1.source} (out=${ethers.utils.formatUnits(midOut, 18)} USDT, ` + + `floor=${ethers.utils.formatUnits(midFloor, 18)} USDT)`, + ); if (twoHop) { // Hop 2: convert the hop-1 USDT to the (non-USDT) debt asset via an allowlisted AMM/aggregator. + // Size this leg off the hop-1 FLOOR, not the indicative `out`: on-chain the contract approves router2 + // for the ACTUAL hop-1 delta (`midDelta`), while this calldata bakes in a fixed `amountIn`. Quote it + // at `out` and an indicative source that fills even slightly under would leave the router pulling + // more than the approval — hop 2 reverts on allowance. `floor <= midDelta` always holds, so the pull + // always fits. Any surplus (`midDelta - floor`, bounded by slippage) stays as USDT inventory and the + // contract emits `PartialSwapLeftover` for it — sweepable, not lost. const amm = await getAmmSwap( { tokenIn: nativeOut, tokenOut: debt.address, - amountIn: midOut.toString(), + amountIn: midFloor.toString(), recipient: liquidator.address, slippage, }, diff --git a/tests/hardhat/BStockLiquidatorLiquidMesh.ts b/tests/hardhat/BStockLiquidatorLiquidMesh.ts index 4226e3b86..98f2cb8b3 100644 --- a/tests/hardhat/BStockLiquidatorLiquidMesh.ts +++ b/tests/hardhat/BStockLiquidatorLiquidMesh.ts @@ -122,9 +122,10 @@ describe("BStockLiquidator + Liquid Mesh (separate spender)", () => { it("REVERTS without a configured spender — approving the call target leaves the puller unapproved", async () => { // No setRouterSpender: `_swap` approves the router itself, but MockSpender.pull does the transferFrom - // and has no allowance -> "spender pull failed" -> the low-level call fails -> SwapFailed. + // and has no allowance -> the pull reverts and `_swap` bubbles the ERC20's own reason up (SwapFailed() + // remains only the empty-returndata fallback). expect(await liq.routerSpender(lmRouter.address)).to.equal(ZERO); - await expect(liq.connect(owner).liquidate(params())).to.be.revertedWithCustomError(liq, "SwapFailed"); + await expect(liq.connect(owner).liquidate(params())).to.be.revertedWith("ERC20: insufficient allowance"); }); it("SUCCEEDS atomically once the spender is set — seizes, redeems, and sells a token it never pre-held", async () => { @@ -173,8 +174,8 @@ describe("BStockLiquidator + Liquid Mesh (separate spender)", () => { await liq.connect(owner).setRouterSpender(lmRouter.address, spender.address); await liq.connect(owner).setRouterSpender(lmRouter.address, ZERO); expect(await liq.routerSpender(lmRouter.address)).to.equal(ZERO); - // Back to old behaviour -> the split router reverts again. - await expect(liq.connect(owner).liquidate(params())).to.be.revertedWithCustomError(liq, "SwapFailed"); + // Back to old behaviour -> the unapproved spender's pull reverts, bubbled up by `_swap`. + await expect(liq.connect(owner).liquidate(params())).to.be.revertedWith("ERC20: insufficient allowance"); }); it("setRouterSpender is owner-only", async () => { @@ -208,6 +209,6 @@ describe("BStockLiquidator + Liquid Mesh (separate spender)", () => { // Re-allowlisting does NOT bring the old spender back — it must be set again explicitly. await liq.connect(owner).setRouter(lmRouter.address, true); expect(await liq.routerSpender(lmRouter.address)).to.equal(ZERO); - await expect(liq.connect(owner).liquidate(params())).to.be.revertedWithCustomError(liq, "SwapFailed"); + await expect(liq.connect(owner).liquidate(params())).to.be.revertedWith("ERC20: insufficient allowance"); }); }); From 6a21bcac0f9c46dd82c167eea14a49b0e2a0dca1 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 15 Jul 2026 18:36:33 +0530 Subject: [PATCH 58/78] feat(bstock): support VAI debt in the backstop liquidator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A borrower whose only debt is VAI was unliquidatable: _liquidate called underlying(), which the VAIController does not have. Resolve VAI via getVAIAddress() and route it through the gate's _liquidateVAI branch, two-hop bStock->USDT->VAI with the PSM as hop 2. INVENTORY only — VAI is burned on repay, so there is no market to flash from. --- contracts/BStock/BStockLiquidator.sol | 43 ++++++++++--- contracts/BStock/IBStockLiquidator.sol | 5 ++ contracts/test/BStockLiquidationMocks.sol | 44 +++++++++++++- deploy/019-deploy-bstock-liquidator.ts | 2 + scripts/bstock/atomic-liquidate.ts | 53 +++++++++++----- tests/hardhat/BStockLiquidator.ts | 74 +++++++++++++++++++++++ 6 files changed, 197 insertions(+), 24 deletions(-) diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol index 7dc9bd1ab..69030bb86 100644 --- a/contracts/BStock/BStockLiquidator.sol +++ b/contracts/BStock/BStockLiquidator.sol @@ -14,7 +14,7 @@ import { IWBNB } from "../external/IWBNB.sol"; // Shared Venus interfaces: IComptroller (Core diamond — liquidator gate + flash loan), IVToken // (flash-loan asset array element), and ILiquidator (the pool-wide Venus Liquidator gate that pulls // the repay and returns our share of the seized collateral). -import { IComptroller, IVToken, ILiquidator } from "../InterfacesV8.sol"; +import { IComptroller, IVToken, ILiquidator, IVAIController } from "../InterfacesV8.sol"; import { IBStockLiquidator } from "./IBStockLiquidator.sol"; /** @@ -44,6 +44,15 @@ import { IBStockLiquidator } from "./IBStockLiquidator.sol"; * (1:1 with BNB). FLASH mode borrows from vWBNB, NOT vBNB: vBNB cannot be flash-repaid (its * `doTransferIn` requires `msg.value`), whereas vWBNB's underlying is a plain ERC20. * + * VAI debt (VAIController): supported in INVENTORY mode only. VAI is not a vToken — a `vDebt` equal to + * `comptroller.vaiController()` is VAI, and like vBNB it has no `underlying()`, so the debt token is + * resolved via `getVAIAddress()`. The repay is a plain ERC20 approval to the gate, which takes its + * `_liquidateVAI` branch (pulls the VAI from us, then burns it via `VAIController.liquidateVAI`). + * Because RFQ sources quote bStock->USDT only, a VAI debt is inherently two-hop (bStock->USDT->VAI); + * hop 2 is expected to be the Peg Stability Module (`swapStableForVAI`, allowlisted as `router2`), + * which mints VAI from USDT at the oracle rate. FLASH mode is rejected (`FlashNotSupportedForVai`): + * `executeFlashLoan` lends a vToken's underlying, and VAI is minted/burned with no vVAI market. + * * Ownership / scope: this is Venus's OWN backstop tool, NOT a public utility — `liquidate` and * `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock * liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus @@ -260,6 +269,12 @@ contract BStockLiquidator is if (params.minOut == 0) revert ZeroMinOut(); if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp); + // VAI has no market to flash from: `executeFlashLoan` lends a vToken's underlying, whereas VAI is + // MINTED/BURNED by the VAIController (`repayVAIFresh` burns it) and has no vVAI. Reject up front + // instead of passing the VAIController into `executeFlashLoan` and failing opaquely. Use + // `liquidate` (INVENTORY) with pre-funded VAI for a VAI debt. + if (address(params.vDebt) == address(comptroller.vaiController())) revert FlashNotSupportedForVai(); + // BNB debt is flash-funded from vWBNB (an ERC20 market), not vBNB: vBNB cannot be flash-repaid. // The flashed WBNB is unwrapped to native BNB for the repay inside `_liquidate` (see `executeOperation`). IVToken[] memory vTokens = new IVToken[](1); @@ -392,12 +407,26 @@ contract BStockLiquidator is // debt-accounting token throughout (1:1 with BNB), so the whole swap/minOut path below is reused. // KEEP THIS ORDER — hoisting `underlying()` above the check reverts every BNB liquidation. bool isBnb = address(params.vDebt) == vBNB; - // Native RFQ only quotes bStock->USDT, so a BNB debt is inherently two-hop (...->WBNB). Reject a - // single-hop BNB config up front instead of failing opaquely later on a zero WBNB delta. - if (isBnb && params.router2 == address(0)) revert InvalidIntermediate(); - IERC20Upgradeable debt = isBnb - ? IERC20Upgradeable(address(wbnb)) - : IERC20Upgradeable(params.vDebt.underlying()); + IERC20Upgradeable debt; + // Scoped so `vaiCtrl`/`isVai` don't hold stack slots for the rest of the frame. + { + // A debt equal to the VAIController is VAI: it is not a vToken and has no `underlying()` + // either, so — like vBNB — the check MUST come before any `underlying()` evaluation. The gate + // takes its VAI branch (`Liquidator._liquidateVAI`), which pulls VAI from us and burns it via + // `VAIController.liquidateVAI`; the repay is a plain ERC20 approval, so the non-BNB path below + // is reused as-is. + IVAIController vaiCtrl = comptroller.vaiController(); + bool isVai = address(params.vDebt) == address(vaiCtrl); + // RFQ sources only quote bStock->USDT, so BNB and VAI debts are inherently two-hop + // (...->WBNB / ...->VAI). Reject a single-hop config up front instead of failing opaquely + // later on a zero debt-asset delta. + if ((isBnb || isVai) && params.router2 == address(0)) revert InvalidIntermediate(); + debt = isBnb + ? IERC20Upgradeable(address(wbnb)) + : isVai + ? IERC20Upgradeable(vaiCtrl.getVAIAddress()) + : IERC20Upgradeable(params.vDebt.underlying()); + } IERC20Upgradeable bStock = IERC20Upgradeable(params.vBStock.underlying()); // 1. Repay the borrow, seizing the bStock vToken to this contract. diff --git a/contracts/BStock/IBStockLiquidator.sol b/contracts/BStock/IBStockLiquidator.sol index bfbe458dd..518940c89 100644 --- a/contracts/BStock/IBStockLiquidator.sol +++ b/contracts/BStock/IBStockLiquidator.sol @@ -107,6 +107,11 @@ interface IBStockLiquidator { /// @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); diff --git a/contracts/test/BStockLiquidationMocks.sol b/contracts/test/BStockLiquidationMocks.sol index b47784620..ee3bdb18c 100644 --- a/contracts/test/BStockLiquidationMocks.sol +++ b/contracts/test/BStockLiquidationMocks.sol @@ -36,6 +36,20 @@ interface IFlashReceiverLike { ) external returns (bool, uint256[] memory); } +/// @dev Minimal VAIController stand-in. VAI is not a vToken and has no `underlying()`; the debt token +/// is resolved through `getVAIAddress()`, exactly as the real VAIController exposes it. +contract MockVAIController { + address public immutable vai; + + constructor(address vai_) { + vai = vai_; + } + + function getVAIAddress() external view returns (address) { + return vai; + } +} + /// @dev Minimal Comptroller surface the script + BStockLiquidator read, plus a flash lender. contract MockComptrollerLite { uint256 public shortfall; @@ -44,6 +58,11 @@ contract MockComptrollerLite { uint256 public flashPremiumMantissa; // fee on flash principal, 1e18-scaled (default 0) address public liquidatorContract; // pool-wide gate; 0 = permissionless (direct liquidateBorrow) uint256 public treasuryPercent; // redeem fee, 1e18-scaled (default 0) + address public vaiController; // a debt equal to this is VAI (no underlying(); see MockVAIController) + + function setVaiController(address v) external { + vaiController = v; + } function setShortfall(uint256 s) external { shortfall = s; @@ -85,11 +104,22 @@ contract MockComptrollerLite { return (0, (repayAmount * liquidationIncentiveMantissa) / 1e18); } + /// @dev VAI's seize math (VAIController.liquidateVAIFresh calls this): VAI is priced at $1 and the + /// incentive is the borrower-agnostic getLiquidationIncentive — hence the 2-arg shape. + function liquidateVAICalculateSeizeTokens(address, uint256 repayAmount) external view returns (uint256, uint256) { + return (0, (repayAmount * liquidationIncentiveMantissa) / 1e18); + } + /// @dev Read by the off-chain script to size the Venus Liquidator's bonus cut. function getEffectiveLiquidationIncentive(address, address) external view returns (uint256) { return liquidationIncentiveMantissa; } + /// @dev Borrower-agnostic incentive — the one VAI's seize math uses. + function getLiquidationIncentive(address) external view returns (uint256) { + return liquidationIncentiveMantissa; + } + /// @dev Flash lender: send principal from the (pre-funded) debt vToken, call back, pull repay. function executeFlashLoan( address payable /* onBehalf */, @@ -314,6 +344,7 @@ contract MockVenusLiquidator { uint256 public treasuryCutMantissa; // cut of the seized collateral kept by the liquidator (default 0) uint256 public pullMantissa = 1e18; // fraction of repayAmount actually pulled (default 100%) address public vBnb; // native BNB market; a repay for this vToken must arrive as msg.value + address public vaiController; // VAI "market"; the repay is the VAI ERC20, resolved via getVAIAddress() function setTreasuryCut(uint256 m) external { treasuryCutMantissa = m; @@ -330,6 +361,13 @@ contract MockVenusLiquidator { vBnb = v; } + /// @dev Mirrors `Liquidator._liquidateVAI`: a VAI repay is pulled as the VAI ERC20 (resolved via + /// `getVAIAddress()`, since the VAIController has no `underlying()`), then burned by the real + /// VAIController. Here we just keep it — only the pull is observable to the liquidator. + function setVaiController(address v) external { + vaiController = v; + } + /// @dev Mirrors the real Venus Liquidator getter the off-chain script reads to precompute the cut. function treasuryPercentMantissa() external view returns (uint256) { return treasuryCutMantissa; @@ -345,7 +383,10 @@ contract MockVenusLiquidator { // Native BNB repay: require exact msg.value (mirrors Liquidator.sol) and keep the BNB. require(msg.value == repayAmount, "bad value"); } else { - address underlying = MockVTokenDebt(vToken).underlying(); + // VAI has no `underlying()` — resolve it via `getVAIAddress()`, like `Liquidator._liquidateVAI`. + address underlying = vToken == vaiController + ? MockVAIController(vToken).getVAIAddress() + : MockVTokenDebt(vToken).underlying(); uint256 pulled = (repayAmount * pullMantissa) / 1e18; require(ERC20(underlying).transferFrom(msg.sender, address(this), pulled), "repay pull failed"); } @@ -367,6 +408,7 @@ contract MockMaliciousFlashComptroller { Mode public mode; address public liquidatorContract; // unset: the receiver would liquidate directly + address public vaiController; // unset: `flashLiquidate`'s VAI check reads this and never matches vDebt function setMode(Mode mode_) external { mode = mode_; diff --git a/deploy/019-deploy-bstock-liquidator.ts b/deploy/019-deploy-bstock-liquidator.ts index 921ad19d0..bc6a79a9b 100644 --- a/deploy/019-deploy-bstock-liquidator.ts +++ b/deploy/019-deploy-bstock-liquidator.ts @@ -64,6 +64,8 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // 1. setRouter(nativeRfqRouter, true) — hop-1 Native RFQ router (bStock -> USDT) // 2. setRouter(ammRouter, true) — hop-2 AMM/aggregator router(s) for non-USDT / BNB debt // 3. setOperator(operatorKey, true) — each liquidation-bot key (or each Safe signer) + // (VAI debt only) setRouter(PegStability_USDT, true) — hop-2 USDT->VAI via swapStableForVAI. The + // PSM is the call target AND the token puller, so it needs no setRouterSpender entry. // These are onlyOwner, so they cannot run here (deployer is not the owner); ship them as a Safe batch. // // FLASH mode ONLY (skip if using INVENTORY mode): flashLiquidate calls Comptroller.executeFlashLoan, diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 5dc16c1e6..09e1db020 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -84,10 +84,16 @@ const ERC20_ABI = [ const COMPTROLLER_ABI = [ "function getAccountLiquidity(address) view returns (uint256,uint256,uint256)", "function liquidateCalculateSeizeTokens(address,address,address,uint256) view returns (uint256,uint256)", + // VAI's seize math is a separate function: VAI is priced at $1 and the incentive is the + // borrower-agnostic getLiquidationIncentive (see ComptrollerLens.liquidateVAICalculateSeizeTokens). + "function liquidateVAICalculateSeizeTokens(address,uint256) view returns (uint256,uint256)", "function treasuryPercent() view returns (uint256)", "function liquidatorContract() view returns (address)", + "function vaiController() view returns (address)", "function getEffectiveLiquidationIncentive(address,address) view returns (uint256)", + "function getLiquidationIncentive(address) view returns (uint256)", ]; +const VAI_CONTROLLER_ABI = ["function getVAIAddress() view returns (address)"]; const VENUS_LIQUIDATOR_ABI = ["function treasuryPercentMantissa() view returns (uint256)"]; function env(name: string, required = true): string { @@ -198,13 +204,23 @@ export async function atomicLiquidate(signer: Signer) { // unwraps the repay internally, so off-chain the debt asset for the swap chain + minOut is WBNB. // WBNB is immutable on BSC, so it is the canonical constant; WBNB_ADDR only overrides it so a // non-fork test can point at a freshly-deployed mock (mirrors USDT_ADDR below). + // VAI is not a vToken: its "market" is the VAIController, which has no underlying() EITHER. It must + // therefore be detected BEFORE the vBNB fallback below — otherwise the catch would misread a VAI debt + // as native BNB and account it in WBNB. The VAI token itself is a plain ERC20 (decimals/symbol work). + const vaiControllerAddr: string = await comptroller.vaiController(); + const isVai = vDebt.address.toLowerCase() === vaiControllerAddr.toLowerCase(); + let debtAddr: string; let isBnb = false; - try { - debtAddr = await vDebt.underlying(); - } catch { - isBnb = true; - debtAddr = ethers.utils.getAddress(process.env.WBNB_ADDR || BSC_WBNB); + if (isVai) { + debtAddr = await new Contract(vaiControllerAddr, VAI_CONTROLLER_ABI, signer).getVAIAddress(); + } else { + try { + debtAddr = await vDebt.underlying(); + } catch { + isBnb = true; + debtAddr = ethers.utils.getAddress(process.env.WBNB_ADDR || BSC_WBNB); + } } const debt = new Contract(debtAddr, ERC20_ABI, signer); const bStock = new Contract(await vBStock.underlying(), ERC20_ABI, signer); @@ -218,16 +234,17 @@ export async function atomicLiquidate(signer: Signer) { if (shortfall.eq(0)) throw new Error(`${borrower} has no shortfall — not liquidatable`); console.log(`borrower ${borrower} shortfall=${ethers.utils.formatEther(shortfall)} (USD-scaled)`); - // 1 + 2. precompute the exact seize so the quote amount matches what redeem() yields. Use the - // borrower-aware 4-arg overload (reads the pool the borrower is actually in via - // getEffectiveLiquidationIncentive) — the same version vToken.liquidateBorrowFresh calls on-chain. - // The 3-arg overload always reads Core Pool params and diverges if the borrower has switched pools. - const [seizeErr, seizeTokens]: BigNumber[] = await comptroller.liquidateCalculateSeizeTokens( - borrower, - vDebt.address, - vBStock.address, - repay, - ); + // 1 + 2. precompute the exact seize so the quote amount matches what redeem() yields. Mirror the + // function the on-chain path actually calls for this debt: + // - VAI -> VAIController.liquidateVAIFresh calls liquidateVAICalculateSeizeTokens(vCollateral, + // repay): VAI is priced at $1 and the incentive is the borrower-agnostic + // getLiquidationIncentive, so the 4-arg overload does NOT apply. + // - else -> vToken.liquidateBorrowFresh calls the borrower-aware 4-arg overload (reads the pool the + // borrower is actually in). The 3-arg overload always reads Core Pool params and diverges + // if the borrower has switched pools. + const [seizeErr, seizeTokens]: BigNumber[] = isVai + ? await comptroller.liquidateVAICalculateSeizeTokens(vBStock.address, repay) + : await comptroller.liquidateCalculateSeizeTokens(borrower, vDebt.address, vBStock.address, repay); if (!seizeErr.eq(0)) throw new Error(`liquidateCalculateSeizeTokens error ${seizeErr}`); // A zero seize means the incentive resolved to 0 (e.g. bStock unlisted in the borrower's pool): // surface it here rather than building a degenerate quote that reverts on-chain. @@ -251,7 +268,11 @@ export async function atomicLiquidate(signer: Signer) { const venusLiquidator = new Contract(gate, VENUS_LIQUIDATOR_ABI, signer); const liqTreasuryPct: BigNumber = await venusLiquidator.treasuryPercentMantissa(); if (!liqTreasuryPct.eq(0)) { - const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); + // Use the SAME incentive the seize above was computed with, else the bonus (and so the cut) is + // sized against the wrong number: VAI's seize math uses the borrower-agnostic getLiquidationIncentive. + const totalIncentive: BigNumber = isVai + ? await comptroller.getLiquidationIncentive(vBStock.address) + : await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); const bonusAmount = seizeTokens.mul(totalIncentive.sub(ONE)).div(totalIncentive); const treasuryCut = bonusAmount.mul(liqTreasuryPct).div(ONE); vReceived = seizeTokens.sub(treasuryCut); diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts index 74acd3d4c..c16bfd19e 100644 --- a/tests/hardhat/BStockLiquidator.ts +++ b/tests/hardhat/BStockLiquidator.ts @@ -22,6 +22,7 @@ describe("BStockLiquidator (atomic)", () => { let usdt: Contract, bStock: Contract; let comptroller: Contract, vBStock: Contract, vDebt: Contract, router: Contract, venusLiq: Contract; let wbnb: Contract, vWBNB: Contract; + let vai: Contract, vaiController: Contract; let liq: Contract; const REPAY = U("5000"); @@ -54,6 +55,12 @@ describe("BStockLiquidator (atomic)", () => { await comptroller.setLiquidatorContract(venusLiq.address); await venusLiq.setVBnb(VBNB); + // VAI: not a vToken, so the debt token is resolved via the VAIController's getVAIAddress(). + vai = await (await ethers.getContractFactory("MockMintableERC20")).deploy("Venus VAI", "VAI", 18); + vaiController = await (await ethers.getContractFactory("MockVAIController")).deploy(vai.address); + await comptroller.setVaiController(vaiController.address); + await venusLiq.setVaiController(vaiController.address); + liq = await deployLiquidator(comptroller.address); await liq.connect(owner).setRouter(router.address, true); @@ -390,6 +397,73 @@ describe("BStockLiquidator (atomic)", () => { }); }); + // VAI debt. VAI is not a vToken: the "market" is the VAIController, which has no underlying(), so the + // debt token is resolved via getVAIAddress(). The gate takes its _liquidateVAI branch, pulling the VAI + // ERC20 from us. RFQ quotes bStock->USDT only, so VAI is inherently two-hop (bStock -> USDT -> VAI), + // with the Peg Stability Module standing in as hop 2 (mocked here as a plain USDT->VAI router). + describe("VAI debt (VAIController-routed, PSM hop 2)", () => { + let psm: Contract; + + function psmSwapCalldata(amountIn: BigNumber, to: string) { + return psm.interface.encodeFunctionData("swap", [usdt.address, amountIn, vai.address, to]); + } + function vaiParams(over: Partial = {}) { + return params({ + vDebt: vaiController.address, // VAI is repaid through the VAIController, not a vToken + router2: psm.address, + swapCalldata2: psmSwapCalldata(SEIZED, liq.address), + intermediateToken: usdt.address, + ...over, + }); + } + + beforeEach(async () => { + psm = await (await ethers.getContractFactory("MockNativeRouter")).deploy(); + await liq.connect(owner).setRouter(psm.address, true); + await vai.mint(psm.address, OUT); // the PSM mints VAI for the USDT it pulls + }); + + it("inventory: repays VAI, sells bStock -> USDT -> VAI, keeps the incentive", async () => { + await vai.mint(liq.address, REPAY); // pre-funded VAI inventory + + expect(await liq.connect(owner).callStatic.liquidate(vaiParams())).to.equal(OUT); + + await expect(liq.connect(owner).liquidate(vaiParams())) + .to.emit(liq, "Liquidated") + .withArgs(borrower.address, vBStock.address, vaiController.address, REPAY, SEIZED, OUT, false); + + expect(await vai.balanceOf(liq.address)).to.equal(OUT); // profit = OUT - REPAY + expect(await usdt.balanceOf(liq.address)).to.equal(0); // intermediate fully consumed + expect(await bStock.balanceOf(liq.address)).to.equal(0); + // The repay reached the gate as the VAI ERC20 (its _liquidateVAI branch). + expect(await vai.balanceOf(venusLiq.address)).to.equal(REPAY); + // No standing approvals: to the gate (VAI) nor on either hop. + expect(await vai.allowance(liq.address, venusLiq.address)).to.equal(0); + expect(await usdt.allowance(liq.address, psm.address)).to.equal(0); + }); + + it("rejects a single-hop VAI config (router2 == 0)", async () => { + await vai.mint(liq.address, REPAY); + await expect( + liq.connect(owner).liquidate(vaiParams({ router2: ZERO, swapCalldata2: "0x", intermediateToken: ZERO })), + ).to.be.revertedWithCustomError(liq, "InvalidIntermediate"); + }); + + it("rejects flashLiquidate for a VAI debt (VAI is burned; there is no vVAI to flash from)", async () => { + await expect(liq.connect(owner).flashLiquidate(vaiParams())).to.be.revertedWithCustomError( + liq, + "FlashNotSupportedForVai", + ); + }); + + it("enforces minOut in VAI when the PSM hop underdelivers", async () => { + await vai.mint(liq.address, REPAY); + await psm.setRate(U("0.9")); // 5500 USDT -> 4950 VAI, below the 5400 minOut + await expect(liq.connect(owner).liquidate(vaiParams())).to.be.revertedWithCustomError(liq, "InsufficientOut"); + expect(await vai.balanceOf(liq.address)).to.equal(REPAY); // reverted, inventory intact + }); + }); + // Native BNB debt. vBNB has no underlying() and its borrow is repaid in native BNB, so WBNB is the // debt-accounting token: only the repay amount is unwrapped (WBNB -> BNB) for the gate's payable path, // and the two-hop swap lands WBNB (bStock -> USDT -> WBNB). FLASH mode borrows from vWBNB (an ERC20 From 90fed52b1e0feab087c9b7f315e915106713c939 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 15 Jul 2026 18:37:13 +0530 Subject: [PATCH 59/78] fix(bstock): stop safe-fallback misreading a VAI debt as native BNB It detected vBNB by catching underlying(), which the VAIController also lacks, so a VAI debt built a {value: repay} batch the gate rejects. Detect VAI explicitly and use its own seize math (liquidateVAICalculateSeizeTokens, borrower-agnostic incentive). This path ships the seized bStock instead of swapping it, so it is the fallback when the atomic path's PSM hop is down. --- scripts/bstock/safe-fallback.ts | 60 +++++++++++++++++++++-------- tests/hardhat/BStockSafeFallback.ts | 29 +++++++++++++- 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/scripts/bstock/safe-fallback.ts b/scripts/bstock/safe-fallback.ts index 121f72d1d..4e2cc91e7 100644 --- a/scripts/bstock/safe-fallback.ts +++ b/scripts/bstock/safe-fallback.ts @@ -22,7 +22,14 @@ * Action 2 — hand off: * 4. bStock.transfer(TARGET, seizedRaw) // raw bStock -> Binance top-up * - * Seize amount is the on-chain truth from `Comptroller.liquidateCalculateSeizeTokens`. The Venus + * VAI debt (VAIController): supported. VAI is not a vToken and has no `underlying()`, so the debt token + * is resolved via `getVAIAddress()`; the batch is the normal ERC20 shape (approve VAI to the gate, then a + * zero-value liquidateBorrow), which the gate settles through its `_liquidateVAI` branch. Because this + * script ships the seized bStock to Binance rather than swapping it, no PSM/USDT->VAI leg is involved — + * making it the manual fallback when the atomic path's PSM hop is paused or capped. + * + * Seize amount is the on-chain truth from `Comptroller.liquidateCalculateSeizeTokens` (or + * `liquidateVAICalculateSeizeTokens` for VAI). The Venus * Liquidator keeps a treasury cut of the liquidation bonus, so the Safe is credited fewer vTokens than * `seizeTokens`; we redeem only that credited amount (`received`). Snapshotted at the current block — if * the borrower's position changes before the Safe executes, REGENERATE, else a stale redeem/transfer @@ -70,9 +77,15 @@ const COMPTROLLER_ABI = [ "function liquidationIncentiveMantissa() view returns (uint256)", "function liquidatorContract() view returns (address)", "function liquidateCalculateSeizeTokens(address,address,address,uint256) view returns (uint256,uint256)", + // VAI's seize math is a separate function: VAI is priced at $1 and the incentive is the + // borrower-agnostic getLiquidationIncentive (see ComptrollerLens.liquidateVAICalculateSeizeTokens). + "function liquidateVAICalculateSeizeTokens(address,uint256) view returns (uint256,uint256)", "function treasuryPercent() view returns (uint256)", + "function vaiController() view returns (address)", "function getEffectiveLiquidationIncentive(address,address) view returns (uint256)", + "function getLiquidationIncentive(address) view returns (uint256)", ]; +const VAI_CONTROLLER_ABI = ["function getVAIAddress() view returns (address)"]; const LIQUIDATOR_ABI = ["function treasuryPercentMantissa() view returns (uint256)"]; const ZERO = "0x0000000000000000000000000000000000000000"; @@ -91,15 +104,27 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { const vDebt = new Contract(env("VDEBT"), VTOKEN_ABI, provider); const comptroller = new Contract(await vBStock.comptroller(), COMPTROLLER_ABI, provider); + // VAI is not a vToken: its "market" is the VAIController, which has no underlying() EITHER. It must be + // detected BEFORE the vBNB fallback below — otherwise the catch would misread a VAI debt as native BNB + // and build a `{value: repay}` batch that the gate rejects (its VAI branch requires msg.value == 0). + // The VAI token itself is a plain ERC20, so it takes the normal approve-then-liquidate batch below. + const vaiControllerAddr: string = await comptroller.vaiController(); + const isVai = vDebt.address.toLowerCase() === vaiControllerAddr.toLowerCase(); + // vBNB has no underlying(): a native-BNB debt market is repaid in native BNB, so there is no ERC20 // debt token to approve or read. Detect it the way the atomic script does — try underlying(), treat // a revert as native BNB (18 decimals, "BNB"). `debt` stays undefined on the native path. let isBnb = false; let debt: Contract | undefined; - try { - debt = new Contract(await vDebt.underlying(), ERC20_ABI, provider); - } catch { - isBnb = true; + if (isVai) { + const vaiAddr: string = await new Contract(vaiControllerAddr, VAI_CONTROLLER_ABI, provider).getVAIAddress(); + debt = new Contract(vaiAddr, ERC20_ABI, provider); + } else { + try { + debt = new Contract(await vDebt.underlying(), ERC20_ABI, provider); + } catch { + isBnb = true; + } } const bStock = new Contract(await vBStock.underlying(), ERC20_ABI, provider); @@ -138,15 +163,16 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { // --- seize math from on-chain truth --- const ONE = BigNumber.from(10).pow(18); - // Borrower-aware 4-arg overload (reads the borrower's actual pool via getEffectiveLiquidationIncentive), - // matching vToken.liquidateBorrowFresh on-chain. The 3-arg overload always reads Core Pool params and - // diverges if the borrower has switched pools, producing a stale redeem amount in the batch. - const [seizeErr, seizeTokens]: BigNumber[] = await comptroller.liquidateCalculateSeizeTokens( - borrower, - vDebt.address, - vBStock.address, - repay, - ); + // Mirror the function the on-chain path actually calls for this debt: + // - VAI -> VAIController.liquidateVAIFresh calls liquidateVAICalculateSeizeTokens(vCollateral, + // repay): VAI is priced at $1 and the incentive is the borrower-agnostic + // getLiquidationIncentive, so the 4-arg overload does NOT apply. + // - else -> vToken.liquidateBorrowFresh calls the borrower-aware 4-arg overload (reads the + // borrower's actual pool). The 3-arg overload always reads Core Pool params and diverges + // if the borrower has switched pools, producing a stale redeem amount in the batch. + const [seizeErr, seizeTokens]: BigNumber[] = isVai + ? await comptroller.liquidateVAICalculateSeizeTokens(vBStock.address, repay) + : await comptroller.liquidateCalculateSeizeTokens(borrower, vDebt.address, vBStock.address, repay); if (!seizeErr.eq(0)) throw new Error(`liquidateCalculateSeizeTokens error code ${seizeErr}`); // A zero seize means the incentive resolved to 0 (e.g. bStock unlisted in the borrower's pool): // surface it here rather than baking a degenerate redeem amount into the batch. @@ -158,7 +184,11 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { let vReceived = seizeTokens; const liqTreasuryPct: BigNumber = await new Contract(gate, LIQUIDATOR_ABI, provider).treasuryPercentMantissa(); if (!liqTreasuryPct.eq(0)) { - const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); + // Use the SAME incentive the seize above was computed with, else the bonus (and so the cut) is + // sized against the wrong number: VAI's seize math uses the borrower-agnostic getLiquidationIncentive. + const totalIncentive: BigNumber = isVai + ? await comptroller.getLiquidationIncentive(vBStock.address) + : await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); const bonusAmount = seizeTokens.mul(totalIncentive.sub(ONE)).div(totalIncentive); const treasuryCut = bonusAmount.mul(liqTreasuryPct).div(ONE); vReceived = seizeTokens.sub(treasuryCut); diff --git a/tests/hardhat/BStockSafeFallback.ts b/tests/hardhat/BStockSafeFallback.ts index b5fcc2801..9acf9a0dc 100644 --- a/tests/hardhat/BStockSafeFallback.ts +++ b/tests/hardhat/BStockSafeFallback.ts @@ -2,7 +2,7 @@ // BStock mocks and asserts the emitted Transaction Builder batch. Covers the gate routing (T1), the // Venus Liquidator bonus-cut deduction, and the redeem treasuryPercent fee (T2). import { expect } from "chai"; -import { Contract } from "ethers"; +import { BigNumber, Contract } from "ethers"; import { ethers } from "hardhat"; import { buildSafeFallbackBatch } from "../../scripts/bstock/safe-fallback"; @@ -94,6 +94,33 @@ describe("BStock safe-fallback batch generator", () => { expect(seizedRaw).to.equal(SEIZE); // cut 0, treasuryPercent 0 → full seize at 1:1 rate }); + it("VAI debt: approves the VAI token and emits a zero-value batch (not misread as native BNB)", async () => { + // VAI has no underlying(), same as vBNB — the script must detect it explicitly, else the vBNB + // fallback would build a `{value: repay}` batch the gate rejects. No PSM leg here: this path ships + // the seized bStock rather than swapping it, so it works even when the atomic path's PSM hop is down. + const vai = await (await ethers.getContractFactory("MockMintableERC20")).deploy("Venus VAI", "VAI", 18); + const vaiController = await (await ethers.getContractFactory("MockVAIController")).deploy(vai.address); + await comptroller.setVaiController(vaiController.address); + await venusLiq.setVaiController(vaiController.address); + + setEnv({ VDEBT: vaiController.address }); + const { txs, vReceived } = await buildSafeFallbackBatch(ethers.provider); + + // ERC20 shape (4 txs incl. the approve) — a BNB misread would drop the approve and use value. + expect(txs).to.have.length(4); + expect(ethers.utils.getAddress(txs[0].to)).to.equal(vai.address); // approve the VAI token itself + const approve = ethers.utils.defaultAbiCoder.decode(["address", "uint256"], "0x" + txs[0].data.slice(10)); + expect(approve[0]).to.equal(venusLiq.address); // the gate is the repay spender + expect(approve[1]).to.equal(REPAY); + + // liquidateBorrow routes through the gate with ZERO value (the gate's VAI branch requires msg.value == 0). + expect(ethers.utils.getAddress(txs[1].to)).to.equal(venusLiq.address); + expect(txs[1].data.slice(0, 10)).to.equal(SEL_ROUTED); + expect(BigNumber.from(txs[1].value)).to.equal(0); + + expect(vReceived).to.equal(SEIZE); // seize via the VAI 2-arg math, cut 0 + }); + it("throws when the gate is unset, aligning with the on-chain liquidator", async () => { await comptroller.setLiquidatorContract(ethers.constants.AddressZero); setEnv(); From 3ab7e4f062f789e1f3aba98373ff901e80cae4f0 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 16 Jul 2026 18:27:48 +0530 Subject: [PATCH 60/78] feat(bstock): build the VAI hop-2 PSM calldata in the script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The contract and its tests already specified the Peg Stability Module (swapStableForVAI) as router2 for a VAI debt, but atomic-liquidate.ts had no code to construct that calldata — a VAI run fell through to the AMM aggregator path. Add lib/psm.ts: encodes swapStableForVAI locally (receiver = the liquidator, amountIn = the hop-1 floor), derives expectedOut from previewSwapStableForVAI, and pre-flights isPaused and mint-cap headroom. Reject MODE=flash for VAI before quoting, mirroring the contract's FlashNotSupportedForVai. --- contracts/test/BStockLiquidationMocks.sol | 46 ++++++++ scripts/bstock/README.md | 1 + scripts/bstock/atomic-liquidate.ts | 36 +++++-- scripts/bstock/lib/psm.ts | 66 ++++++++++++ tests/hardhat/BStockAtomicLiquidate.ts | 121 ++++++++++++++++++++++ 5 files changed, 259 insertions(+), 11 deletions(-) create mode 100644 scripts/bstock/lib/psm.ts diff --git a/contracts/test/BStockLiquidationMocks.sol b/contracts/test/BStockLiquidationMocks.sol index ee3bdb18c..bb317e11c 100644 --- a/contracts/test/BStockLiquidationMocks.sol +++ b/contracts/test/BStockLiquidationMocks.sol @@ -337,6 +337,52 @@ contract MockSplitRouter { } } +/// @dev Peg Stability Module stand-in exposing the REAL PSM surface the off-chain script encodes +/// against (`swapStableForVAI` / `previewSwapStableForVAI` / `isPaused` / `vaiMintCap` / +/// `vaiMinted`) — unlike the generic router mocks, so the script's locally-built calldata is what +/// executes here. `rateMantissa` mirrors the PSM's IN-direction pricing (min($1, oracle), minus +/// feeIn): out = amountIn * rateMantissa / 1e18. Pays VAI by minting, like the real PSM. +contract MockPSM { + address public immutable stableToken; + MockMintableERC20 public immutable vai; + + uint256 public rateMantissa = 1e18; + bool public isPaused; + uint256 public vaiMintCap = type(uint256).max; + uint256 public vaiMinted; + + constructor(address stableToken_, MockMintableERC20 vai_) { + stableToken = stableToken_; + vai = vai_; + } + + function setRate(uint256 r) external { + rateMantissa = r; + } + + function setPaused(bool p) external { + isPaused = p; + } + + function setVaiMintCap(uint256 c) external { + vaiMintCap = c; + } + + function previewSwapStableForVAI(uint256 stableTknAmount) public view returns (uint256) { + return (stableTknAmount * rateMantissa) / 1e18; + } + + function swapStableForVAI(address receiver, uint256 stableTknAmount) external returns (uint256) { + require(!isPaused, "psm paused"); + uint256 vaiToMint = previewSwapStableForVAI(stableTknAmount); + require(vaiMinted + vaiToMint <= vaiMintCap, "mint cap reached"); + vaiMinted += vaiToMint; + require(ERC20(stableToken).transferFrom(msg.sender, address(this), stableTknAmount), "stable pull failed"); + vai.mint(receiver, vaiToMint); + return vaiToMint; + } +} + /// @dev Stand-in for the pool-wide Venus Liquidator (`liquidatorContract`). Pulls the repay from the /// caller and credits the seized collateral (repay * incentive) to the caller, minus a treasury cut. contract MockVenusLiquidator { diff --git a/scripts/bstock/README.md b/scripts/bstock/README.md index 7a29f76e7..de7c6d142 100644 --- a/scripts/bstock/README.md +++ b/scripts/bstock/README.md @@ -127,6 +127,7 @@ call `liquidate`/`flashLiquidate`. | `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) | | `SEIZE_BUFFER` | | `0.1` | Haircut on the QUOTED seize (%) so an oracle uptick before inclusion can't make the router pull more bStock than was seized (→ `SwapFailed`); the unsold remainder stays as bStock inventory (sweepable) | | `AMM_PROVIDER` | | `kyberswap` | Hop-2 route for non-USDT debt: `kyberswap` / `openocean` / `pcsv2` | +| `PSM_ADDR` | | BSC PSM | Peg Stability Module used as hop 2 for a VAI debt (`swapStableForVAI` calldata encoded locally, expected out from `previewSwapStableForVAI`); must be allowlisted via `setRouter`. `MODE=flash` rejected | | `WBNB_ADDR` | | BSC WBNB | Only for a vBNB debt market (native BNB auto-detected; contract unwraps) | _Fork/local testing:_ `MOCK_NATIVE="router:calldata"` (hop 1), `MOCK_AMM` (hop 2), `MOCK_OUT` (final diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 09e1db020..d0411ceeb 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -50,6 +50,9 @@ * Keep it small: in MODE=flash the quoted proceeds must still cover principal + premium, * so an oversized buffer trips the on-chain InsufficientOut (run DRY_RUN first). * AMM_PROVIDER hop-2 route source for non-USDT debt: kyberswap (default) | openocean | pcsv2 + * PSM_ADDR Peg Stability Module used as hop 2 for a VAI debt (default: BSC mainnet + * PegStability_USDT). Must be allowlisted via setRouter; calldata is encoded + * locally (lib/psm.ts). MODE=flash is rejected for VAI (no vVAI to flash from). * DRY_RUN "1" -> callStatic only, send nothing * MOCK_NATIVE hop-1 "router:calldata" for fork/local tests (see below) * MOCK_AMM hop-2 "router:calldata" for fork/local tests (two-hop); MOCK_OUT = final debt out @@ -62,6 +65,7 @@ import { ethers } from "hardhat"; import { BSC_WBNB, getAmmSwap } from "./lib/amm"; import { BSC_USDT } from "./lib/native"; +import { getPsmSwap } from "./lib/psm"; import { QuoteArgs, selectedSources } from "./lib/sources"; const PARAMS_TUPLE = @@ -229,6 +233,12 @@ export async function atomicLiquidate(signer: Signer) { const repay = ethers.utils.parseUnits(env("REPAY_AMOUNT"), debtDec); if (isBnb) console.log(`native BNB debt: accounting in WBNB ${debt.address} (contract unwraps the repay)`); + // Mirrors the contract's FlashNotSupportedForVai: VAI is minted/burned by the VAIController and has + // no vVAI market to flash from. Fail here, before burning a hop-1 quote on a call that must revert. + if (isVai && mode === "flash") { + throw new Error("MODE=flash is not supported for a VAI debt (no vVAI to flash from) — use MODE=inventory"); + } + // 0. liquidatable? const [, , shortfall]: BigNumber[] = await comptroller.getAccountLiquidity(borrower); if (shortfall.eq(0)) throw new Error(`${borrower} has no shortfall — not liquidatable`); @@ -356,23 +366,27 @@ export async function atomicLiquidate(signer: Signer) { ); if (twoHop) { - // Hop 2: convert the hop-1 USDT to the (non-USDT) debt asset via an allowlisted AMM/aggregator. + // Hop 2: convert the hop-1 USDT to the (non-USDT) debt asset. For VAI the leg is the Peg Stability + // Module (`swapStableForVAI` mints VAI from USDT at the oracle rate; calldata encoded locally in + // lib/psm.ts — no aggregator involved); every other debt goes through an allowlisted AMM/aggregator. // Size this leg off the hop-1 FLOOR, not the indicative `out`: on-chain the contract approves router2 // for the ACTUAL hop-1 delta (`midDelta`), while this calldata bakes in a fixed `amountIn`. Quote it // at `out` and an indicative source that fills even slightly under would leave the router pulling // more than the approval — hop 2 reverts on allowance. `floor <= midDelta` always holds, so the pull // always fits. Any surplus (`midDelta - floor`, bounded by slippage) stays as USDT inventory and the // contract emits `PartialSwapLeftover` for it — sweepable, not lost. - const amm = await getAmmSwap( - { - tokenIn: nativeOut, - tokenOut: debt.address, - amountIn: midFloor.toString(), - recipient: liquidator.address, - slippage, - }, - ethers.provider, - ); + const amm = isVai + ? await getPsmSwap({ amountIn: midFloor, recipient: liquidator.address }, ethers.provider) + : await getAmmSwap( + { + tokenIn: nativeOut, + tokenOut: debt.address, + amountIn: midFloor.toString(), + recipient: liquidator.address, + slippage, + }, + ethers.provider, + ); router2 = ethers.utils.getAddress(amm.router); swapCalldata2 = amm.calldata; intermediateToken = nativeOut; diff --git a/scripts/bstock/lib/psm.ts b/scripts/bstock/lib/psm.ts new file mode 100644 index 000000000..0def04b8d --- /dev/null +++ b/scripts/bstock/lib/psm.ts @@ -0,0 +1,66 @@ +/** + * Peg Stability Module client for the VAI hop-2 leg (USDT -> VAI). + * + * A VAI debt is inherently two-hop (RFQ sources quote bStock->USDT only) and its hop 2 is the PSM, + * not an AMM: `swapStableForVAI` mints VAI against USDT at the oracle rate (min($1, price) on the + * way in, minus feeIn), so there is no pool depth to eat or sandwich. Unlike lib/amm.ts there is no + * quote API either — the calldata is a fixed function encoded locally, and the "quote" is the PSM's + * own `previewSwapStableForVAI` view. The PSM must be allowlisted on the liquidator (`setRouter`) + * like any hop-2 router. + * + * Returns the same `{ router, calldata, expectedOut }` shape as lib/amm.ts, sized off the hop-1 + * FLOOR (see atomic-liquidate.ts): the PSM pulls exactly `amountIn` via `safeTransferFrom`, which + * always fits inside the contract's `midDelta` approval because `floor <= midDelta`. + */ +import { BigNumber, Contract, providers, utils } from "ethers"; + +import { AmmSwap } from "./amm"; + +// Venus PegStability_USDT proxy on BSC mainnet. +export const BSC_PSM_USDT = "0xC138aa4E424D1A8539e8F38Af5a754a2B7c3Cc36"; + +const PSM_ABI = [ + "function swapStableForVAI(address receiver, uint256 stableTknAmount) returns (uint256)", + "function previewSwapStableForVAI(uint256 stableTknAmount) view returns (uint256)", + "function isPaused() view returns (bool)", + "function vaiMintCap() view returns (uint256)", + "function vaiMinted() view returns (uint256)", +]; + +export interface PsmSwapParams { + amountIn: BigNumber; // wei of USDT to sell — the hop-1 floor + recipient: string; // where the VAI must land (the liquidator contract, where minOut reads it) +} + +/** Build the PSM hop-2 swap: pre-flight the pause/mint-cap state, then encode `swapStableForVAI`. */ +export async function getPsmSwap(p: PsmSwapParams, provider: providers.Provider): Promise { + const psmAddr = utils.getAddress(process.env.PSM_ADDR || BSC_PSM_USDT); + const psm = new Contract(psmAddr, PSM_ABI, provider); + + // Fail legibly off-chain instead of a bare on-chain revert. safe-fallback.ts is the documented + // manual path when the PSM leg is unavailable. + if (await psm.isPaused()) { + throw new Error(`PSM ${psmAddr} is paused — VAI hop 2 unavailable (use safe-fallback)`); + } + + // The PSM's cap check charges the USD value of the pull (`vaiMinted + amountUSD > vaiMintCap`) + // and its IN-direction price is min($1, oracle), so `amountIn` (USDT is 18 decimals on BSC) + // bounds that USD value from above: headroom >= amountIn can never trip VAIMintCapReached. + const [cap, minted] = await Promise.all([psm.vaiMintCap(), psm.vaiMinted()]); + const headroom: BigNumber = cap.sub(minted); + if (headroom.lt(p.amountIn)) { + throw new Error( + `PSM mint-cap headroom ${utils.formatEther(headroom)} VAI < hop-1 floor ` + + `${utils.formatEther(p.amountIn)} USDT — reduce REPAY_AMOUNT or use safe-fallback`, + ); + } + + // expectedOut is the PSM's own preview (oracle rate minus feeIn), NOT 1:1: USDT usually prices a + // hair under $1, and a 1:1 assumption would put the derived minOut above what the PSM can deliver. + const expectedOut: BigNumber = await psm.previewSwapStableForVAI(p.amountIn); + return { + router: psmAddr, + calldata: psm.interface.encodeFunctionData("swapStableForVAI", [p.recipient, p.amountIn]), + expectedOut: expectedOut.toString(), + }; +} diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index 422da59f6..8a3c9f135 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -42,6 +42,7 @@ const SCRIPT_ENV = [ "SOURCE", "LM_API_KEY", "LM_PRIVATE_KEY_SEED", + "PSM_ADDR", ] as const; describe("bStock atomic liquidation script", () => { @@ -308,6 +309,126 @@ describe("bStock atomic liquidation script", () => { expect(await usdt.balanceOf(liq.address)).to.equal(0); // intermediate consumed }); + // VAI debt: VDEBT is the VAIController (no underlying(); resolved via getVAIAddress()). RFQ sources + // quote bStock->USDT only, so VAI is inherently two-hop — and hop 2 is the Peg Stability Module, whose + // `swapStableForVAI` calldata the script must construct ITSELF (lib/psm.ts: encoded locally, expected + // out from previewSwapStableForVAI — no aggregator API). MockPSM exposes the real PSM surface, so what + // executes on-chain is exactly the blob the script built. + describe("VAI debt (PSM hop 2 built by the script)", () => { + let vai: Contract, vaiController: Contract, psm: Contract; + + beforeEach(async () => { + const ERC20 = await ethers.getContractFactory("MockMintableERC20"); + vai = await ERC20.deploy("Vai Stablecoin", "VAI", 18); + vaiController = await (await ethers.getContractFactory("MockVAIController")).deploy(vai.address); + await comptroller.setVaiController(vaiController.address); + const venusLiq = await ethers.getContractAt("MockVenusLiquidator", await comptroller.liquidatorContract()); + await venusLiq.setVaiController(vaiController.address); + + psm = await (await ethers.getContractFactory("MockPSM")).deploy(usdt.address, vai.address); + await liq.connect(owner).setRouter(psm.address, true); + }); + + // Real hop-1 source path (fetch stubbed with a firm Native quote), so the script reaches the REAL + // hop-2 build — the PSM branch under test. MOCK_NATIVE would bypass it. + function stubNativeFetch() { + const real = globalThis.fetch; + globalThis.fetch = (async () => ({ + json: async () => ({ + success: true, + recipient: liq.address, + amountIn: "0", + amountOut: OUT.toString(), + orders: [{ deadlineTimestamp: Math.floor(Date.now() / 1000) + 3600 }], + txRequest: { target: router.address, calldata: swapAllCalldata(liq.address), value: "0" }, + }), + })) as unknown as typeof fetch; + return real; + } + + function vaiEnv(over: Record = {}) { + setEnv({ + VDEBT: vaiController.address, + MOCK_NATIVE: "", + MOCK_OUT: "", + NATIVE_API_KEY: "test-key", + PSM_ADDR: psm.address, + ...over, + }); + } + + it("builds the swapStableForVAI calldata itself and settles through the PSM", async () => { + await vai.mint(liq.address, REPAY); // pre-funded VAI inventory + const real = stubNativeFetch(); + try { + vaiEnv(); + await atomicLiquidate(owner); + } finally { + globalThis.fetch = real; + } + + // Hop 1 is firm, so its floor == OUT: the PSM pulled exactly OUT USDT and minted OUT VAI (rate 1:1) + // to the contract. Started with REPAY VAI, repaid REPAY, received OUT -> profit retained as VAI. + expect(await usdt.balanceOf(psm.address)).to.equal(OUT); + expect(await psm.vaiMinted()).to.equal(OUT); + expect(await vai.balanceOf(liq.address)).to.equal(OUT); + expect(await usdt.balanceOf(liq.address)).to.equal(0); // intermediate fully consumed + expect(await bStock.balanceOf(liq.address)).to.equal(0); + }); + + it("derives minOut from previewSwapStableForVAI, not a 1:1 assumption", async () => { + await vai.mint(liq.address, REPAY); + // Oracle prices USDT under $1: the PSM mints 0.999 VAI per USDT. A 1:1 expectedOut would put + // minOut (0.5% buffer) above the 5494.5 delivered and revert InsufficientOut; the preview-derived + // minOut clears it. + await psm.setRate(U("0.999")); + const real = stubNativeFetch(); + try { + vaiEnv(); + await atomicLiquidate(owner); + } finally { + globalThis.fetch = real; + } + + const delivered = OUT.mul(U("0.999")).div(U("1")); + expect(await vai.balanceOf(liq.address)).to.equal(delivered); // repay spent, preview-out received + }); + + it("fails legibly before settling when the PSM is paused", async () => { + await vai.mint(liq.address, REPAY); + await psm.setPaused(true); + const real = stubNativeFetch(); + try { + vaiEnv(); + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/paused/i); + } finally { + globalThis.fetch = real; + } + expect(await vai.balanceOf(liq.address)).to.equal(REPAY); // untouched + }); + + it("fails legibly when the PSM mint-cap headroom cannot cover the hop-1 floor", async () => { + await vai.mint(liq.address, REPAY); + await psm.setVaiMintCap(U("100")); // headroom 100 < floor 5500 + const real = stubNativeFetch(); + try { + vaiEnv(); + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/mint-cap/i); + } finally { + globalThis.fetch = real; + } + expect(await vai.balanceOf(liq.address)).to.equal(REPAY); // untouched + }); + + it("rejects MODE=flash for a VAI debt before quoting (mirrors FlashNotSupportedForVai)", async () => { + await vai.mint(liq.address, REPAY); + vaiEnv({ MODE: "flash" }); + // No fetch stub: the guard must fire before any hop-1 quote is requested. + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/flash.*VAI/i); + expect(await vai.balanceOf(liq.address)).to.equal(REPAY); // untouched + }); + }); + // ------------------------------------------------------------------------ // // Liquidity-source registry (lib/sources.ts): Native vs Liquid Mesh // // ------------------------------------------------------------------------ // From 1baf78f22f77016ade94a0ae143368b175c7a5b1 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 16 Jul 2026 18:53:38 +0530 Subject: [PATCH 61/78] test(bstock): fork-test the VAI PSM hop against the real PegStability module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Settles a real underwater VAI position on a bscmainnet fork: bStock collateral on the live diamond, VAI minted through the real VAIController (prime-only flag flipped via the real ACM + timelock), repay through the real gate's VAI branch, and hop 2 executed with the exact swapStableForVAI calldata lib/psm.ts builds — asserting the real PSM mints exactly its previewSwapStableForVAI amount to the contract. A fresh proxy is used because the deployed one predates VAI support. Also proves getPsmSwap refuses to build when the real PSM is paused. --- tests/hardhat/Fork/BStockLiquidatorFork.ts | 128 +++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts index 0594aaa49..b3f723adb 100644 --- a/tests/hardhat/Fork/BStockLiquidatorFork.ts +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -32,6 +32,7 @@ import { parseEther, parseUnits } from "ethers/lib/utils"; import fc from "fast-check"; import { ethers, upgrades } from "hardhat"; +import { getPsmSwap } from "../../../scripts/bstock/lib/psm"; import { A, Action, @@ -64,6 +65,23 @@ const DEPLOYED_LIQ = "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1"; const VUSDT = "0xfD5840Cd36d94D7229439859C0112a4185BC0255"; const VCAKE = "0x86aC3974e2BD0d60825230fa6F355fF11409df5c"; +// VAI debt leg: the real VAIController (the "market" a VAI debt is keyed on), the VAI ERC20, and the +// real Peg Stability Module that lib/psm.ts targets as hop 2. +const VAI_CONTROLLER = "0x004065D34C6b18cE4370ced1CeBDE94865DbFAFE"; +const VAI = "0x4BD17003473389A42DAF6a0a729f6Fdb328BbBd7"; +const PSM_USDT = "0xC138aa4E424D1A8539e8F38Af5a754a2B7c3Cc36"; +const VAI_CONTROLLER_ABI = [ + "function mintVAI(uint256) returns (uint256)", + "function getVAIRepayAmount(address) view returns (uint256)", + "function toggleOnlyPrimeHolderMint() returns (uint256)", +]; +const ACM_GIVE_ABI = ["function giveCallPermission(address,string,address)"]; +const PSM_FORK_ABI = [ + "function vaiMinted() view returns (uint256)", + "function isPaused() view returns (bool)", + "function pause()", +]; + // bStock USD prices: healthy vs post-crash (the stock gapped down ~80%). const P_HEALTHY = parseUnits("250", 18); const P_CRASH = parseUnits("50", 18); @@ -73,6 +91,7 @@ const B = { USDT: ethers.utils.getAddress("0x00000000000000000000000000000000ca110001"), CAKE: ethers.utils.getAddress("0x00000000000000000000000000000000ca110002"), BNB: ethers.utils.getAddress("0x00000000000000000000000000000000ca110003"), + VAI: ethers.utils.getAddress("0x00000000000000000000000000000000ca11000a"), FLASH: ethers.utils.getAddress("0x00000000000000000000000000000000ca110004"), FORCED: ethers.utils.getAddress("0x00000000000000000000000000000000ca110005"), DEADLINE: ethers.utils.getAddress("0x00000000000000000000000000000000ca110006"), @@ -326,6 +345,115 @@ const test = () => { expect(await ethers.provider.getBalance(liq.address)).to.equal(0); }); + it("two-hop VAI debt, inventory mode: bStock->USDT (mock) -> VAI through the REAL PSM, script-built calldata", async () => { + // VAI minting is prime-holder-gated at the fork block; flip the flag through the real ACM + + // timelock so a plain borrower can take on VAI debt (a governance-reachable state). + const timelock = await initMainnetUser(A.TIMELOCK, parseEther("5")); + await new ethers.Contract(A.ACM, ACM_GIVE_ABI, timelock).giveCallPermission( + VAI_CONTROLLER, + "toggleOnlyPrimeHolderMint()", + owner.address, + ); + const vaiCtrl = new ethers.Contract(VAI_CONTROLLER, VAI_CONTROLLER_ABI, owner); + await vaiCtrl.toggleOnlyPrimeHolderMint(); + + // Underwater VAI borrower: supply bStock, enter the market, mint VAI against it, stock gaps down. + // (makeUnderwaterBorrower borrows from a vToken; VAI debt is minted on the VAIController instead.) + const borrower = await initMainnetUser(B.VAI, parseUnits("10", 18)); + const COLLATERAL = parseUnits("100", 18); + await mkt.bStock.mint(B.VAI, COLLATERAL); + await mkt.bStock.connect(borrower).approve(mkt.vBStock.address, COLLATERAL); + await new ethers.Contract(mkt.vBStock.address, VTOKEN_ABI, borrower).mint(COLLATERAL); + await new ethers.Contract( + A.COMPTROLLER, + ["function enterMarkets(address[]) returns (uint256[])"], + borrower, + ).enterMarkets([mkt.vBStock.address]); + // mintVAI returns an error CODE (legacy style) — probe first so a cap/pause surfaces loudly. + const MINTED = parseUnits("4000", 18); + const mintCode: BigNumber = await vaiCtrl.connect(borrower).callStatic.mintVAI(MINTED); + if (!mintCode.eq(0)) throw new Error(`mintVAI returned code ${mintCode.toString()}`); + await vaiCtrl.connect(borrower).mintVAI(MINTED); + expect(await vaiCtrl.getVAIRepayAmount(B.VAI)).to.be.gte(MINTED); + await mkt.setPrice(P_CRASH); + await mock.setRate(P_CRASH); + + // The DEPLOYED proxy predates VAI support (it reads `underlying()` on every vDebt, which the + // VAIController lacks), so this scenario deploys a FRESH proxy on the current implementation and + // drives it against the same real gate, real VAIController, and real PSM. + const Factory = await ethers.getContractFactory("BStockLiquidator"); + const liqVai = await upgrades.deployProxy(Factory, [owner.address], { + constructorArgs: [A.COMPTROLLER, A.VBNB, A.VWBNB, TOK.WBNB], + unsafeAllow: ["constructor", "state-variable-immutable"], + }); + await liqVai.connect(owner).setRouter(mock.address, true); // hop-1 (Native RFQ mock) + + const REPAY = parseUnits("1500", 18); // VAI (< closeFactor * debt = 2000) + await fund(VAI, liqVai.address, REPAY); // pre-funded VAI inventory (flash is not supported for VAI) + await liqVai.connect(owner).setRouter(PSM_USDT, true); // hop-2 router: the real PSM + + // Size hop 2 exactly as the script does — off the hop-1 output, undershot 10% like the PCS leg, + // so the fixed pull always fits under the on-chain approval — then let the SCRIPT's builder + // (lib/psm.ts) produce the calldata: what settles on the fork is the blob it constructs against + // the real PSM, expected out from the real previewSwapStableForVAI. + const comptroller = new ethers.Contract( + A.COMPTROLLER, + ["function liquidateVAICalculateSeizeTokens(address,uint256) view returns (uint256,uint256)"], + owner, + ); + const [, seizeTokens] = await comptroller.liquidateVAICalculateSeizeTokens(mkt.vBStock.address, REPAY); + const xr: BigNumber = await mkt.vBStock.exchangeRateStored(); + const usdtFromHop1 = seizeTokens.mul(xr).div(ONE).mul(P_CRASH).div(ONE); + const floor = usdtFromHop1.mul(90).div(100); + const psmSwap = await getPsmSwap({ amountIn: floor, recipient: liqVai.address }, ethers.provider); + expect(psmSwap.router).to.equal(PSM_USDT); + const expectedOut = BigNumber.from(psmSwap.expectedOut); + + const params = { + borrower: B.VAI, + vDebt: VAI_CONTROLLER, + vBStock: mkt.vBStock.address, + repayAmount: REPAY, + router: mock.address, + swapCalldata: buildSingleHopMock(mkt, mock, liqVai.address), + minOut: expectedOut.mul(999).div(1000), + router2: psmSwap.router, + swapCalldata2: psmSwap.calldata, + intermediateToken: TOK.USDT, + deadline: ethers.constants.MaxUint256, + }; + + const psm = new ethers.Contract(PSM_USDT, PSM_FORK_ABI, owner); + const debtBefore = await vaiCtrl.getVAIRepayAmount(B.VAI); + const mintedBefore: BigNumber = await psm.vaiMinted(); + const out = await liqVai.connect(owner).callStatic.liquidate(params); + expect(out).to.be.gte(params.minOut); + await liqVai.connect(owner).liquidate(params); + + // Debt shrank through the real gate's VAI branch; the real PSM minted exactly its preview to the + // contract; the inventory repay was consumed; no bStock or native BNB stranded. + expect(await vaiCtrl.getVAIRepayAmount(B.VAI)).to.be.lt(debtBefore); + const vai = new ethers.Contract(VAI, ERC20_ABI, owner); + expect(await vai.balanceOf(liqVai.address)).to.equal(expectedOut); + expect((await psm.vaiMinted()).sub(mintedBefore)).to.equal(expectedOut); + expect(await mkt.bStock.balanceOf(liqVai.address)).to.equal(0); + expect(await ethers.provider.getBalance(liqVai.address)).to.equal(0); + }); + + it("getPsmSwap aborts legibly when the real PSM is paused on the fork", async () => { + // Pause the real PSM through the real ACM + timelock, then the script's pre-flight must refuse + // to build the VAI hop instead of producing calldata that reverts on-chain. + const timelock = await initMainnetUser(A.TIMELOCK, parseEther("5")); + await new ethers.Contract(A.ACM, ACM_GIVE_ABI, timelock).giveCallPermission(PSM_USDT, "pause()", owner.address); + const psm = new ethers.Contract(PSM_USDT, PSM_FORK_ABI, owner); + await psm.pause(); + expect(await psm.isPaused()).to.equal(true); + + await expect(getPsmSwap({ amountIn: ONE, recipient: liq.address }, ethers.provider)).to.be.rejectedWith( + /paused/i, + ); + }); + it("minOut breach -> InsufficientOut, FULL rollback: borrow + inventory intact, nothing stranded", async () => { await makeUnderwaterBorrower({ mkt, From 111af24985ba3d34fadddf9332b9baa9dd7d598f Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 16 Jul 2026 19:30:44 +0530 Subject: [PATCH 62/78] test(bstock): script-driven VAI liquidation fork scenario MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runs atomicLiquidate() itself end-to-end on the bscmainnet fork for a constructed VAI-debt borrower (prime-only mint flag flipped through the real ACM + timelock, bStock collateral gapped into shortfall): the script detects the VAIController debt, sizes the seize with the VAI overload, builds the swapStableForVAI calldata against its real default PSM address, derives minOut from the live preview, and settles — the real PSM mints exactly the previewed amount and the undershot hop-1 surplus stays as sweepable USDT. Also asserts the script refuses MODE=flash for VAI before quoting. VAI scaffolding extracted into shared helpers. --- tests/hardhat/Fork/BStockLiquidatorFork.ts | 143 +++++++++++++++++---- 1 file changed, 119 insertions(+), 24 deletions(-) diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts index b3f723adb..05fb759ca 100644 --- a/tests/hardhat/Fork/BStockLiquidatorFork.ts +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -32,6 +32,7 @@ import { parseEther, parseUnits } from "ethers/lib/utils"; import fc from "fast-check"; import { ethers, upgrades } from "hardhat"; +import { atomicLiquidate } from "../../../scripts/bstock/atomic-liquidate"; import { getPsmSwap } from "../../../scripts/bstock/lib/psm"; import { A, @@ -92,6 +93,7 @@ const B = { CAKE: ethers.utils.getAddress("0x00000000000000000000000000000000ca110002"), BNB: ethers.utils.getAddress("0x00000000000000000000000000000000ca110003"), VAI: ethers.utils.getAddress("0x00000000000000000000000000000000ca11000a"), + VAI_SCRIPT: ethers.utils.getAddress("0x00000000000000000000000000000000ca11000b"), FLASH: ethers.utils.getAddress("0x00000000000000000000000000000000ca110004"), FORCED: ethers.utils.getAddress("0x00000000000000000000000000000000ca110005"), DEADLINE: ethers.utils.getAddress("0x00000000000000000000000000000000ca110006"), @@ -345,9 +347,12 @@ const test = () => { expect(await ethers.provider.getBalance(liq.address)).to.equal(0); }); - it("two-hop VAI debt, inventory mode: bStock->USDT (mock) -> VAI through the REAL PSM, script-built calldata", async () => { - // VAI minting is prime-holder-gated at the fork block; flip the flag through the real ACM + - // timelock so a plain borrower can take on VAI debt (a governance-reachable state). + // Shared VAI scaffolding. VAI minting is prime-holder-gated at the fork block, so the flag is + // flipped through the real ACM + timelock (a governance-reachable state, not a hack); the + // borrower is then constructed for real — supply bStock, enter the market, mint VAI on the real + // VAIController, and gap the stock down into shortfall. (makeUnderwaterBorrower borrows from a + // vToken; VAI debt is minted on the VAIController instead, hence the dedicated builder.) + async function makeUnderwaterVaiBorrower(borrowerAddr: string, vaiDebt: BigNumber): Promise { const timelock = await initMainnetUser(A.TIMELOCK, parseEther("5")); await new ethers.Contract(A.ACM, ACM_GIVE_ABI, timelock).giveCallPermission( VAI_CONTROLLER, @@ -357,11 +362,9 @@ const test = () => { const vaiCtrl = new ethers.Contract(VAI_CONTROLLER, VAI_CONTROLLER_ABI, owner); await vaiCtrl.toggleOnlyPrimeHolderMint(); - // Underwater VAI borrower: supply bStock, enter the market, mint VAI against it, stock gaps down. - // (makeUnderwaterBorrower borrows from a vToken; VAI debt is minted on the VAIController instead.) - const borrower = await initMainnetUser(B.VAI, parseUnits("10", 18)); + const borrower = await initMainnetUser(borrowerAddr, parseUnits("10", 18)); const COLLATERAL = parseUnits("100", 18); - await mkt.bStock.mint(B.VAI, COLLATERAL); + await mkt.bStock.mint(borrowerAddr, COLLATERAL); await mkt.bStock.connect(borrower).approve(mkt.vBStock.address, COLLATERAL); await new ethers.Contract(mkt.vBStock.address, VTOKEN_ABI, borrower).mint(COLLATERAL); await new ethers.Contract( @@ -370,41 +373,53 @@ const test = () => { borrower, ).enterMarkets([mkt.vBStock.address]); // mintVAI returns an error CODE (legacy style) — probe first so a cap/pause surfaces loudly. - const MINTED = parseUnits("4000", 18); - const mintCode: BigNumber = await vaiCtrl.connect(borrower).callStatic.mintVAI(MINTED); + const mintCode: BigNumber = await vaiCtrl.connect(borrower).callStatic.mintVAI(vaiDebt); if (!mintCode.eq(0)) throw new Error(`mintVAI returned code ${mintCode.toString()}`); - await vaiCtrl.connect(borrower).mintVAI(MINTED); - expect(await vaiCtrl.getVAIRepayAmount(B.VAI)).to.be.gte(MINTED); + await vaiCtrl.connect(borrower).mintVAI(vaiDebt); + expect(await vaiCtrl.getVAIRepayAmount(borrowerAddr)).to.be.gte(vaiDebt); await mkt.setPrice(P_CRASH); await mock.setRate(P_CRASH); + return vaiCtrl; + } - // The DEPLOYED proxy predates VAI support (it reads `underlying()` on every vDebt, which the - // VAIController lacks), so this scenario deploys a FRESH proxy on the current implementation and - // drives it against the same real gate, real VAIController, and real PSM. + // The DEPLOYED proxy predates VAI support (it reads `underlying()` on every vDebt, which the + // VAIController lacks), so the VAI scenarios deploy a FRESH proxy on the current implementation + // and drive it against the same real gate, real VAIController, and real PSM. + async function deployVaiLiquidator(): Promise { const Factory = await ethers.getContractFactory("BStockLiquidator"); const liqVai = await upgrades.deployProxy(Factory, [owner.address], { constructorArgs: [A.COMPTROLLER, A.VBNB, A.VWBNB, TOK.WBNB], unsafeAllow: ["constructor", "state-variable-immutable"], }); await liqVai.connect(owner).setRouter(mock.address, true); // hop-1 (Native RFQ mock) - - const REPAY = parseUnits("1500", 18); // VAI (< closeFactor * debt = 2000) - await fund(VAI, liqVai.address, REPAY); // pre-funded VAI inventory (flash is not supported for VAI) await liqVai.connect(owner).setRouter(PSM_USDT, true); // hop-2 router: the real PSM + return liqVai; + } - // Size hop 2 exactly as the script does — off the hop-1 output, undershot 10% like the PCS leg, - // so the fixed pull always fits under the on-chain approval — then let the SCRIPT's builder - // (lib/psm.ts) produce the calldata: what settles on the fork is the blob it constructs against - // the real PSM, expected out from the real previewSwapStableForVAI. + // Hop-1 USDT output at the crashed price for a given VAI repay, undershot 10% like the PCS leg, + // so a fixed hop-2 amountIn always fits under the contract's approval of the actual hop-1 delta. + async function vaiHop1Floor(repay: BigNumber): Promise { const comptroller = new ethers.Contract( A.COMPTROLLER, ["function liquidateVAICalculateSeizeTokens(address,uint256) view returns (uint256,uint256)"], owner, ); - const [, seizeTokens] = await comptroller.liquidateVAICalculateSeizeTokens(mkt.vBStock.address, REPAY); + const [, seizeTokens] = await comptroller.liquidateVAICalculateSeizeTokens(mkt.vBStock.address, repay); const xr: BigNumber = await mkt.vBStock.exchangeRateStored(); - const usdtFromHop1 = seizeTokens.mul(xr).div(ONE).mul(P_CRASH).div(ONE); - const floor = usdtFromHop1.mul(90).div(100); + return seizeTokens.mul(xr).div(ONE).mul(P_CRASH).div(ONE).mul(90).div(100); + } + + it("two-hop VAI debt, inventory mode: bStock->USDT (mock) -> VAI through the REAL PSM, script-built calldata", async () => { + const vaiCtrl = await makeUnderwaterVaiBorrower(B.VAI, parseUnits("4000", 18)); + const liqVai = await deployVaiLiquidator(); + + const REPAY = parseUnits("1500", 18); // VAI (< closeFactor * debt = 2000) + await fund(VAI, liqVai.address, REPAY); // pre-funded VAI inventory (flash is not supported for VAI) + + // Size hop 2 exactly as the script does, then let the SCRIPT's builder (lib/psm.ts) produce the + // calldata: what settles on the fork is the blob it constructs against the real PSM, expected + // out from the real previewSwapStableForVAI. + const floor = await vaiHop1Floor(REPAY); const psmSwap = await getPsmSwap({ amountIn: floor, recipient: liqVai.address }, ethers.provider); expect(psmSwap.router).to.equal(PSM_USDT); const expectedOut = BigNumber.from(psmSwap.expectedOut); @@ -440,6 +455,86 @@ const test = () => { expect(await ethers.provider.getBalance(liqVai.address)).to.equal(0); }); + // The SCRIPT end-to-end for a VAI debt: atomicLiquidate() itself must detect the VAIController + // debt, size the seize with the VAI-specific overload, build the PSM hop-2 calldata via lib/psm.ts + // against its REAL default PSM address (PSM_ADDR deliberately unset), derive minOut from the real + // preview, and submit the settle. Hop-1 is a fetch-stubbed firm Native quote against the funded + // mock router — NOT the MOCK_NATIVE env path, which would bypass the hop-2 build under test. + it("script-driven VAI liquidation: atomicLiquidate() builds and settles the real PSM hop 2", async () => { + const vaiCtrl = await makeUnderwaterVaiBorrower(B.VAI_SCRIPT, parseUnits("4000", 18)); + const liqVai = await deployVaiLiquidator(); + + const REPAY = parseUnits("1500", 18); + await fund(VAI, liqVai.address, REPAY); // inventory (the script rejects flash for VAI) + + // The firm quote the stub serves; for a firm source the script's floor == this out, so the PSM + // pull is exactly `quoteOut` and the delivered VAI is exactly its preview. + const quoteOut = await vaiHop1Floor(REPAY); + const psm = new ethers.Contract( + PSM_USDT, + [...PSM_FORK_ABI, "function previewSwapStableForVAI(uint256) view returns (uint256)"], + owner, + ); + const expectedOut: BigNumber = await psm.previewSwapStableForVAI(quoteOut); + + const SCRIPT_ENV = [ + "LIQUIDATOR", + "BORROWER", + "VBSTOCK", + "VDEBT", + "REPAY_AMOUNT", + "MODE", + "SOURCE", + "NATIVE_API_KEY", + "MOCK_NATIVE", + "MOCK_OUT", + "PSM_ADDR", + ]; + const realFetch = globalThis.fetch; + globalThis.fetch = (async () => ({ + json: async () => ({ + success: true, + recipient: liqVai.address, + amountIn: "0", + amountOut: quoteOut.toString(), + orders: [{ deadlineTimestamp: Math.floor(Date.now() / 1000) + 3600 }], + txRequest: { target: mock.address, calldata: buildSingleHopMock(mkt, mock, liqVai.address), value: "0" }, + }), + })) as unknown as typeof fetch; + + const debtBefore = await vaiCtrl.getVAIRepayAmount(B.VAI_SCRIPT); + const mintedBefore: BigNumber = await psm.vaiMinted(); + try { + Object.assign(process.env, { + LIQUIDATOR: liqVai.address, + BORROWER: B.VAI_SCRIPT, + VBSTOCK: mkt.vBStock.address, + VDEBT: VAI_CONTROLLER, // the real VAIController — the script must key its VAI path off it + REPAY_AMOUNT: "1500", + MODE: "inventory", + SOURCE: "native", + NATIVE_API_KEY: "test-key", + }); + await atomicLiquidate(owner); + + // And the script refuses flash for VAI before any quote (mirrors FlashNotSupportedForVai). + process.env.MODE = "flash"; + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/flash.*VAI/i); + } finally { + globalThis.fetch = realFetch; + for (const k of SCRIPT_ENV) delete process.env[k]; + } + + // Debt shrank through the real gate; the real PSM minted exactly the preview of the script's + // floor; the hop-1 surplus over the undershot floor stayed behind as sweepable USDT inventory. + expect(await vaiCtrl.getVAIRepayAmount(B.VAI_SCRIPT)).to.be.lt(debtBefore); + const vai = new ethers.Contract(VAI, ERC20_ABI, owner); + expect(await vai.balanceOf(liqVai.address)).to.equal(expectedOut); + expect((await psm.vaiMinted()).sub(mintedBefore)).to.equal(expectedOut); + expect(await new ethers.Contract(TOK.USDT, ERC20_ABI, owner).balanceOf(liqVai.address)).to.be.gt(0); + expect(await mkt.bStock.balanceOf(liqVai.address)).to.equal(0); + }); + it("getPsmSwap aborts legibly when the real PSM is paused on the fork", async () => { // Pause the real PSM through the real ACM + timelock, then the script's pre-flight must refuse // to build the VAI hop instead of producing calldata that reverts on-chain. From 4214098bc967e3f1436d4828ed8af0056e2a7038 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 16 Jul 2026 20:43:59 +0530 Subject: [PATCH 63/78] fix(bstock): harden liquidation off-chain seize, minOut, and pre-flight guards Correctness and safety fixes to the off-chain liquidation scripts, each with tests (mocked script suites + a bscmainnet-fork scenario): - Size the Venus Liquidator treasury cut off getEffectiveLiquidationIncentive for every debt type, VAI included, mirroring Liquidator._splitLiquidationIncentive exactly. The VAI path previously used the borrower-agnostic core incentive, which diverges when the borrower sits in a non-core pool. Correct the stale "cut is 0 today" comments (the live BSC gate keeps 50% of the bonus) and log the resolved cut. - Derive the single-hop minOut from the built order's guaranteed floor instead of the indicative quote, so an in-slippage Liquid Mesh fill below the quote no longer reverts InsufficientOut (matches the two-hop sizing). - Buffer the safe-fallback redeem/transfer amounts by SEIZE_BUFFER so ordinary oracle price drift between batch generation and signer quorum leaves sweepable dust rather than reverting the redeem. - Validate SLIPPAGE and MIN_OUT_BUFFER to [0,100) like SEIZE_BUFFER. - Read the getAccountLiquidity error slot and report an oracle failure distinctly; gate the no-shortfall abort behind ALLOW_NO_SHORTFALL=1 so forced liquidations of healthy accounts are supported. - Sanity-bound the Liquid Mesh order expiry to reject a millisecond-epoch value that would silently disable the off-chain TTL guards. Test mocks gain a divergent effective-vs-core incentive and a getAccountLiquidity error slot to exercise the above. --- contracts/test/BStockLiquidationMocks.sol | 21 ++++- scripts/bstock/atomic-liquidate.ts | 82 ++++++++++++++--- scripts/bstock/lib/liquidmesh.ts | 15 ++- scripts/bstock/safe-fallback.ts | 53 +++++++---- tests/hardhat/BStockAtomicLiquidate.ts | 101 +++++++++++++++++++++ tests/hardhat/BStockSafeFallback.ts | 55 ++++++++++- tests/hardhat/Fork/BStockLiquidatorFork.ts | 74 +++++++++++++++ 7 files changed, 365 insertions(+), 36 deletions(-) diff --git a/contracts/test/BStockLiquidationMocks.sol b/contracts/test/BStockLiquidationMocks.sol index bb317e11c..b355bb1b4 100644 --- a/contracts/test/BStockLiquidationMocks.sol +++ b/contracts/test/BStockLiquidationMocks.sol @@ -53,8 +53,13 @@ contract MockVAIController { /// @dev Minimal Comptroller surface the script + BStockLiquidator read, plus a flash lender. contract MockComptrollerLite { uint256 public shortfall; + uint256 public liquidityErr; // getAccountLiquidity error slot; non-zero -> a failed reading (oracle etc.) uint256 public closeFactorMantissa = 0.5e18; uint256 public liquidationIncentiveMantissa = 1.1e18; + // Borrower-aware effective incentive. 0 -> falls back to liquidationIncentiveMantissa (the common + // case: a single-pool borrower). Set non-zero to model a borrower in a non-core pool whose vBStock + // incentive DIFFERS from core — the case the treasury-cut math must size against (effective, not core). + uint256 public effectiveIncentiveMantissa; uint256 public flashPremiumMantissa; // fee on flash principal, 1e18-scaled (default 0) address public liquidatorContract; // pool-wide gate; 0 = permissionless (direct liquidateBorrow) uint256 public treasuryPercent; // redeem fee, 1e18-scaled (default 0) @@ -68,6 +73,14 @@ contract MockComptrollerLite { shortfall = s; } + function setLiquidityErr(uint256 e) external { + liquidityErr = e; + } + + function setEffectiveIncentive(uint256 m) external { + effectiveIncentiveMantissa = m; + } + function setTreasuryPercent(uint256 p) external { treasuryPercent = p; } @@ -81,7 +94,7 @@ contract MockComptrollerLite { } function getAccountLiquidity(address) external view returns (uint256, uint256, uint256) { - return (0, 0, shortfall); + return (liquidityErr, 0, shortfall); } /// @dev Mirrors the seize math: seizeTokens = repay * incentive (1:1 collateral rate here). @@ -110,9 +123,11 @@ contract MockComptrollerLite { return (0, (repayAmount * liquidationIncentiveMantissa) / 1e18); } - /// @dev Read by the off-chain script to size the Venus Liquidator's bonus cut. + /// @dev Read by the off-chain script to size the Venus Liquidator's bonus cut. Returns the + /// borrower-aware effective incentive when set, else the core value — so a test can make the + /// two DIVERGE and prove the cut is sized off the effective one (mirrors the real gate). function getEffectiveLiquidationIncentive(address, address) external view returns (uint256) { - return liquidationIncentiveMantissa; + return effectiveIncentiveMantissa != 0 ? effectiveIncentiveMantissa : liquidationIncentiveMantissa; } /// @dev Borrower-agnostic incentive — the one VAI's seize math uses. diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index d0411ceeb..922f19102 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -43,8 +43,12 @@ * LM_SPENDER)` set (separate puller). New sources = one adapter in lib/sources.ts. * LM_MIN_TTL min seconds left on a built Liquid Mesh order, else abort pre-submit (default 15); * LM RFQ orders are short-lived and an already-tight order would revert on-chain - * SLIPPAGE Native/LM slippage %, default 0.5 - * MIN_OUT_BUFFER extra haircut on minOut beyond slippage, default 0.5 (%) + * SLIPPAGE Native/LM slippage %, default 0.5 (validated to [0,100)) + * MIN_OUT_BUFFER extra haircut on minOut beyond slippage, default 0.5 (%) (validated to [0,100)) + * SETTLE_TTL_MARGIN min seconds of Native/LM quote TTL required immediately before submit, else + * abort + refetch instead of burning gas on an on-chain DeadlineExpired (default 10) + * ALLOW_NO_SHORTFALL "1" -> proceed even when the borrower has no shortfall (FORCED liquidation of a + * healthy account); default aborts as a fat-finger guard * SEIZE_BUFFER haircut on the QUOTED seize so a small oracle uptick can't make the router pull more * bStock than was seized (which reverts). Default 0.1 (%); unsold remainder is sweepable. * Keep it small: in MODE=flash the quoted proceeds must still cover principal + premium, @@ -180,6 +184,16 @@ export async function atomicLiquidate(signer: Signer) { const slippage = Number(process.env.SLIPPAGE || "0.5"); const minOutBufferPct = Number(process.env.MIN_OUT_BUFFER || "0.5"); const seizeBufferPct = Number(process.env.SEIZE_BUFFER || "0.1"); + // Bound SLIPPAGE / MIN_OUT_BUFFER the same way SEIZE_BUFFER is bounded below: a NaN slippage would + // reach the Native/LM request (and `out.mul(NaN)`) as garbage, and a negative MIN_OUT_BUFFER would + // silently push minOut ABOVE the quote — a guaranteed on-chain revert after the quote is already + // burned. Fail loudly here instead. + if (!Number.isFinite(slippage) || slippage < 0 || slippage >= 100) { + throw new Error(`SLIPPAGE must be a percent in [0, 100), got "${process.env.SLIPPAGE}"`); + } + if (!Number.isFinite(minOutBufferPct) || minOutBufferPct < 0 || minOutBufferPct >= 100) { + throw new Error(`MIN_OUT_BUFFER must be a percent in [0, 100), got "${process.env.MIN_OUT_BUFFER}"`); + } // Bound the haircut: a garbage or oversized value would silently under-quote (and in flash mode // starve the principal + premium repay). The on-chain InsufficientOut still backstops it, but fail // loudly here instead of after burning a Native quote. @@ -239,9 +253,25 @@ export async function atomicLiquidate(signer: Signer) { throw new Error("MODE=flash is not supported for a VAI debt (no vVAI to flash from) — use MODE=inventory"); } - // 0. liquidatable? - const [, , shortfall]: BigNumber[] = await comptroller.getAccountLiquidity(borrower); - if (shortfall.eq(0)) throw new Error(`${borrower} has no shortfall — not liquidatable`); + // 0. liquidatable? getAccountLiquidity returns (errorCode, liquidity, shortfall). A non-zero error + // code means the reading itself failed (e.g. an oracle PRICE_ERROR), so the shortfall is unreliable — + // surface THAT distinctly rather than mislabel it "no shortfall". A zero shortfall means the account + // is healthy by the normal metric; abort by default (guards against a fat-fingered borrower), but let + // ALLOW_NO_SHORTFALL=1 through: the contract deliberately does NOT pre-check liquidatability + // (BStockLiquidator._validateRouters comment) because Core's FORCED liquidations liquidate healthy + // accounts, and this script must be able to serve that path. + const [liqErr, , shortfall]: BigNumber[] = await comptroller.getAccountLiquidity(borrower); + if (!liqErr.eq(0)) { + throw new Error(`getAccountLiquidity returned error code ${liqErr} for ${borrower} — cannot assess shortfall`); + } + if (shortfall.eq(0)) { + if (process.env.ALLOW_NO_SHORTFALL !== "1") { + throw new Error( + `${borrower} has no shortfall — not liquidatable. Set ALLOW_NO_SHORTFALL=1 for a forced liquidation of a healthy account.`, + ); + } + console.warn(`WARN: ${borrower} has no shortfall — proceeding under ALLOW_NO_SHORTFALL (forced liquidation).`); + } console.log(`borrower ${borrower} shortfall=${ethers.utils.formatEther(shortfall)} (USD-scaled)`); // 1 + 2. precompute the exact seize so the quote amount matches what redeem() yields. Mirror the @@ -273,19 +303,28 @@ export async function atomicLiquidate(signer: Signer) { // The gate keeps a treasury cut of the liquidation BONUS (see Liquidator._splitLiquidationIncentive), // so this contract receives fewer vTokens than `seizeTokens`. Deduct that cut, else the precomputed - // amount overstates our holdings and the fixed-amountIn router pull reverts. 0 today, but governance-settable. + // amount overstates our holdings and the fixed-amountIn router pull reverts. On BSC mainnet today this + // cut is 50% of the bonus (treasuryPercentMantissa = 0.5e18) — not 0 — and is governance-settable. let vReceived = seizeTokens; const venusLiquidator = new Contract(gate, VENUS_LIQUIDATOR_ABI, signer); const liqTreasuryPct: BigNumber = await venusLiquidator.treasuryPercentMantissa(); if (!liqTreasuryPct.eq(0)) { - // Use the SAME incentive the seize above was computed with, else the bonus (and so the cut) is - // sized against the wrong number: VAI's seize math uses the borrower-agnostic getLiquidationIncentive. - const totalIncentive: BigNumber = isVai - ? await comptroller.getLiquidationIncentive(vBStock.address) - : await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); + // Mirror the gate EXACTLY: `_splitLiquidationIncentive` sizes the bonus with + // `getEffectiveLiquidationIncentive(borrower, vCollateral)` for EVERY debt type, VAI included — so + // use it here regardless of `isVai`. (The borrower-agnostic getLiquidationIncentive is only correct + // for VAI's SEIZE math, above; it is the wrong basis for the cut and would diverge whenever the + // borrower sits in a non-core pool whose vBStock incentive differs from core.) + const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive( + borrower, + vBStock.address, + ); const bonusAmount = seizeTokens.mul(totalIncentive.sub(ONE)).div(totalIncentive); const treasuryCut = bonusAmount.mul(liqTreasuryPct).div(ONE); vReceived = seizeTokens.sub(treasuryCut); + console.log( + `Venus Liquidator treasury cut ${ethers.utils.formatEther(liqTreasuryPct)} of bonus -> ` + + `-${ethers.utils.formatUnits(treasuryCut, 8)} v${bStockSym} (credited ${ethers.utils.formatUnits(vReceived, 8)})`, + ); } // Core redeem then routes `treasuryPercent` of the redeemed underlying to the treasury, so we hold @@ -317,7 +356,13 @@ export async function atomicLiquidate(signer: Signer) { let router: string; let swapCalldata: string; - let amountOut: BigNumber; // final debt-asset out (drives minOut) + let amountOut: BigNumber; // final debt-asset out — display / expected proceeds + // The number minOut is derived from. It must be the GUARANTEED worst-case debt-asset out, not the + // indicative one: for a single-hop firm quote these coincide, but for a single-hop INDICATIVE winner + // (Liquid Mesh) the built order can fill anywhere down to its own floor, so deriving minOut off the + // indicative `out` would set minOut above what the fill guarantees and revert InsufficientOut on a + // perfectly in-slippage fill. Two-hop already sizes off the floor via `amm.expectedOut`. + let minOutBasis: BigNumber; let router2 = ethers.constants.AddressZero; let swapCalldata2 = "0x"; let intermediateToken = ethers.constants.AddressZero; @@ -331,6 +376,7 @@ export async function atomicLiquidate(signer: Signer) { router = ethers.utils.getAddress(r); swapCalldata = data; amountOut = BigNumber.from(process.env.MOCK_OUT || "0"); + minOutBasis = amountOut; // mock path has no floor/indicative distinction if (process.env.MOCK_AMM) { const [r2, data2] = process.env.MOCK_AMM.split(":"); router2 = ethers.utils.getAddress(r2); @@ -391,14 +437,20 @@ export async function atomicLiquidate(signer: Signer) { swapCalldata2 = amm.calldata; intermediateToken = nativeOut; amountOut = BigNumber.from(amm.expectedOut); + minOutBasis = amountOut; // hop-2 expectedOut is already computed off the hop-1 floor console.log( `${hop1.source}: ${seizedHumanQuote} ${bStockSym} -> ${ethers.utils.formatUnits(midOut, 18)} USDT (TTL ${ttl}s, ${router}); ` + `AMM: -> ${ethers.utils.formatUnits(amountOut, debtDec)} ${debtSym} (${router2})`, ); } else { + // USDT debt: the single hop IS the debt asset. Display the indicative out, but derive minOut from + // the built order's GUARANTEED floor (== out for a firm Native quote; the built worst-case for an + // indicative Liquid Mesh order), so an in-slippage LM fill below the indicative quote still clears. amountOut = midOut; + minOutBasis = midFloor; console.log( - `${hop1.source} quote: ${seizedHumanQuote} ${bStockSym} -> ${ethers.utils.formatUnits(amountOut, debtDec)} ${debtSym} (TTL ${ttl}s, router ${router})`, + `${hop1.source} quote: ${seizedHumanQuote} ${bStockSym} -> ${ethers.utils.formatUnits(amountOut, debtDec)} ${debtSym} ` + + `(floor ${ethers.utils.formatUnits(midFloor, debtDec)}, TTL ${ttl}s, router ${router})`, ); } } @@ -411,8 +463,8 @@ export async function atomicLiquidate(signer: Signer) { } } - // minOut = amountOut minus an extra safety buffer on top of the quote slippage. - const minOut = amountOut.mul(Math.round((100 - minOutBufferPct) * 100)).div(10000); + // minOut = the GUARANTEED basis minus an extra safety buffer on top of the quote slippage. + const minOut = minOutBasis.mul(Math.round((100 - minOutBufferPct) * 100)).div(10000); const params = { borrower, diff --git a/scripts/bstock/lib/liquidmesh.ts b/scripts/bstock/lib/liquidmesh.ts index 0de0aa7ad..36c21a991 100644 --- a/scripts/bstock/lib/liquidmesh.ts +++ b/scripts/bstock/lib/liquidmesh.ts @@ -215,5 +215,18 @@ export async function buildSwap(p: LmSwapParams): Promise { throw new Error(`Liquid Mesh /swap error (HTTP ${res.status}) ${j.code}: ${j.msg}`); } const d = j.data; - return { chainId: d.chainId, callMsg: d.callMsg, orderId: d.orderId, expiryTimestamp: Number(d.expiryTimestamp) }; + const expiryTimestamp = Number(d.expiryTimestamp); + // Sanity-bound the unit: every downstream TTL check (LM_MIN_TTL, the pre-submit re-check, the + // on-chain `deadline`) assumes unix SECONDS. A millisecond-epoch (or otherwise malformed) value would + // sail through them all and place a far-future deadline on-chain, silently disabling the off-chain + // expiry guards. A real RFQ order lives seconds-to-minutes, so anything > 24h out is a unit/format + // bug — fail legibly instead of trusting it. + const nowSec = Math.floor(Date.now() / 1000); + if (!Number.isFinite(expiryTimestamp) || expiryTimestamp > nowSec + 86400) { + throw new Error( + `Liquid Mesh /swap returned an implausible expiryTimestamp ${d.expiryTimestamp} ` + + `(expected unix seconds within 24h of now) — refusing to trust it as the on-chain deadline`, + ); + } + return { chainId: d.chainId, callMsg: d.callMsg, orderId: d.orderId, expiryTimestamp }; } diff --git a/scripts/bstock/safe-fallback.ts b/scripts/bstock/safe-fallback.ts index 4e2cc91e7..0893f87d2 100644 --- a/scripts/bstock/safe-fallback.ts +++ b/scripts/bstock/safe-fallback.ts @@ -31,9 +31,10 @@ * Seize amount is the on-chain truth from `Comptroller.liquidateCalculateSeizeTokens` (or * `liquidateVAICalculateSeizeTokens` for VAI). The Venus * Liquidator keeps a treasury cut of the liquidation bonus, so the Safe is credited fewer vTokens than - * `seizeTokens`; we redeem only that credited amount (`received`). Snapshotted at the current block — if - * the borrower's position changes before the Safe executes, REGENERATE, else a stale redeem/transfer - * that exceeds the seized balance reverts the batch. + * `seizeTokens`; we redeem only that credited amount, further haircut by `SEIZE_BUFFER` (default 0.1%) + * so ordinary oracle price drift between generation and signer quorum leaves dust rather than reverting + * the redeem. Snapshotted at the current block — PRICE DRIFT ALONE (not just a position change) can + * invalidate the exact amounts, so regenerate immediately before signing for anything but a tiny move. * * Usage: * RPC_URL=https://bsc-dataseed.bnbchain.org \ @@ -49,6 +50,8 @@ * REPAY_AMOUNT (req) repay amount in DEBT underlying, human units * TARGET (req) Binance top-up / custody address to receive bStock * — set ALLOW_PLACEHOLDER=1 to emit with a zero target (DRAFT) + * SEIZE_BUFFER haircut % on the redeem/transfer amounts (default 0.1) absorbing oracle price + * drift before the Safe executes; the unredeemed dust is sweepable * OUT output path (default out/bstock-safe-fallback.json) */ import { BigNumber, Contract, providers, utils } from "ethers"; @@ -180,28 +183,45 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { // The Venus Liquidator keeps a treasury cut of the liquidation BONUS (see // Liquidator._splitLiquidationIncentive), so the Safe is credited fewer vTokens than seizeTokens. - // Redeem only the credited amount, else the batch reverts. + // Redeem only the credited amount, else the batch reverts. On BSC mainnet the cut is 50% of the bonus + // (treasuryPercentMantissa = 0.5e18) today — not 0 — and is governance-settable. let vReceived = seizeTokens; const liqTreasuryPct: BigNumber = await new Contract(gate, LIQUIDATOR_ABI, provider).treasuryPercentMantissa(); if (!liqTreasuryPct.eq(0)) { - // Use the SAME incentive the seize above was computed with, else the bonus (and so the cut) is - // sized against the wrong number: VAI's seize math uses the borrower-agnostic getLiquidationIncentive. - const totalIncentive: BigNumber = isVai - ? await comptroller.getLiquidationIncentive(vBStock.address) - : await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); + // Mirror the gate EXACTLY: `_splitLiquidationIncentive` sizes the bonus with + // `getEffectiveLiquidationIncentive(borrower, vCollateral)` for EVERY debt type, VAI included. The + // borrower-agnostic getLiquidationIncentive is only correct for VAI's SEIZE math above; using it for + // the cut would diverge whenever the borrower sits in a non-core pool with a different vBStock incentive. + const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive( + borrower, + vBStock.address, + ); const bonusAmount = seizeTokens.mul(totalIncentive.sub(ONE)).div(totalIncentive); const treasuryCut = bonusAmount.mul(liqTreasuryPct).div(ONE); vReceived = seizeTokens.sub(treasuryCut); } - // Raw bStock from redeeming vReceived, after Core's redeem treasuryPercent fee, FLOORED at the current + // The credited vTokens are decided at EXECUTION time, but the batch bakes in fixed amounts computed + // now. seizeTokens ∝ priceBorrowed / (priceCollateral · exchangeRate), and the bStock oracle price + // moves continuously — so between generation and signer quorum (often hours) an ordinary upward tick + // makes the real credit LESS than vReceived and reverts the redeem (tx #3). Haircut vReceived by + // SEIZE_BUFFER so a modest price move leaves harmless dust vTokens rather than bricking the batch; + // the dust is sweepable later. Regenerate for a large move. (Mirrors atomic-liquidate.ts SEIZE_BUFFER.) + const seizeBufferPct = Number(process.env.SEIZE_BUFFER || "0.1"); + if (!Number.isFinite(seizeBufferPct) || seizeBufferPct < 0 || seizeBufferPct >= 100) { + throw new Error(`SEIZE_BUFFER must be a percent in [0, 100), got "${process.env.SEIZE_BUFFER}"`); + } + const vRedeem = vReceived.mul(Math.round((100 - seizeBufferPct) * 100)).div(10000); + + // Raw bStock from redeeming vRedeem, after Core's redeem treasuryPercent fee, FLOORED at the current // exchange rate (rate only grows, so transferring this floor never exceeds what we hold). const exchangeRate: BigNumber = await vBStock.exchangeRateStored(); const treasuryPercent: BigNumber = await comptroller.treasuryPercent(); - const seizedRaw = vReceived.mul(exchangeRate).div(ONE).mul(ONE.sub(treasuryPercent)).div(ONE); + const seizedRaw = vRedeem.mul(exchangeRate).div(ONE).mul(ONE.sub(treasuryPercent)).div(ONE); console.log( - `seize: ${utils.formatUnits(seizeTokens, 8)} v${bStockSym} -> ~${utils.formatUnits(seizedRaw, bStockDec)} ` + - `${bStockSym} (floor)`, + `seize: ${utils.formatUnits(seizeTokens, 8)} v${bStockSym} credited ~${utils.formatUnits(vReceived, 8)} ` + + `-> redeem ${utils.formatUnits(vRedeem, 8)} (SEIZE_BUFFER ${seizeBufferPct}%) -> ` + + `~${utils.formatUnits(seizedRaw, bStockDec)} ${bStockSym} (floor, ship)`, ); // --- target (Binance top-up) --- @@ -227,7 +247,7 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { ); const seizeTxs = [ - call(vBStock.address, "redeem(uint256)", [vReceived]), + call(vBStock.address, "redeem(uint256)", [vRedeem]), call(bStock.address, "transfer(address,uint256)", [target, seizedRaw]), ]; @@ -242,12 +262,13 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { description: `Repay ${utils.formatUnits(repay, debtDec)} ${debtSym} of ${borrower}, ` + `seize ~${utils.formatUnits(seizedRaw, bStockDec)} ${bStockSym}, ship to ${target}. ` + - `Snapshot @ block ${await provider.getBlockNumber()}; regenerate if the position changed.`, + `Snapshot @ block ${await provider.getBlockNumber()}; SEIZE_BUFFER ${seizeBufferPct}% absorbs small ` + + `oracle drift — regenerate for a large price move or a position change.`, createdAt: Date.now(), transactions: txs, }); - return { batch, txs, gate, seizeTokens, vReceived, seizedRaw, target }; + return { batch, txs, gate, seizeTokens, vReceived, vRedeem, seizedRaw, target }; } async function main() { diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index 8a3c9f135..f28e6a640 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -38,6 +38,7 @@ const SCRIPT_ENV = [ "MIN_OUT_BUFFER", "SEIZE_BUFFER", "SETTLE_TTL_MARGIN", + "ALLOW_NO_SHORTFALL", "NATIVE_API_KEY", "SOURCE", "LM_API_KEY", @@ -175,6 +176,35 @@ describe("bStock atomic liquidation script", () => { await expect(atomicLiquidate(owner)).to.be.rejectedWith(/SETTLE_TTL_MARGIN/); }); + it("rejects a non-numeric SLIPPAGE before quoting", async () => { + setEnv({ SLIPPAGE: "abc" }); + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/SLIPPAGE/); + }); + + it("rejects a negative MIN_OUT_BUFFER before quoting (would push minOut above the quote)", async () => { + setEnv({ MIN_OUT_BUFFER: "-1" }); + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/MIN_OUT_BUFFER/); + }); + + it("aborts distinctly when getAccountLiquidity reports an error code (not a healthy account)", async () => { + await usdt.mint(liq.address, REPAY); + await comptroller.setLiquidityErr(13); // e.g. an oracle PRICE_ERROR — the shortfall reading is unreliable + setEnv(); + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/error code 13/); + expect(await usdt.balanceOf(liq.address)).to.equal(REPAY); // untouched + }); + + it("proceeds on a healthy (no-shortfall) borrower under ALLOW_NO_SHORTFALL (forced liquidation)", async () => { + await usdt.mint(liq.address, REPAY); + await comptroller.setShortfall(0); // healthy by the normal metric — a forced liquidation target + setEnv({ ALLOW_NO_SHORTFALL: "1" }); + + await atomicLiquidate(owner); // no throw: the override lets the forced path through + + expect(await usdt.balanceOf(liq.address)).to.equal(OUT); // settled like the happy path + expect(await bStock.balanceOf(liq.address)).to.equal(0); + }); + it("aborts before submit when the Native quote TTL is below the safety margin", async () => { await usdt.mint(liq.address, REPAY); // inventory so the guard, not a funding error, is what trips // Real Native quote path (no MOCK_NATIVE): stub fetch to return a quote that expires in ~2s, below @@ -420,6 +450,45 @@ describe("bStock atomic liquidation script", () => { expect(await vai.balanceOf(liq.address)).to.equal(REPAY); // untouched }); + it("sizes the Liquidator treasury cut off the effective incentive, not core (VAI)", async () => { + await vai.mint(liq.address, REPAY); + // Model a VAI borrower in a non-core pool: the EFFECTIVE vBStock incentive (1.25x) differs from + // core (1.1x). The gate sizes its bonus cut with the effective incentive for EVERY debt type + // (Liquidator._splitLiquidationIncentive), so the script must too. Using the borrower-agnostic + // core incentive (the pre-fix VAI path) would under-deduct the cut and over-quote the hop-1 amount. + await comptroller.setEffectiveIncentive(U("1.25")); + const venusLiq = await ethers.getContractAt("MockVenusLiquidator", await comptroller.liquidatorContract()); + await venusLiq.setTreasuryCut(U("0.5")); // 50% of the bonus, like BSC mainnet today + + // seize = repay*core = 5500; bonus = 5500*(0.25/1.25) = 1100; cut = 550 -> QUOTED seize 4950. + // The pre-fix core-based math would give bonus 500, cut 250, quoted 5250 — the divergence asserted. + // (The MockVenusLiquidator credits by its own simpler formula, so hop 1 delivers 2750 USDT on-chain; + // stub the firm-quote `out` to that so the settle stays self-consistent. The ASSERTION is the + // captured off-chain quote `amount`, which is driven purely by the script's cut math under test.) + let quotedAmount: string | null = null; + const realFetch = globalThis.fetch; + globalThis.fetch = (async (url: unknown) => { + quotedAmount = new URL(String(url)).searchParams.get("amount"); + return { + json: async () => ({ + success: true, + recipient: liq.address, + amountIn: "0", + amountOut: U("2750").toString(), // what the mock gate actually credits + hop 1 delivers + orders: [{ deadlineTimestamp: Math.floor(Date.now() / 1000) + 3600 }], + txRequest: { target: router.address, calldata: swapAllCalldata(liq.address), value: "0" }, + }), + }; + }) as unknown as typeof fetch; + try { + vaiEnv({ SEIZE_BUFFER: "0" }); // no buffer: the quoted amount equals the credited seize exactly + await atomicLiquidate(owner); + } finally { + globalThis.fetch = realFetch; + } + expect(quotedAmount).to.equal("4950.0"); // effective-incentive cut, not the core-based 5250 + }); + it("rejects MODE=flash for a VAI debt before quoting (mirrors FlashNotSupportedForVai)", async () => { await vai.mint(liq.address, REPAY); vaiEnv({ MODE: "flash" }); @@ -580,6 +649,24 @@ describe("bStock atomic liquidation script", () => { expect(await usdt.balanceOf(liq.address)).to.equal(OUT); }); + it("single-hop: derives minOut from the LM built floor, so a fill below the indicative quote still settles", async () => { + await usdt.mint(liq.address, REPAY); // USDT debt inventory (single hop, no AMM leg) + // SLIPPAGE (1%) > MIN_OUT_BUFFER (0.5%) opens the window the bug lived in. The built LM order only + // guarantees out*(1-slippage) = 5445, but the OLD code derived minOut from the INDICATIVE out + // (5500*(1-0.5%) = 5472.5). An in-slippage fill between the floor and that stale minOut reverted + // InsufficientOut. Deriving minOut from the floor (5445*(1-0.5%) = 5417.775) lets it settle. + await lmRouter.setRate(U("0.99")); // fills at exactly the floor 5445 (< the stale 5472.5 minOut) + const real = stubFetch({ nativeOut: U("0"), lmOut: OUT, lmCalldata: lmSwapAll() }); + try { + lmEnv({ SOURCE: "liquidmesh", SLIPPAGE: "1", MIN_OUT_BUFFER: "0.5" }); + await atomicLiquidate(owner); + } finally { + globalThis.fetch = real; + } + // Settled at the floor fill with no revert — proof minOut was sized off the floor, not the quote. + expect(await usdt.balanceOf(liq.address)).to.equal(U("5445")); + }); + it("tolerates a trailing comma / empty segment in SOURCE", async () => { await usdt.mint(liq.address, REPAY); const real = stubFetch({ nativeOut: U("5400"), lmOut: OUT, lmCalldata: lmSwapAll() }); @@ -611,6 +698,20 @@ describe("bStock atomic liquidation script", () => { expect(await bStock.balanceOf(lmRouter.address)).to.equal(0); // nothing settled }); + it("rejects an implausible (millisecond-epoch) Liquid Mesh order expiry", async () => { + await usdt.mint(liq.address, REPAY); + // A ms-epoch value read as unix SECONDS lands ~55000 AD — it would pass every TTL check and put a + // far-future deadline on-chain, silently disabling the off-chain expiry guards. buildSwap bounds it. + const real = stubFetch({ nativeOut: U("0"), lmOut: OUT, lmCalldata: lmSwapAll(), lmExpiry: Date.now() }); + try { + lmEnv({ SOURCE: "liquidmesh" }); + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/implausible expiryTimestamp/); + } finally { + globalThis.fetch = real; + } + expect(await bStock.balanceOf(lmRouter.address)).to.equal(0); // nothing settled + }); + it("rejects an unknown SOURCE name", async () => { await usdt.mint(liq.address, REPAY); lmEnv({ SOURCE: "nope" }); diff --git a/tests/hardhat/BStockSafeFallback.ts b/tests/hardhat/BStockSafeFallback.ts index 9acf9a0dc..bf988412f 100644 --- a/tests/hardhat/BStockSafeFallback.ts +++ b/tests/hardhat/BStockSafeFallback.ts @@ -51,6 +51,9 @@ describe("BStock safe-fallback batch generator", () => { VDEBT: vDebt.address, REPAY_AMOUNT: "5000", TARGET: target.address, + // Isolate the price-drift buffer from the cut/fee assertions: default it off here so those tests + // assert exact seize-based amounts. The dedicated buffer tests below override it. + SEIZE_BUFFER: "0", ...over, }); } @@ -58,7 +61,16 @@ describe("BStock safe-fallback batch generator", () => { beforeEach(async () => { await deploy(); // wipe any env leakage between tests - for (const k of ["SAFE", "BORROWER", "VBSTOCK", "VDEBT", "REPAY_AMOUNT", "TARGET", "ALLOW_PLACEHOLDER"]) { + for (const k of [ + "SAFE", + "BORROWER", + "VBSTOCK", + "VDEBT", + "REPAY_AMOUNT", + "TARGET", + "ALLOW_PLACEHOLDER", + "SEIZE_BUFFER", + ]) { delete process.env[k]; } }); @@ -176,4 +188,45 @@ describe("BStock safe-fallback batch generator", () => { expect(xfer[0]).to.equal(target.address); expect(xfer[1]).to.equal(seizedRaw); }); + + it("haircuts the redeem/transfer by SEIZE_BUFFER so oracle drift leaves dust, not a revert (M4)", async () => { + setEnv({ SEIZE_BUFFER: "10" }); // 10% haircut for a clean, observable number + const { vReceived, vRedeem, seizedRaw, txs } = await buildSafeFallbackBatch(ethers.provider); + + // Credited (vReceived) is still the full seize (cut 0); only the REDEEMED amount is haircut, so a + // small upward price move before quorum leaves the batch redeeming less than credited (dust remains). + expect(vReceived).to.equal(SEIZE); + expect(vRedeem).to.equal(SEIZE.mul(9000).div(10000)); // 90% of 5500 = 4950 + expect(seizedRaw).to.equal(SEIZE.mul(9000).div(10000)); // 1:1 rate, treasuryPercent 0 + + // The redeem tx uses the haircut amount, not the full credit. + const redeem = ethers.utils.defaultAbiCoder.decode(["uint256"], txs[2].data.slice(0, 2) + txs[2].data.slice(10)); + expect(redeem[0]).to.equal(vRedeem); + }); + + it("rejects an out-of-range SEIZE_BUFFER", async () => { + setEnv({ SEIZE_BUFFER: "150" }); + await expect(buildSafeFallbackBatch(ethers.provider)).to.be.rejectedWith(/SEIZE_BUFFER/); + }); + + it("sizes the Liquidator treasury cut off the effective incentive, not core (VAI)", async () => { + // VAI borrower in a non-core pool: effective vBStock incentive (1.25x) differs from core (1.1x). The + // gate sizes the bonus cut with the effective incentive for every debt type, so the batch must too — + // the borrower-agnostic core incentive is only correct for VAI's SEIZE math, not the cut. + const vai = await (await ethers.getContractFactory("MockMintableERC20")).deploy("Venus VAI", "VAI", 18); + const vaiController = await (await ethers.getContractFactory("MockVAIController")).deploy(vai.address); + await comptroller.setVaiController(vaiController.address); + await venusLiq.setVaiController(vaiController.address); + await comptroller.setEffectiveIncentive(U("1.25")); + await venusLiq.setTreasuryCut(U("0.5")); // 50% of the bonus + + setEnv({ VDEBT: vaiController.address }); + const { vReceived } = await buildSafeFallbackBatch(ethers.provider); + + // seize = repay*core = 5500; bonus = 5500*(0.25/1.25) = 1100; cut = 550 -> credited 4950. + // Core-based sizing (the pre-fix path) would give bonus 500, cut 250, credited 5250. + const bonusAmount = SEIZE.mul(U("1.25").sub(ONE)).div(U("1.25")); + const cut = bonusAmount.mul(U("0.5")).div(ONE); + expect(vReceived).to.equal(SEIZE.sub(cut)); // 4950, not 5250 + }); }); diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts index 05fb759ca..85b41531d 100644 --- a/tests/hardhat/Fork/BStockLiquidatorFork.ts +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -96,6 +96,7 @@ const B = { VAI_SCRIPT: ethers.utils.getAddress("0x00000000000000000000000000000000ca11000b"), FLASH: ethers.utils.getAddress("0x00000000000000000000000000000000ca110004"), FORCED: ethers.utils.getAddress("0x00000000000000000000000000000000ca110005"), + FORCED_SCRIPT: ethers.utils.getAddress("0x00000000000000000000000000000000ca11000c"), DEADLINE: ethers.utils.getAddress("0x00000000000000000000000000000000ca110006"), REENTRANCY: ethers.utils.getAddress("0x00000000000000000000000000000000ca110007"), ROLLBACK: ethers.utils.getAddress("0x00000000000000000000000000000000ca110008"), @@ -628,6 +629,79 @@ const test = () => { await assertSettledFor(liq, mkt, VUSDT, B.FORCED, borrowBefore); }); + it("script-driven forced liquidation: atomicLiquidate under ALLOW_NO_SHORTFALL settles a healthy account", async () => { + // The SCRIPT counterpart of the contract-level forced test above: a HEALTHY (no-shortfall) + // account with forced-liquidation enabled. atomicLiquidate aborts such an account by default (a + // fat-finger guard), and ALLOW_NO_SHORTFALL=1 lets the forced path through. It must then settle + // end-to-end against the real gate — proving the guard downgrade did not break the forced flow. + await makeUnderwaterBorrower({ + mkt, + vDebt: VUSDT, + borrower: B.FORCED_SCRIPT, + collateralBStock: parseUnits("100", 18), + borrowAmount: parseUnits("5000", 18), + }); + await mock.setRate(P_HEALTHY); // MM quotes the uncrashed price + + const acm = new ethers.Contract(A.ACM, ACM_GIVE_ABI, await initMainnetUser(A.TIMELOCK, parseEther("10"))); + await acm.giveCallPermission(A.COMPTROLLER, "_setForcedLiquidation(address,bool)", A.TIMELOCK); + const cAsTl = new ethers.Contract( + A.COMPTROLLER, + ["function _setForcedLiquidation(address,bool)"], + await initMainnetUser(A.TIMELOCK, parseEther("10")), + ); + await cAsTl._setForcedLiquidation(VUSDT, true); + + await fund(TOK.USDT, liq.address, parseUnits("2000", 18)); // inventory for the repay + // Firm Native quote stubbed LOW so minOut (derived from its floor) is trivially cleared by the + // real hop-1 delivery — the assertion under test is the ALLOW_NO_SHORTFALL gate, not quote size. + const SCRIPT_ENV = [ + "LIQUIDATOR", + "BORROWER", + "VBSTOCK", + "VDEBT", + "REPAY_AMOUNT", + "MODE", + "SOURCE", + "NATIVE_API_KEY", + "ALLOW_NO_SHORTFALL", + ]; + const realFetch = globalThis.fetch; + globalThis.fetch = (async () => ({ + json: async () => ({ + success: true, + recipient: liq.address, + amountIn: "0", + amountOut: parseUnits("500", 18).toString(), + orders: [{ deadlineTimestamp: Math.floor(Date.now() / 1000) + 3600 }], + txRequest: { target: mock.address, calldata: buildSingleHopMock(mkt, mock, liq.address), value: "0" }, + }), + })) as unknown as typeof fetch; + + const borrowBefore = await new ethers.Contract(VUSDT, VTOKEN_ABI, owner).borrowBalanceStored(B.FORCED_SCRIPT); + try { + Object.assign(process.env, { + LIQUIDATOR: liq.address, + BORROWER: B.FORCED_SCRIPT, + VBSTOCK: mkt.vBStock.address, + VDEBT: VUSDT, + REPAY_AMOUNT: "2000", + MODE: "inventory", + SOURCE: "native", + NATIVE_API_KEY: "test-key", + }); + // Default (no override) must refuse the healthy account before any settle. + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/no shortfall/i); + // With the forced-liquidation override it proceeds and settles. + process.env.ALLOW_NO_SHORTFALL = "1"; + await atomicLiquidate(owner); + } finally { + globalThis.fetch = realFetch; + for (const k of SCRIPT_ENV) delete process.env[k]; + } + await assertSettledFor(liq, mkt, VUSDT, B.FORCED_SCRIPT, borrowBefore); + }); + it("expired deadline -> DeadlineExpired, no state change", async () => { await makeUnderwaterBorrower({ mkt, From 6d5c7e9d0dc12f1871a9269cc7ebcd426bab79b3 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 16 Jul 2026 20:44:14 +0530 Subject: [PATCH 64/78] docs(bstock): document env knobs and correct the runbook and sizing comments - README: add SETTLE_TTL_MARGIN and ALLOW_NO_SHORTFALL to the atomic env table, SEIZE_BUFFER to the safe-fallback table, an advanced/override env subsection, and correct "4 txs" to "3-4" (native BNB drops the approve). Note the single-hop minOut is derived from the guaranteed floor and that price drift alone can invalidate a safe-fallback batch. - amm.ts: correct the stale comments that described exact-match hop-2 sizing; the caller passes the hop-1 floor and relies on floor <= midDelta. --- scripts/bstock/README.md | 24 +++++++++++++++++------- scripts/bstock/lib/amm.ts | 13 ++++++++----- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/scripts/bstock/README.md b/scripts/bstock/README.md index de7c6d142..11eef6341 100644 --- a/scripts/bstock/README.md +++ b/scripts/bstock/README.md @@ -122,9 +122,11 @@ call `liquidate`/`flashLiquidate`. | `LM_PRIVATE_KEY_SEED` | | | Liquid Mesh Ed25519 seed, base64url (required for `liquidmesh`/`auto`) | | `LM_MIN_TTL` | | `15` | Min seconds left on the LM order at build time, else abort (LM RFQ orders are short-lived; the on-chain deadline still enforces the real expiry) | | `SOURCE_TIMEOUT_MS` | | `8000` | Per-request timeout (ms) on each hop-1 source API call, so a hung source aborts and drops out of the `auto` race instead of blocking the live one | +| `SETTLE_TTL_MARGIN` | | `10` | Min seconds of quote TTL required immediately before submit; below it the script aborts + refetches instead of burning gas on an on-chain `DeadlineExpired` | +| `ALLOW_NO_SHORTFALL` | | | `1` → proceed even when the borrower has no shortfall (FORCED liquidation of a healthy account); default aborts as a fat-finger guard | | `DRY_RUN` | | | `1` → callStatic only, sends nothing | -| `SLIPPAGE` | | `0.5` | Native/LM slippage % | -| `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) | +| `SLIPPAGE` | | `0.5` | Native/LM slippage % (validated to `[0,100)`) | +| `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) (validated to `[0,100)`). For a single-hop indicative (Liquid Mesh) quote, `minOut` is derived from the built order's guaranteed floor, not the indicative out | | `SEIZE_BUFFER` | | `0.1` | Haircut on the QUOTED seize (%) so an oracle uptick before inclusion can't make the router pull more bStock than was seized (→ `SwapFailed`); the unsold remainder stays as bStock inventory (sweepable) | | `AMM_PROVIDER` | | `kyberswap` | Hop-2 route for non-USDT debt: `kyberswap` / `openocean` / `pcsv2` | | `PSM_ADDR` | | BSC PSM | Peg Stability Module used as hop 2 for a VAI debt (`swapStableForVAI` calldata encoded locally, expected out from `previewSwapStableForVAI`); must be allowlisted via `setRouter`. `MODE=flash` rejected | @@ -133,6 +135,11 @@ call `liquidate`/`flashLiquidate`. _Fork/local testing:_ `MOCK_NATIVE="router:calldata"` (hop 1), `MOCK_AMM` (hop 2), `MOCK_OUT` (final debt out), `IMPERSONATE=0x..` to run as an operator. +_Advanced / override env (defaults are correct for BSC mainnet; only touch these for tests or an +endpoint migration):_ `USDT_ADDR`, `WBNB_ADDR` (asset overrides); `KYBER_CLIENT_ID`, `KYBER_API_BASE`, +`OPENOCEAN_API_BASE`, `OPENOCEAN_GAS_GWEI`, `AMM_ROUTER`, `AMM_PATH`, `AMM_DEADLINE_SECS` (hop-2 AMM +tuning); `NATIVE_API_BASE`, `LM_API_HOST` (RFQ endpoints). + --- ## 3. Safe fallback — manual multisig path @@ -147,9 +154,9 @@ BORROWER=0x.. VBSTOCK=0x.. VDEBT=0x.. REPAY_AMOUNT=5000 TARGET=0x.. \ Then: **Safe → Apps → Transaction Builder → Load** the JSON, review, sign, execute. -The batch is 4 txs (approve → liquidateBorrow → redeem → transfer): the Safe repays from its own -funds, seizes the bStock, and ships raw bStock to `TARGET` (Binance top-up / custody) for finance to -offload on the CEX. +The batch is 3–4 txs (approve → liquidateBorrow → redeem → transfer; the approve is dropped for a +native BNB debt, so 3): the Safe repays from its own funds, seizes the bStock, and ships raw bStock to +`TARGET` (Binance top-up / custody) for finance to offload on the CEX. | Var | Req | Default | Notes | | -------------- | --- | ------------------------------- | -------------------------------------------------------------------------------------- | @@ -160,7 +167,10 @@ offload on the CEX. | `TARGET` | ✓ | | Binance top-up / custody address for the bStock (or `ALLOW_PLACEHOLDER=1` for a draft) | | `SAFE` | | `0xdc6E…2029` | Executing Safe | | `RPC_URL` | | public dataseed | BSC RPC | +| `SEIZE_BUFFER` | | `0.1` | Haircut % on the redeem/transfer amounts, absorbing oracle price drift before the Safe executes; the unredeemed dust is sweepable | | `OUT` | | `out/bstock-safe-fallback.json` | Output path | -> The batch is a **snapshot** at the current block. If the borrower's position changes before the Safe -> executes, **regenerate** — a stale redeem/transfer that exceeds the seized balance reverts the batch. +> The batch is a **snapshot** at the current block. `SEIZE_BUFFER` absorbs small oracle price drift, but +> **price drift alone** (not just a position change) can still invalidate the exact amounts — a stale +> redeem/transfer that exceeds the seized balance reverts the batch. **Regenerate immediately before +> signing** for anything but a tiny move. diff --git a/scripts/bstock/lib/amm.ts b/scripts/bstock/lib/amm.ts index 62567a5e9..9d269d612 100644 --- a/scripts/bstock/lib/amm.ts +++ b/scripts/bstock/lib/amm.ts @@ -30,9 +30,12 @@ export interface AmmSwapParams { tokenIn: string; // intermediate (USDT) tokenOut: string; // debt asset // Wei of tokenIn to sell. Providers bake this into the swap calldata as a FIXED input amount, while - // the contract approves router2 only the ACTUAL on-chain hop-1 output (`midDelta`). Pass the exact - // hop-1 output: if `midDelta` ends up below this value the router pulls more than approved and the - // hop reverts `SwapFailed()`. Native RFQ (hop 1) fills firm quotes exactly, so the two match. + // the contract approves router2 only the ACTUAL on-chain hop-1 output (`midDelta`). Pass the hop-1 + // FLOOR (the built order's guaranteed worst-case out), NOT its indicative quote: the invariant + // `floor <= midDelta` then always holds, so the fixed pull fits inside the approval and any surplus + // (`midDelta - floor`) stays as sweepable intermediate inventory. Sizing this off the indicative + // `out` instead would let an under-filling hop-1 leave the router pulling more than approved + // (`SwapFailed()`). See atomic-liquidate.ts (hop-2 sizing) — do NOT change this back to `out`. amountIn: string; recipient: string; // where the debt asset must land (the liquidator contract) slippage: number; // percent, e.g. 0.5 @@ -130,8 +133,8 @@ async function openocean(p: AmmSwapParams): Promise { * Caveat: `amountIn` is encoded verbatim as the V2 `amountIn`, so the router pulls exactly that many * tokens. The contract approves router2 only the actual hop-1 output (`midDelta`), so if `midDelta` * is even 1 wei below the `amountIn` passed here the pull exceeds the approval and the hop reverts - * `SwapFailed()` with no further detail. Hop 1 is a Native RFQ firm quote (exact fill), so in the - * normal flow `midDelta == amountIn`; keep hop 1 on Native and do not hand this a stale/rounded value. + * `SwapFailed()` with no further detail. The caller passes the hop-1 FLOOR, and `floor <= midDelta` + * always holds, so the pull fits; do not hand this the indicative quote or a stale/rounded value. */ async function pcsv2(p: AmmSwapParams, rpc?: providers.Provider): Promise { if (!rpc) throw new Error("pcsv2 AMM provider needs an RPC provider (pass one to getAmmSwap)"); From 801f18040a5a00a1f90ec039238c4a71425fa988 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 17 Jul 2026 01:46:04 +0530 Subject: [PATCH 65/78] docs(bstock): correct VAI incentive-divergence comments in liquidation scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The treasury-cut comments claimed getEffectiveLiquidationIncentive diverges from the core getLiquidationIncentive "whenever the borrower sits in a non-core pool" — false for VAI. A VAI borrower is core-pool-locked (VAIController.mintVAI requires the core pool and MarketFacet.hasValidPoolBorrows bars leaving it while mintedVAIs>0), so userPoolId is always 0 and effective == core for VAI. Divergence is only reachable for a non-VAI ERC20 borrower in a non-core pool. Both scripts already call effective for the cut (gate-parity with Liquidator._splitLiquidationIncentive + future-proofing); only the rationale was wrong. Comments updated in atomic-liquidate.ts and safe-fallback.ts. --- scripts/bstock/atomic-liquidate.ts | 17 ++++++++++------- scripts/bstock/safe-fallback.ts | 18 +++++++++++------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 922f19102..8350258df 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -311,13 +311,16 @@ export async function atomicLiquidate(signer: Signer) { if (!liqTreasuryPct.eq(0)) { // Mirror the gate EXACTLY: `_splitLiquidationIncentive` sizes the bonus with // `getEffectiveLiquidationIncentive(borrower, vCollateral)` for EVERY debt type, VAI included — so - // use it here regardless of `isVai`. (The borrower-agnostic getLiquidationIncentive is only correct - // for VAI's SEIZE math, above; it is the wrong basis for the cut and would diverge whenever the - // borrower sits in a non-core pool whose vBStock incentive differs from core.) - const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive( - borrower, - vBStock.address, - ); + // use it here regardless of `isVai`. The borrower-agnostic getLiquidationIncentive is only correct + // for VAI's SEIZE math above (what liquidateVAICalculateSeizeTokens reads); the CUT is always the + // effective, pool-resolved incentive. + // - Non-VAI: the borrower can be in a non-core pool whose vBStock incentive differs from core, so + // effective != core is REACHABLE — core here would missize the cut and the fixed router pull. + // - VAI: effective == core ALWAYS (a VAI borrower is core-pool-locked — VAIController.mintVAI + // requires the core pool and hasValidPoolBorrows bars leaving it while mintedVAIs>0 — so + // userPoolId==0). Calling effective is a safe no-op there, keeping one path and staying correct + // if that invariant is ever relaxed. + const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); const bonusAmount = seizeTokens.mul(totalIncentive.sub(ONE)).div(totalIncentive); const treasuryCut = bonusAmount.mul(liqTreasuryPct).div(ONE); vReceived = seizeTokens.sub(treasuryCut); diff --git a/scripts/bstock/safe-fallback.ts b/scripts/bstock/safe-fallback.ts index 0893f87d2..6f8a27bc8 100644 --- a/scripts/bstock/safe-fallback.ts +++ b/scripts/bstock/safe-fallback.ts @@ -189,13 +189,17 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { const liqTreasuryPct: BigNumber = await new Contract(gate, LIQUIDATOR_ABI, provider).treasuryPercentMantissa(); if (!liqTreasuryPct.eq(0)) { // Mirror the gate EXACTLY: `_splitLiquidationIncentive` sizes the bonus with - // `getEffectiveLiquidationIncentive(borrower, vCollateral)` for EVERY debt type, VAI included. The - // borrower-agnostic getLiquidationIncentive is only correct for VAI's SEIZE math above; using it for - // the cut would diverge whenever the borrower sits in a non-core pool with a different vBStock incentive. - const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive( - borrower, - vBStock.address, - ); + // `getEffectiveLiquidationIncentive(borrower, vCollateral)` for EVERY debt type, VAI included — so + // use it here regardless of `isVai`. Two distinct incentives are in play and must not be conflated: + // the borrower-agnostic getLiquidationIncentive drives VAI's SEIZE math above (that is what + // liquidateVAICalculateSeizeTokens reads), while the CUT is always the effective (pool-resolved) one. + // - Non-VAI: the borrower may have switched to a non-core pool whose vBStock incentive differs from + // core, so effective != core is REACHABLE — using core here would missize the cut and the redeem. + // - VAI: effective == core ALWAYS. A VAI borrower is core-pool-locked (VAIController.mintVAI requires + // the core pool, and MarketFacet.hasValidPoolBorrows bars leaving it while mintedVAIs>0), so + // userPoolId==0 and the two getters return the same value. Calling effective is a safe no-op that + // keeps one code path and stays correct if that VAI-core invariant is ever relaxed. + const totalIncentive: BigNumber = await comptroller.getEffectiveLiquidationIncentive(borrower, vBStock.address); const bonusAmount = seizeTokens.mul(totalIncentive.sub(ONE)).div(totalIncentive); const treasuryCut = bonusAmount.mul(liqTreasuryPct).div(ONE); vReceived = seizeTokens.sub(treasuryCut); From 17bc7fd6a2123e199e9d2843ae6cdeae274acd6e Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 17 Jul 2026 01:46:04 +0530 Subject: [PATCH 66/78] test(bstock): fork-prove effective-incentive divergence for a non-core ERC20 borrower Add a bscmainnet-fork test that stands up a real non-core e-mode pool listing vBStock at a divergent liquidation incentive (1.25x vs core 1.1x), puts a USDT borrower in it, and gaps the stock into shortfall. It proves (a) getEffectiveLiquidationIncentive and getLiquidationIncentive actually diverge on the live diamond for a non-VAI debt, (b) atomic-liquidate.ts sizes the treasury cut off the effective incentive (the hop-1 quote amount matches the effective-cut seize, not the core one), and (c) it settles end-to-end through the real gate. Also mark the VAI treasury-cut unit test as defensive: its forced core-vs-effective divergence is on-chain-impossible for VAI (core-pool-locked), so it guards the script's cut formula, not a reproducible mainnet state; the genuine case is the new fork test. --- tests/hardhat/BStockAtomicLiquidate.ts | 17 +- tests/hardhat/Fork/BStockLiquidatorFork.ts | 178 +++++++++++++++++++++ 2 files changed, 190 insertions(+), 5 deletions(-) diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index f28e6a640..5a0e5f449 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -450,12 +450,19 @@ describe("bStock atomic liquidation script", () => { expect(await vai.balanceOf(liq.address)).to.equal(REPAY); // untouched }); - it("sizes the Liquidator treasury cut off the effective incentive, not core (VAI)", async () => { + it("sizes the Liquidator treasury cut off the effective incentive, not core (VAI, defensive)", async () => { await vai.mint(liq.address, REPAY); - // Model a VAI borrower in a non-core pool: the EFFECTIVE vBStock incentive (1.25x) differs from - // core (1.1x). The gate sizes its bonus cut with the effective incentive for EVERY debt type - // (Liquidator._splitLiquidationIncentive), so the script must too. Using the borrower-agnostic - // core incentive (the pre-fix VAI path) would under-deduct the cut and over-quote the hop-1 amount. + // DEFENSIVE guard, not a reproducible mainnet state. The gate sizes its bonus cut with the + // EFFECTIVE incentive for EVERY debt type (Liquidator._splitLiquidationIncentive), so the script + // must too; using the borrower-agnostic core incentive (the pre-fix VAI path) would under-deduct + // the cut and over-quote hop-1. This test forces core (1.1x) != effective (1.25x) on the MOCK to + // pin the script to the gate's formula. On the REAL protocol the two CANNOT diverge for a VAI + // borrower: VAI mint requires the core pool (VAIController.mintVAI) and an account with VAI debt is + // barred from leaving it (MarketFacet.hasValidPoolBorrows rejects a non-core switch while + // mintedVAIs>0), so userPoolId is always 0 and getEffectiveLiquidationIncentive == getLiquidationIncentive. + // The genuinely divergent case is an ERC20 debt with the borrower in a non-core pool — exercised + // on real contracts in the fork suite ("effective-incentive divergence"). We still assert effective + // here so the script stays correct if that VAI-core invariant is ever relaxed (e.g. e-mode VAI). await comptroller.setEffectiveIncentive(U("1.25")); const venusLiq = await ethers.getContractAt("MockVenusLiquidator", await comptroller.liquidatorContract()); await venusLiq.setTreasuryCut(U("0.5")); // 50% of the bonus, like BSC mainnet today diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts index 85b41531d..df3b65bc0 100644 --- a/tests/hardhat/Fork/BStockLiquidatorFork.ts +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -82,6 +82,23 @@ const PSM_FORK_ABI = [ "function isPaused() view returns (bool)", "function pause()", ]; +// e-mode pool admin surface used to stand up a NON-CORE pool that lists vBStock at a divergent +// liquidation incentive — the only reachable state where getEffectiveLiquidationIncentive differs +// from the core-pool getLiquidationIncentive (impossible for VAI, which is core-pool-locked). +const POOL_ADMIN_ABI = [ + "function createPool(string) returns (uint96)", + "function addPoolMarkets(uint96[],address[])", + "function setCollateralFactor(uint96,address,uint256,uint256) returns (uint256)", + "function setLiquidationIncentive(uint96,address,uint256) returns (uint256)", + "function setIsBorrowAllowed(uint96,address,bool)", + "function enterPool(uint96)", + "function enterMarkets(address[]) returns (uint256[])", + "function getLiquidationIncentive(address) view returns (uint256)", + "function getEffectiveLiquidationIncentive(address,address) view returns (uint256)", + "function treasuryPercent() view returns (uint256)", + "function liquidatorContract() view returns (address)", +]; +const VENUS_LIQUIDATOR_ABI = ["function treasuryPercentMantissa() view returns (uint256)"]; // bStock USD prices: healthy vs post-crash (the stock gapped down ~80%). const P_HEALTHY = parseUnits("250", 18); @@ -94,6 +111,7 @@ const B = { BNB: ethers.utils.getAddress("0x00000000000000000000000000000000ca110003"), VAI: ethers.utils.getAddress("0x00000000000000000000000000000000ca11000a"), VAI_SCRIPT: ethers.utils.getAddress("0x00000000000000000000000000000000ca11000b"), + EMODE: ethers.utils.getAddress("0x00000000000000000000000000000000ca11000d"), FLASH: ethers.utils.getAddress("0x00000000000000000000000000000000ca110004"), FORCED: ethers.utils.getAddress("0x00000000000000000000000000000000ca110005"), FORCED_SCRIPT: ethers.utils.getAddress("0x00000000000000000000000000000000ca11000c"), @@ -410,6 +428,57 @@ const test = () => { return seizeTokens.mul(xr).div(ONE).mul(P_CRASH).div(ONE).mul(90).div(100); } + // Stand up a NON-CORE e-mode pool that lists vBStock at a liquidation incentive DIFFERENT from + // core, put the borrower in it with an ERC20 (USDT) debt, and gap the stock into shortfall. This is + // the only state where getEffectiveLiquidationIncentive (pool-resolved) diverges from the core + // getLiquidationIncentive — VAI can never reach it (VAIController.mintVAI requires the core pool and + // hasValidPoolBorrows bars leaving it while VAI debt is open), so the treasury-cut divergence is + // proven here on a real ERC20 debt against the live diamond. + async function makeUnderwaterEmodeBorrower( + borrowerAddr: string, + poolIncentive: BigNumber, + borrowUsdt: BigNumber, + ): Promise { + const tl = await initMainnetUser(A.TIMELOCK, parseEther("10")); + const acm = new ethers.Contract(A.ACM, ACM_GIVE_ABI, tl); + for (const sig of [ + "createPool(string)", + "addPoolMarkets(uint96[],address[])", + "setCollateralFactor(uint96,address,uint256,uint256)", + "setLiquidationIncentive(uint96,address,uint256)", + "setIsBorrowAllowed(uint96,address,bool)", + ]) { + await acm.giveCallPermission(A.COMPTROLLER, sig, A.TIMELOCK); + } + const cTl = new ethers.Contract(A.COMPTROLLER, POOL_ADMIN_ABI, tl); + const poolId: BigNumber = await cTl.callStatic.createPool("bstock-emode-divergence"); + await cTl.createPool("bstock-emode-divergence"); + // vBStock is the collateral in the pool at a DIVERGENT incentive; VUSDT is the borrowable debt. + await cTl.addPoolMarkets([poolId, poolId], [mkt.vBStock.address, VUSDT]); + await cTl.setCollateralFactor(poolId, mkt.vBStock.address, parseUnits("0.6", 18), parseUnits("0.6", 18)); + await cTl.setLiquidationIncentive(poolId, mkt.vBStock.address, poolIncentive); + await cTl.setIsBorrowAllowed(poolId, VUSDT, true); + + const borrower = await initMainnetUser(borrowerAddr, parseUnits("10", 18)); + const COLLATERAL = parseUnits("100", 18); + await mkt.bStock.mint(borrowerAddr, COLLATERAL); + await mkt.bStock.connect(borrower).approve(mkt.vBStock.address, COLLATERAL); + await new ethers.Contract(mkt.vBStock.address, VTOKEN_ABI, borrower).mint(COLLATERAL); + const cAsBorrower = new ethers.Contract(A.COMPTROLLER, POOL_ADMIN_ABI, borrower); + await cAsBorrower.enterMarkets([mkt.vBStock.address]); + // Switch pools BEFORE borrowing: with no open debt the enterPool liquidity check passes trivially. + await cAsBorrower.enterPool(poolId); + // Borrow USDT while in the non-core pool. Legacy vTokens return an error CODE — probe, then assert. + const vUsdt = new ethers.Contract(VUSDT, VTOKEN_ABI, borrower); + const code: BigNumber = await vUsdt.callStatic.borrow(borrowUsdt); + if (!code.eq(0)) throw new Error(`borrow(VUSDT) in pool ${poolId.toString()} returned code ${code.toString()}`); + await vUsdt.borrow(borrowUsdt); + if ((await vUsdt.borrowBalanceStored(borrowerAddr)).eq(0)) throw new Error("borrow produced no debt"); + await mkt.setPrice(P_CRASH); + await mock.setRate(P_CRASH); + return poolId; + } + it("two-hop VAI debt, inventory mode: bStock->USDT (mock) -> VAI through the REAL PSM, script-built calldata", async () => { const vaiCtrl = await makeUnderwaterVaiBorrower(B.VAI, parseUnits("4000", 18)); const liqVai = await deployVaiLiquidator(); @@ -550,6 +619,115 @@ const test = () => { ); }); + it("effective-incentive divergence (ERC20 debt, non-core pool): script sizes the cut off effective and settles", async () => { + // The divergence the VAI unit test can only MODEL is REAL here. A non-core e-mode pool lists + // vBStock at 1.25x while core stays 1.1x, and the USDT borrower sits in that pool — so on the live + // diamond getEffectiveLiquidationIncentive(borrower, vBStock) (1.25) differs from the core + // getLiquidationIncentive(vBStock) (1.1). The gate sizes its treasury cut off the effective one for + // EVERY debt (Liquidator._splitLiquidationIncentive), so the script must too; a core-based cut + // would overstate the credited seize and missize the hop-1 sell. We prove (a) the two getters + // actually diverge on real contracts, (b) the script quotes hop-1 off the EFFECTIVE-derived seize, + // and (c) it settles end-to-end through the real gate. + const POOL_INCENTIVE = parseUnits("1.25", 18); + await makeUnderwaterEmodeBorrower(B.EMODE, POOL_INCENTIVE, parseUnits("5000", 18)); + + const comptroller = new ethers.Contract(A.COMPTROLLER, POOL_ADMIN_ABI, owner); + const coreInc: BigNumber = await comptroller.getLiquidationIncentive(mkt.vBStock.address); + const effInc: BigNumber = await comptroller.getEffectiveLiquidationIncentive(B.EMODE, mkt.vBStock.address); + // (a) real, reachable divergence — not a mock. + expect(coreInc).to.equal(parseUnits("1.1", 18)); + expect(effInc).to.equal(POOL_INCENTIVE); + expect(effInc.eq(coreInc)).to.equal(false); + + const REPAY = parseUnits("2000", 18); + // Reproduce the script's cut math for BOTH incentives so the assertion is discriminating. + const seizeLens = new ethers.Contract( + A.COMPTROLLER, + ["function liquidateCalculateSeizeTokens(address,address,address,uint256) view returns (uint256,uint256)"], + owner, + ); + const [, seizeTokens]: BigNumber[] = await seizeLens.liquidateCalculateSeizeTokens( + B.EMODE, + VUSDT, + mkt.vBStock.address, + REPAY, + ); + const xr: BigNumber = await mkt.vBStock.exchangeRateStored(); + const treasuryPercent: BigNumber = await comptroller.treasuryPercent(); + const gate: string = await comptroller.liquidatorContract(); + const treasuryPct: BigNumber = await new ethers.Contract( + gate, + VENUS_LIQUIDATOR_ABI, + owner, + ).treasuryPercentMantissa(); + // Mirrors atomic-liquidate.ts exactly (SEIZE_BUFFER=0 -> seizedForQuote == seizedRaw), so the + // human string below equals the script's quoted `amount` bit-for-bit. + const sellFor = (inc: BigNumber): BigNumber => { + const bonus = seizeTokens.mul(inc.sub(ONE)).div(inc); + const cut = bonus.mul(treasuryPct).div(ONE); + const vRecv = seizeTokens.sub(cut); + return vRecv.mul(xr).div(ONE).mul(ONE.sub(treasuryPercent)).div(ONE); + }; + const sellEff = sellFor(effInc); + const sellCore = sellFor(coreInc); + expect(sellEff.eq(sellCore)).to.equal(false); // the incentive choice materially moves the sell size + + // Run the SCRIPT and capture the hop-1 quote `amount` (bStock, human units) it asks Native for. + await fund(TOK.USDT, liq.address, REPAY); // inventory + const usdtDelivered = P_CRASH.mul(sellEff).div(ONE); // mock router pays this for the seized bStock + let quotedAmount: string | null = null; + const realFetch = globalThis.fetch; + globalThis.fetch = (async (url: unknown) => { + quotedAmount = new URL(String(url)).searchParams.get("amount"); + return { + json: async () => ({ + success: true, + recipient: liq.address, + amountIn: "0", + amountOut: usdtDelivered.toString(), + orders: [{ deadlineTimestamp: Math.floor(Date.now() / 1000) + 3600 }], + txRequest: { target: mock.address, calldata: buildSingleHopMock(mkt, mock, liq.address), value: "0" }, + }), + }; + }) as unknown as typeof fetch; + + const SCRIPT_ENV = [ + "LIQUIDATOR", + "BORROWER", + "VBSTOCK", + "VDEBT", + "REPAY_AMOUNT", + "MODE", + "SOURCE", + "NATIVE_API_KEY", + "SEIZE_BUFFER", + ]; + const borrowBefore = await new ethers.Contract(VUSDT, VTOKEN_ABI, owner).borrowBalanceStored(B.EMODE); + try { + Object.assign(process.env, { + LIQUIDATOR: liq.address, + BORROWER: B.EMODE, + VBSTOCK: mkt.vBStock.address, + VDEBT: VUSDT, + REPAY_AMOUNT: "2000", + MODE: "inventory", + SOURCE: "native", + NATIVE_API_KEY: "test-key", + SEIZE_BUFFER: "0", // no haircut -> the quoted amount equals the effective-cut seizedRaw exactly + }); + await atomicLiquidate(owner); + } finally { + globalThis.fetch = realFetch; + for (const k of SCRIPT_ENV) delete process.env[k]; + } + + // (b) the script quoted the EFFECTIVE-derived sell size — and it is NOT the core-derived one. + expect(quotedAmount).to.equal(ethers.utils.formatUnits(sellEff, 18)); + expect(quotedAmount).to.not.equal(ethers.utils.formatUnits(sellCore, 18)); + // (c) settled through the real gate. + await assertSettledFor(liq, mkt, VUSDT, B.EMODE, borrowBefore); + }); + it("minOut breach -> InsufficientOut, FULL rollback: borrow + inventory intact, nothing stranded", async () => { await makeUnderwaterBorrower({ mkt, From 0b4a7544a7997b92272a9196e6997b79fe40bcb4 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 17 Jul 2026 02:21:10 +0530 Subject: [PATCH 67/78] docs(bstock): expand liquidation flowchart and correct Native/LM/PSM fallback framing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rework the operator flowchart to reflect the current multi-source, VAI-capable scripts: pre-flight gates (debt-type detect, shortfall/ALLOW_NO_SHORTFALL, VAI-forces-inventory), hop-2 routing by debt type (single-hop USDT, AMM for other ERC20, WBNB unwrap for native BNB, PSM swapStableForVAI for VAI), and three distinct fallback triggers merging into one rail (no hop-1 source fills, VAI PSM paused/capped, dry-run fails). native-smoke probes Native ONLY — there is no Liquid Mesh liveness probe, so LM is only validated inside atomic-liquidate. The chart now says "Native quote alive?" and routes a Native-down case into atomic (SOURCE=liquidmesh) rather than straight to the Safe fallback. Also correct the safe-fallback.ts and README framing: the fallback triggers when the whole quote path is down (every RFQ source, or the VAI PSM hop), and it is RFQ/PSM-independent because it ships bStock to the CEX with no on-chain swap. --- scripts/bstock/README.md | 78 ++++++------ scripts/bstock/liquidation-flowchart.svg | 155 ++++++++++++++++------- scripts/bstock/safe-fallback.ts | 12 +- 3 files changed, 158 insertions(+), 87 deletions(-) diff --git a/scripts/bstock/README.md b/scripts/bstock/README.md index 11eef6341..10fb0a350 100644 --- a/scripts/bstock/README.md +++ b/scripts/bstock/README.md @@ -3,17 +3,17 @@ Operator runbook for liquidating bStock (tokenized-stock) collateral in Venus Core. Three entrypoints, one shared goal: repay a borrower's debt, seize their bStock, and offload it. -| Script | Path | What it does | Chain writes? | -| ----------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| **Atomic** | `atomic-liquidate.ts` | Primary path. Drives the on-chain `BStockLiquidator` — repay, seize, redeem, and sell bStock in ONE tx via a Native or Liquid Mesh RFQ quote (+ optional AMM hop). | Yes | -| **Safe fallback** | `safe-fallback.ts` | Backstop when Native is unavailable (API down / halt / weekend / thin depth). Emits a Safe{Wallet} batch JSON; signers repay from Safe funds and ship raw bStock to Binance. | No — writes JSON | -| **Native smoke** | `native-smoke.ts` | Pre-flight check. Prints the Native orderbook + a live firm-quote (price, spread, TTL). No chain interaction. | No | +| Script | Path | What it does | Chain writes? | +| ----------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| **Atomic** | `atomic-liquidate.ts` | Primary path. Drives the on-chain `BStockLiquidator` — repay, seize, redeem, and sell bStock in ONE tx via a Native or Liquid Mesh RFQ quote (+ optional AMM hop). | Yes | +| **Safe fallback** | `safe-fallback.ts` | Backstop when the quote path is unavailable — every hop-1 RFQ source (Native and Liquid Mesh) down, or for VAI the hop-2 PSM paused / cap-exhausted. Emits a Safe{Wallet} batch JSON; signers repay from Safe funds and ship raw bStock to Binance. | No — writes JSON | +| **Native smoke** | `native-smoke.ts` | Pre-flight check. Prints the Native orderbook + a live firm-quote (price, spread, TTL). No chain interaction. | No | -Pick **Atomic** first. Fall back to **Safe** only if the Native quote path fails. +Pick **Atomic** first. Fall back to **Safe** only if the whole quote path fails — all RFQ sources down, or (for VAI) the PSM hop paused/capped. ## Which script? — start here -![bStock liquidation decision flowchart: native-smoke -> atomic-liquidate (dry-run then send) with Liquid Mesh when Native is down, falling back to the Safe multisig batch when both RFQ sources are dead](liquidation-flowchart.svg) +![bStock liquidation decision flowchart: native-smoke checks the quote path, then atomic-liquidate.ts (DRY_RUN) runs pre-flight gates (debt-type detection, shortfall/ALLOW_NO_SHORTFALL, VAI-forces-inventory) and routes hop-2 by debt type — single-hop USDT, AMM for other ERC20, WBNB unwrap for native BNB, or the PSM swapStableForVAI for VAI — sending atomically when the dry-run passes, and falling back to the Safe multisig batch (which ships bStock to the CEX, PSM-independent) when the RFQ path is dead, the VAI PSM is paused or cap-exhausted, or the dry-run fails](liquidation-flowchart.svg) Rules of thumb: @@ -108,29 +108,29 @@ call `liquidate`/`flashLiquidate`. ### Env -| Var | Req | Default | Notes | -| --------------------- | --- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `LIQUIDATOR` | ✓ | | Deployed `BStockLiquidator` address | -| `BORROWER` | ✓ | | Account to liquidate (must have shortfall) | -| `VBSTOCK` | ✓ | | bStock collateral market (e.g. vTSLAB) | -| `VDEBT` | ✓ | | Borrowed market to repay (e.g. vUSDT) | -| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | -| `NATIVE_API_KEY` | | | Native Swap API key (required for `native`/`auto`) | -| `MODE` | | `inventory` | `inventory` (own funds) or `flash` (Venus flash-loan) | -| `SOURCE` | | `auto` | Hop-1 source: `auto` (price all available, take higher) / `native` / `liquidmesh` / comma-subset (e.g. `native,liquidmesh`) | -| `LM_API_KEY` | | | Liquid Mesh API key (required for `liquidmesh`/`auto`) | -| `LM_PRIVATE_KEY_SEED` | | | Liquid Mesh Ed25519 seed, base64url (required for `liquidmesh`/`auto`) | -| `LM_MIN_TTL` | | `15` | Min seconds left on the LM order at build time, else abort (LM RFQ orders are short-lived; the on-chain deadline still enforces the real expiry) | -| `SOURCE_TIMEOUT_MS` | | `8000` | Per-request timeout (ms) on each hop-1 source API call, so a hung source aborts and drops out of the `auto` race instead of blocking the live one | -| `SETTLE_TTL_MARGIN` | | `10` | Min seconds of quote TTL required immediately before submit; below it the script aborts + refetches instead of burning gas on an on-chain `DeadlineExpired` | -| `ALLOW_NO_SHORTFALL` | | | `1` → proceed even when the borrower has no shortfall (FORCED liquidation of a healthy account); default aborts as a fat-finger guard | -| `DRY_RUN` | | | `1` → callStatic only, sends nothing | -| `SLIPPAGE` | | `0.5` | Native/LM slippage % (validated to `[0,100)`) | +| Var | Req | Default | Notes | +| --------------------- | --- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `LIQUIDATOR` | ✓ | | Deployed `BStockLiquidator` address | +| `BORROWER` | ✓ | | Account to liquidate (must have shortfall) | +| `VBSTOCK` | ✓ | | bStock collateral market (e.g. vTSLAB) | +| `VDEBT` | ✓ | | Borrowed market to repay (e.g. vUSDT) | +| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | +| `NATIVE_API_KEY` | | | Native Swap API key (required for `native`/`auto`) | +| `MODE` | | `inventory` | `inventory` (own funds) or `flash` (Venus flash-loan) | +| `SOURCE` | | `auto` | Hop-1 source: `auto` (price all available, take higher) / `native` / `liquidmesh` / comma-subset (e.g. `native,liquidmesh`) | +| `LM_API_KEY` | | | Liquid Mesh API key (required for `liquidmesh`/`auto`) | +| `LM_PRIVATE_KEY_SEED` | | | Liquid Mesh Ed25519 seed, base64url (required for `liquidmesh`/`auto`) | +| `LM_MIN_TTL` | | `15` | Min seconds left on the LM order at build time, else abort (LM RFQ orders are short-lived; the on-chain deadline still enforces the real expiry) | +| `SOURCE_TIMEOUT_MS` | | `8000` | Per-request timeout (ms) on each hop-1 source API call, so a hung source aborts and drops out of the `auto` race instead of blocking the live one | +| `SETTLE_TTL_MARGIN` | | `10` | Min seconds of quote TTL required immediately before submit; below it the script aborts + refetches instead of burning gas on an on-chain `DeadlineExpired` | +| `ALLOW_NO_SHORTFALL` | | | `1` → proceed even when the borrower has no shortfall (FORCED liquidation of a healthy account); default aborts as a fat-finger guard | +| `DRY_RUN` | | | `1` → callStatic only, sends nothing | +| `SLIPPAGE` | | `0.5` | Native/LM slippage % (validated to `[0,100)`) | | `MIN_OUT_BUFFER` | | `0.5` | Extra haircut on `minOut` beyond slippage (%) (validated to `[0,100)`). For a single-hop indicative (Liquid Mesh) quote, `minOut` is derived from the built order's guaranteed floor, not the indicative out | -| `SEIZE_BUFFER` | | `0.1` | Haircut on the QUOTED seize (%) so an oracle uptick before inclusion can't make the router pull more bStock than was seized (→ `SwapFailed`); the unsold remainder stays as bStock inventory (sweepable) | -| `AMM_PROVIDER` | | `kyberswap` | Hop-2 route for non-USDT debt: `kyberswap` / `openocean` / `pcsv2` | -| `PSM_ADDR` | | BSC PSM | Peg Stability Module used as hop 2 for a VAI debt (`swapStableForVAI` calldata encoded locally, expected out from `previewSwapStableForVAI`); must be allowlisted via `setRouter`. `MODE=flash` rejected | -| `WBNB_ADDR` | | BSC WBNB | Only for a vBNB debt market (native BNB auto-detected; contract unwraps) | +| `SEIZE_BUFFER` | | `0.1` | Haircut on the QUOTED seize (%) so an oracle uptick before inclusion can't make the router pull more bStock than was seized (→ `SwapFailed`); the unsold remainder stays as bStock inventory (sweepable) | +| `AMM_PROVIDER` | | `kyberswap` | Hop-2 route for non-USDT debt: `kyberswap` / `openocean` / `pcsv2` | +| `PSM_ADDR` | | BSC PSM | Peg Stability Module used as hop 2 for a VAI debt (`swapStableForVAI` calldata encoded locally, expected out from `previewSwapStableForVAI`); must be allowlisted via `setRouter`. `MODE=flash` rejected | +| `WBNB_ADDR` | | BSC WBNB | Only for a vBNB debt market (native BNB auto-detected; contract unwraps) | _Fork/local testing:_ `MOCK_NATIVE="router:calldata"` (hop 1), `MOCK_AMM` (hop 2), `MOCK_OUT` (final debt out), `IMPERSONATE=0x..` to run as an operator. @@ -158,17 +158,17 @@ The batch is 3–4 txs (approve → liquidateBorrow → redeem → transfer; the native BNB debt, so 3): the Safe repays from its own funds, seizes the bStock, and ships raw bStock to `TARGET` (Binance top-up / custody) for finance to offload on the CEX. -| Var | Req | Default | Notes | -| -------------- | --- | ------------------------------- | -------------------------------------------------------------------------------------- | -| `BORROWER` | ✓ | | Account to liquidate | -| `VBSTOCK` | ✓ | | bStock collateral market | -| `VDEBT` | ✓ | | Borrowed market to repay | -| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | -| `TARGET` | ✓ | | Binance top-up / custody address for the bStock (or `ALLOW_PLACEHOLDER=1` for a draft) | -| `SAFE` | | `0xdc6E…2029` | Executing Safe | -| `RPC_URL` | | public dataseed | BSC RPC | +| Var | Req | Default | Notes | +| -------------- | --- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| `BORROWER` | ✓ | | Account to liquidate | +| `VBSTOCK` | ✓ | | bStock collateral market | +| `VDEBT` | ✓ | | Borrowed market to repay | +| `REPAY_AMOUNT` | ✓ | | Repay in debt underlying, human units | +| `TARGET` | ✓ | | Binance top-up / custody address for the bStock (or `ALLOW_PLACEHOLDER=1` for a draft) | +| `SAFE` | | `0xdc6E…2029` | Executing Safe | +| `RPC_URL` | | public dataseed | BSC RPC | | `SEIZE_BUFFER` | | `0.1` | Haircut % on the redeem/transfer amounts, absorbing oracle price drift before the Safe executes; the unredeemed dust is sweepable | -| `OUT` | | `out/bstock-safe-fallback.json` | Output path | +| `OUT` | | `out/bstock-safe-fallback.json` | Output path | > The batch is a **snapshot** at the current block. `SEIZE_BUFFER` absorbs small oracle price drift, but > **price drift alone** (not just a position change) can still invalidate the exact amounts — a stale diff --git a/scripts/bstock/liquidation-flowchart.svg b/scripts/bstock/liquidation-flowchart.svg index e9c3bb820..5fe816462 100644 --- a/scripts/bstock/liquidation-flowchart.svg +++ b/scripts/bstock/liquidation-flowchart.svg @@ -1,4 +1,4 @@ - + @@ -10,65 +10,134 @@ .edge { stroke: #475569; stroke-width: 1.6; fill: none; } .elbl { fill: #334155; font-size: 12px; font-style: italic; } .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12.5px; } + .bul { fill: #0f172a; font-size: 11.5px; } + .bmono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; fill: #0f172a; } + .grp { fill: #f8fafc; stroke: #cbd5e1; stroke-width: 1.2; stroke-dasharray: 4 3; } - + - - - - - RFQ alive - + + + 2 · atomic-liquidate.ts  (DRY_RUN=1) + + + + + + + + + alive · auto + + + down → + try LM + + + + + + + + no source fills + + + + + + + PSM live · or non-VAI + + + VAI: PSM paused / cap full - - yes - - - no · can't fix fast - - - both RFQ dead - - + + yes + + + no · can't fix fast + + + - - bStock borrower in shortfall + + bStock borrower in shortfall - - 1 · native-smoke.ts - read-only, ~10s — is the quote path alive? + + 1 · native-smoke.ts + read-only, ~10s — is the Native quote alive? - - Quote path? + + Native quote alive? + Native only · LM tried in atomic + + + + pre-flight gates + • detect debt: VAI · native BNB · ERC20 (USDT / other) + • shortfall = 0 → ABORT unless ALLOW_NO_SHORTFALL=1 (forced) + • VAI ⇒ MODE=inventory (flash rejected: no vVAI) + • size seize − treasury cut (effective incentive) + + + + hop-1 bStock → USDT — Native / Liquid Mesh + SOURCE=auto prices both, takes best out (LM if Native down) - - - 2 · atomic-liquidate.ts (DRY_RUN=1) - prices Native + Liquid Mesh, settles the winner - SOURCE=liquidmesh if Native is down + + + hop-2 route — by debt type + • USDT → single hop (hop-1 IS the debt asset) + • ERC20 ≠ USDT → AMM: kyber / openocean / pcsv2 + • native BNB → USDT → WBNB (contract unwraps) + • VAI → PSM.swapStableForVAI (calldata local, lib/psm.ts) + pause + mint-cap headroom pre-flighted next - - - Dry-run passes? + + + VAI: PSM usable? + not paused & cap headroom ≥ pull + + + + Dry-run passes? + callStatic liquidate / flashLiquidate - - re-run WITHOUT DRY_RUN=1 - → liquidation sent ✓ + + re-run WITHOUT DRY_RUN=1 + → liquidation sent ✓ + MODE=inventory, or flash (non-VAI only) - - 3 · safe-fallback.ts - writes Safe batch JSON (no send) + + 3 · safe-fallback.ts + writes Safe batch JSON (no send) + repay from Safe funds → seize → ship bStock + no on-chain swap — RFQ / PSM independent - - Safe → Transaction Builder - load JSON, review, sign, execute - raw bStock ships to CEX for offload + + Safe → Transaction Builder + load JSON, review, sign, execute + raw bStock ships to CEX for offload + + + + legend + + script step + + decision + + hop-2 routing (VAI = PSM) + + sent atomically + + Safe multisig fallback diff --git a/scripts/bstock/safe-fallback.ts b/scripts/bstock/safe-fallback.ts index 6f8a27bc8..82b32ebad 100644 --- a/scripts/bstock/safe-fallback.ts +++ b/scripts/bstock/safe-fallback.ts @@ -1,10 +1,12 @@ /** - * bStock backstop liquidation — Safe multisig FALLBACK flow (no Native swap). + * bStock backstop liquidation — Safe multisig FALLBACK flow (no on-chain swap). * - * When the Native RFQ path is unavailable (API down, halt, weekend, thin depth), the liquidation is - * settled manually: a Safe{Wallet} multisig repays the bad debt with its OWN funds, seizes the - * bStock, and ships the raw bStock to a custody/Binance top-up address where finance offloads it on - * the CEX. + * When the atomic path's quote leg is unavailable — EVERY hop-1 RFQ source down (both Native and Liquid + * Mesh: API halt, weekend, thin depth) or, for a VAI debt, the hop-2 PSM paused / mint-cap exhausted — + * the liquidation is settled manually: a Safe{Wallet} multisig repays the bad debt with its OWN funds, + * seizes the bStock, and ships the raw bStock to a custody/Binance top-up address where finance offloads + * it on the CEX. Because nothing is swapped on-chain here, this flow is independent of the RFQ sources + * and the PSM — which is exactly why it is the fallback when either is down. * * This script does NOT send anything. It READS chain state and EMITS a Safe Transaction Builder * batch JSON for the signers to review and execute. From c18c57195fc80cbfe839d3ee641870706fbbaaaf Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 17 Jul 2026 02:24:30 +0530 Subject: [PATCH 68/78] docs(bstock): stop the atomic arrow overlapping the DRY_RUN=1 header label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the group header so "2 · atomic-liquidate.ts" stays left and "DRY_RUN=1" is right-aligned, leaving the vertical "alive · auto" edge a clear center gap. --- scripts/bstock/liquidation-flowchart.svg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/bstock/liquidation-flowchart.svg b/scripts/bstock/liquidation-flowchart.svg index 5fe816462..f23486a5f 100644 --- a/scripts/bstock/liquidation-flowchart.svg +++ b/scripts/bstock/liquidation-flowchart.svg @@ -20,7 +20,8 @@ - 2 · atomic-liquidate.ts  (DRY_RUN=1) + 2 · atomic-liquidate.ts + DRY_RUN=1 From 316bbb480fdb00d9896ec51f26cb32a15ccde44f Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 17 Jul 2026 02:29:48 +0530 Subject: [PATCH 69/78] fix(bstock): name the actual seize function in VAI error messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The seize error strings hardcoded liquidateCalculateSeizeTokens even on the isVai branch, which calls liquidateVAICalculateSeizeTokens — misleading when a VAI liquidation or Safe-batch build fails. Derive the function name from isVai and interpolate it into both throws in atomic-liquidate.ts and safe-fallback.ts. --- scripts/bstock/atomic-liquidate.ts | 5 +++-- scripts/bstock/safe-fallback.ts | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 8350258df..b1b75dec5 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -282,13 +282,14 @@ export async function atomicLiquidate(signer: Signer) { // - else -> vToken.liquidateBorrowFresh calls the borrower-aware 4-arg overload (reads the pool the // borrower is actually in). The 3-arg overload always reads Core Pool params and diverges // if the borrower has switched pools. + const seizeFn = isVai ? "liquidateVAICalculateSeizeTokens" : "liquidateCalculateSeizeTokens"; const [seizeErr, seizeTokens]: BigNumber[] = isVai ? await comptroller.liquidateVAICalculateSeizeTokens(vBStock.address, repay) : await comptroller.liquidateCalculateSeizeTokens(borrower, vDebt.address, vBStock.address, repay); - if (!seizeErr.eq(0)) throw new Error(`liquidateCalculateSeizeTokens error ${seizeErr}`); + if (!seizeErr.eq(0)) throw new Error(`${seizeFn} error ${seizeErr}`); // A zero seize means the incentive resolved to 0 (e.g. bStock unlisted in the borrower's pool): // surface it here rather than building a degenerate quote that reverts on-chain. - if (seizeTokens.eq(0)) throw new Error(`liquidateCalculateSeizeTokens returned 0 seize for ${borrower}`); + if (seizeTokens.eq(0)) throw new Error(`${seizeFn} returned 0 seize for ${borrower}`); const exchangeRate: BigNumber = await vBStock.exchangeRateStored(); const ONE = BigNumber.from(10).pow(18); diff --git a/scripts/bstock/safe-fallback.ts b/scripts/bstock/safe-fallback.ts index 82b32ebad..0631cd068 100644 --- a/scripts/bstock/safe-fallback.ts +++ b/scripts/bstock/safe-fallback.ts @@ -175,13 +175,14 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { // - else -> vToken.liquidateBorrowFresh calls the borrower-aware 4-arg overload (reads the // borrower's actual pool). The 3-arg overload always reads Core Pool params and diverges // if the borrower has switched pools, producing a stale redeem amount in the batch. + const seizeFn = isVai ? "liquidateVAICalculateSeizeTokens" : "liquidateCalculateSeizeTokens"; const [seizeErr, seizeTokens]: BigNumber[] = isVai ? await comptroller.liquidateVAICalculateSeizeTokens(vBStock.address, repay) : await comptroller.liquidateCalculateSeizeTokens(borrower, vDebt.address, vBStock.address, repay); - if (!seizeErr.eq(0)) throw new Error(`liquidateCalculateSeizeTokens error code ${seizeErr}`); + if (!seizeErr.eq(0)) throw new Error(`${seizeFn} error code ${seizeErr}`); // A zero seize means the incentive resolved to 0 (e.g. bStock unlisted in the borrower's pool): // surface it here rather than baking a degenerate redeem amount into the batch. - if (seizeTokens.eq(0)) throw new Error(`liquidateCalculateSeizeTokens returned 0 seize for ${borrower}`); + if (seizeTokens.eq(0)) throw new Error(`${seizeFn} returned 0 seize for ${borrower}`); // The Venus Liquidator keeps a treasury cut of the liquidation BONUS (see // Liquidator._splitLiquidationIncentive), so the Safe is credited fewer vTokens than seizeTokens. From b1815d06f0e626c675298cf923c49f0259fc2b93 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 17 Jul 2026 11:42:15 +0530 Subject: [PATCH 70/78] feat(bstock): add lm-smoke probe, align smoke output, clarify naming and docs - Add lm-smoke.ts: a read-only Liquid Mesh /quote probe, the counterpart to native-smoke.ts (never /swap; one best-effort symbol() label read, reusing ARCHIVE_NODE_bscmainnet). Both smokes now print the same shape (amountIn/out, px/token) with a mirrored firm-only / indicative block. - Rename the hop-1 output var nativeOut -> usdtOut in atomic-liquidate.ts (it holds the USDT intermediate; "native" collided with native-BNB debt and the Native source) and de-stale "Native quote" -> "hop-1 quote" wording now that hop-1 is multi-source. - Document the VAI path in the atomic header (PSM swapStableForVAI hop-2, inventory-only) and the test/fork env overrides (MOCK_NATIVE/MOCK_OUT/IMPERSONATE/USDT_ADDR/WBNB_ADDR). - Refresh the flowchart + README: box 1 shows native-smoke and lm-smoke, the decision is "any RFQ source live?", and the Safe fallback trigger is the whole quote path (all RFQ sources down, or the VAI PSM paused/capped) rather than "Native unavailable". - Add the bStock secrets to .env.example (NATIVE_API_KEY, LM_API_KEY, LM_PRIVATE_KEY_SEED, optional RPC_URL). Unit: tests/hardhat/BStock*.ts - 106 passing. --- .env.example | 8 ++ scripts/bstock/README.md | 118 +++++++++++++++++++++-- scripts/bstock/atomic-liquidate.ts | 40 +++++--- scripts/bstock/liquidation-flowchart.svg | 23 +++-- scripts/bstock/lm-smoke.ts | 81 ++++++++++++++++ scripts/bstock/native-smoke.ts | 29 +++--- 6 files changed, 249 insertions(+), 50 deletions(-) create mode 100644 scripts/bstock/lm-smoke.ts diff --git a/.env.example b/.env.example index 8bbb73879..6c249fe25 100644 --- a/.env.example +++ b/.env.example @@ -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. diff --git a/scripts/bstock/README.md b/scripts/bstock/README.md index 10fb0a350..b55cf9f66 100644 --- a/scripts/bstock/README.md +++ b/scripts/bstock/README.md @@ -7,13 +7,14 @@ one shared goal: repay a borrower's debt, seize their bStock, and offload it. | ----------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | | **Atomic** | `atomic-liquidate.ts` | Primary path. Drives the on-chain `BStockLiquidator` — repay, seize, redeem, and sell bStock in ONE tx via a Native or Liquid Mesh RFQ quote (+ optional AMM hop). | Yes | | **Safe fallback** | `safe-fallback.ts` | Backstop when the quote path is unavailable — every hop-1 RFQ source (Native and Liquid Mesh) down, or for VAI the hop-2 PSM paused / cap-exhausted. Emits a Safe{Wallet} batch JSON; signers repay from Safe funds and ship raw bStock to Binance. | No — writes JSON | -| **Native smoke** | `native-smoke.ts` | Pre-flight check. Prints the Native orderbook + a live firm-quote (price, spread, TTL). No chain interaction. | No | +| **Native smoke** | `native-smoke.ts` | Pre-flight check. Fetches a live Native firm-quote (price, TTL, executable router). No chain interaction. | No | +| **LM smoke** | `lm-smoke.ts` | Pre-flight check. Fetches a live Liquid Mesh `/quote` (price, output, route split). Read-only — no `/swap`; only a best-effort `symbol()` read for the label. | No | Pick **Atomic** first. Fall back to **Safe** only if the whole quote path fails — all RFQ sources down, or (for VAI) the PSM hop paused/capped. ## Which script? — start here -![bStock liquidation decision flowchart: native-smoke checks the quote path, then atomic-liquidate.ts (DRY_RUN) runs pre-flight gates (debt-type detection, shortfall/ALLOW_NO_SHORTFALL, VAI-forces-inventory) and routes hop-2 by debt type — single-hop USDT, AMM for other ERC20, WBNB unwrap for native BNB, or the PSM swapStableForVAI for VAI — sending atomically when the dry-run passes, and falling back to the Safe multisig batch (which ships bStock to the CEX, PSM-independent) when the RFQ path is dead, the VAI PSM is paused or cap-exhausted, or the dry-run fails](liquidation-flowchart.svg) +![bStock liquidation decision flowchart: native-smoke and lm-smoke check which RFQ sources are live, then atomic-liquidate.ts (DRY_RUN) runs pre-flight gates (debt-type detection, shortfall/ALLOW_NO_SHORTFALL, VAI-forces-inventory) and routes hop-2 by debt type — single-hop USDT, AMM for other ERC20, WBNB unwrap for native BNB, or the PSM swapStableForVAI for VAI — sending atomically when the dry-run passes, and falling back to the Safe multisig batch (which ships bStock to the CEX, PSM-independent) when the RFQ path is dead, the VAI PSM is paused or cap-exhausted, or the dry-run fails](liquidation-flowchart.svg) Rules of thumb: @@ -32,6 +33,8 @@ Rules of thumb: - **Native API key** (`NATIVE_API_KEY`) for anything that fetches a Native quote. Never commit it. - **Liquid Mesh credentials** (only if `SOURCE=liquidmesh` or `SOURCE=auto`): `LM_API_KEY` and the Ed25519 private-key seed `LM_PRIVATE_KEY_SEED` (base64url). Never commit them. +- These three keys can live in `.env` (git-ignored, loaded automatically by `hardhat.config.ts` when you + run through `npx hardhat`); see `.env.example`. Everything else below is a per-run flag passed inline. - **Deployed `BStockLiquidator`** (see `deploy/019-deploy-bstock-liquidator.ts`). Its owner must have run: - `setRouter(router, true)` for every router the swap will touch (the Native RFQ router and/or the Liquid Mesh router, plus the AMM router for non-USDT debt). The scripts **abort** if a router isn't @@ -82,11 +85,67 @@ Two Liquid-Mesh mechanics, both handled automatically: ## 1. Native smoke — verify the quote path ```bash -NATIVE_API_KEY=... TOKEN=TSLAB AMOUNT=5 npx hardhat run scripts/bstock/native-smoke.ts +# NATIVE_API_KEY read from .env +TOKEN=TSLAB AMOUNT=5 npx hardhat run scripts/bstock/native-smoke.ts ``` -Run this first during an incident to confirm Native is live and see the current price/spread/TTL. -`TOKEN` = any bStock in the orderbook (default `TSLAB`), `AMOUNT` = bStock to sell (default `1`). +```text +Native quote: 5 TSLAB (0x5b19…292f) -> USDT + amountIn : 5 TSLAB + amountOut: 1924.5 USDT + px/token : 384.9000 USDT + -- firm-only -- + deadline : 1752… (58s TTL) + router : 0x… ← executable txRequest.target + orders : 1 +``` + +Run this first during an incident to confirm Native is live and see the current price/TTL. `TOKEN` = any +bStock in the orderbook (default `TSLAB`), `AMOUNT` = bStock to sell (default `1`). The shared fields +(amountIn/out, px/token) match `lm-smoke`; the `firm-only` block (deadline, executable router, orders) is +what a **firm** MM-signed RFQ carries that an **indicative** LM `/quote` does not (LM instead shows +price-impact / mid-price / route split — see §1b). + +--- + +## 1b. Liquid Mesh smoke — verify the LM quote path + +```bash +# keys read from .env (LM_API_KEY, LM_PRIVATE_KEY_SEED) +AMOUNT=5 npx hardhat run scripts/bstock/lm-smoke.ts +# or price a different bStock (LM has no orderbook, so pass the address): +BSTOCK=0x… AMOUNT=5 npx hardhat run scripts/bstock/lm-smoke.ts +``` + +```text +Liquid Mesh quote: 5 TSLAB (0x5b19…292f) -> USDT + amountIn : 5 TSLAB + amountOut: 1927.13 USDT + px/token : 385.4300 USDT + -- indicative -- + priceImp : 0.0000117% + midPrice : 385.43 + est. gas : 219000 + route : rfq_neptune:99% uniswap_v4:1% + deadline : none at quote time (set only on the built /swap order) +``` + +Same shape as `native-smoke` (header, amountIn/out, px/token), with an `indicative` block instead of +Native's `firm-only` one — the only difference is which fields each API returns (see §1). + +The Liquid Mesh counterpart of the Native smoke: fetches a live `/quote` (bStock → USDT) and prints +price, output, price impact, mid-price and the route split (which makers/dexes filled, at what weight). +Use it when `SOURCE=liquidmesh`/`auto` to confirm LM is live and priced, independently of Native. + +- **Read-only.** It calls `/quote` only — never `/swap`. The one chain read is a best-effort `symbol()` + for the label (via `RPC_URL`, else `ARCHIVE_NODE_bscmainnet`, else a public dataseed); it falls back to + the raw address if no RPC is reachable, so the quote itself never depends on a chain. +- **No orderbook** — the token is given by address (`BSTOCK`, default `TSLAB`); `AMOUNT` = bStock to sell + (default `1`), treated as 1e18 units. +- **No quote-level TTL** — a `/quote` is indicative; only the built `/swap` order carries a deadline. This + probe confirms liveness + price, nothing more. + +An executable `/swap` blob and its actual on-chain fill are proven separately by `verify-lm-fork.ts`. --- @@ -98,7 +157,8 @@ NATIVE_API_KEY=... LIQUIDATOR=0x.. BORROWER=0x.. VBSTOCK=0x.. VDEBT=0x.. REPAY_A ``` Flow: precompute the exact seize → fetch the hop-1 quote (bStock→USDT) from the best of Native / Liquid -Mesh with the taker = the contract → for non-USDT debt, append an AMM hop (USDT→debt) → +Mesh with the taker = the contract → for non-USDT debt, append a hop-2 (USDT→debt): an AMM for an ERC20 +debt, or the PSM (`swapStableForVAI`) for a VAI debt → call `liquidate`/`flashLiquidate`. **Always dry-run first** (`DRY_RUN=1`) — it `callStatic`s the settle with no send. @@ -144,10 +204,13 @@ tuning); `NATIVE_API_BASE`, `LM_API_HOST` (RFQ endpoints). ## 3. Safe fallback — manual multisig path -Use when Native is unavailable. Reads chain state and writes a Safe Transaction Builder batch JSON — +Use when the whole quote path is unavailable — every hop-1 RFQ source (Native and Liquid Mesh) down, or +for a VAI debt the hop-2 PSM paused / cap-exhausted. Reads chain state and writes a Safe Transaction +Builder batch JSON — **it sends nothing.** ```bash +# standalone — builds its own provider (RPC_URL), so it uses ts-node, not `hardhat run` BORROWER=0x.. VBSTOCK=0x.. VDEBT=0x.. REPAY_AMOUNT=5000 TARGET=0x.. \ npx ts-node scripts/bstock/safe-fallback.ts ``` @@ -174,3 +237,44 @@ native BNB debt, so 3): the Safe repays from its own funds, seizes the bStock, a > **price drift alone** (not just a position change) can still invalidate the exact amounts — a stale > redeem/transfer that exceeds the seized balance reverts the batch. **Regenerate immediately before > signing** for anything but a tiny move. + +--- + +## 4. Verify LM fork — dev-time on-chain proof (not for incidents) + +Proves a Liquid Mesh `disableSimulate:true` swap blob actually **executes on-chain** — the one thing the +mocked unit suite can't cover. It forks bscmainnet at head, fetches a live `/quote` + `/swap`, then +replicates the contract's `_swap` exactly (approve the LM spender → `router.call(callMsg.data)`) using a +**real, already-allowlisted** bStock holder as the taker, and asserts USDT ≥ `minOut` landed. One-shot +diagnostic — run it when onboarding or debugging the LM integration, never during a live liquidation. + +```bash +FORKED_NETWORK=bscmainnet AMOUNT=3 npx hardhat run scripts/bstock/verify-lm-fork.ts +# LM_API_KEY, LM_PRIVATE_KEY_SEED, ARCHIVE_NODE_bscmainnet read from .env +``` + +**Required config tweak** (temporary, in `hardhat.config.ts` `isFork()`): the RFQ maker orders are +EIP-712 signed with chainId 56 and the head block needs modern opcodes, neither of which the default +fork network provides. Add: + +```ts +chainId: 56, +chains: { 56: { hardforkHistory: { berlin: 0, london: 13000000, shanghai: 40000000, cancun: 48000000 } } } +``` + +Without `chainId: 56` the maker signature fails `InvalidSignature` (the fork otherwise runs as 31337) — +a fork artifact, not a real defect; the script aborts early with this hint if the chainId is wrong. + +| Var | Req | Default | Notes | +| ------------------------- | --- | ----------------------- | ------------------------------------------------------------------------ | +| `LM_API_KEY` | ✓ | | Liquid Mesh API key | +| `LM_PRIVATE_KEY_SEED` | ✓ | | Ed25519 seed, base64url (32 bytes) | +| `ARCHIVE_NODE_bscmainnet` | ✓ | | Archive RPC to fork at head (maker state must match head or it reverts) | +| `FORKED_NETWORK` | ✓ | | Must be `bscmainnet` | +| `BSTOCK` | | `TSLAB` (`0x5b19…292f`) | bStock token to sell | +| `AMOUNT` | | `10` | bStock to sell, human units | +| `TAKER` | | auto (recent LM holder) | Override the taker; default scans recent Transfer logs for a real holder | + +On success it prints the fork block, the LM quote + route, gas, USDT received vs `minOut`, and a +`✅ PASS` — proving the same approve-spender → `router.call` sequence succeeds inside the liquidator's +`_swap`. diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index b1b75dec5..0d59b7e70 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -4,10 +4,12 @@ * One tx: the contract repays the borrow, seizes + redeems the bStock, and sells it to the debt * asset in one or two hops. Hop 1 is a pre-fetched best-of quote (bStock -> USDT) from the source * registry (Native RFQ / Liquid Mesh / future sources — see lib/sources.ts). For a USDT debt market that - * single hop is the debt asset; for a non-USDT debt market a second hop (USDT -> debt via an allowlisted - * AMM/aggregator, see lib/amm.ts) is appended. This script does the OFF-CHAIN half (precompute the seize, - * fetch the quotes with the taker = the contract) and then calls `liquidate` (inventory mode) or - * `flashLiquidate` (Venus flash-loan mode). + * single hop is the debt asset; for a non-USDT debt market a second hop (USDT -> debt) is appended — via + * an allowlisted AMM/aggregator (lib/amm.ts) for an ERC20 debt, or via the Peg Stability Module + * (`swapStableForVAI`, lib/psm.ts) for a VAI debt. VAI is inventory-only (no vVAI to flash from) and its + * seize keys off `liquidateVAICalculateSeizeTokens`. Native BNB debt (vBNB) is handled too (see below). + * This script does the OFF-CHAIN half (precompute the seize, fetch the quotes with the taker = the + * contract) and then calls `liquidate` (inventory mode) or `flashLiquidate` (Venus flash-loan mode). * * 1. Comptroller.liquidateCalculateSeizeTokens(borrower, vDebt, vBStock, repay) -> seize vTokens * 2. seizeTokens * vBStock.exchangeRateStored() / 1e18 -> raw bStock (floor) @@ -58,8 +60,14 @@ * PegStability_USDT). Must be allowlisted via setRouter; calldata is encoded * locally (lib/psm.ts). MODE=flash is rejected for VAI (no vVAI to flash from). * DRY_RUN "1" -> callStatic only, send nothing - * MOCK_NATIVE hop-1 "router:calldata" for fork/local tests (see below) - * MOCK_AMM hop-2 "router:calldata" for fork/local tests (two-hop); MOCK_OUT = final debt out + * + * Test / fork overrides (normally unset): + * MOCK_NATIVE hop-1 "router:calldata" that bypasses the source registry (name is historical — it + * mocks whichever hop-1 source, not only Native); MOCK_OUT = the out it should report + * MOCK_AMM hop-2 "router:calldata" for the two-hop path; MOCK_OUT = final debt out + * IMPERSONATE address to impersonate as the caller on a fork (hardhat_impersonateAccount) + * USDT_ADDR override the hop-1 output / intermediate token (default BSC USDT) + * WBNB_ADDR override WBNB for the native-BNB accounting (default canonical BSC WBNB) * * Native BNB debt (vBNB) is auto-detected — vBNB has no underlying() — and accounted in WBNB at its * canonical BSC address; the contract unwraps the repay, so pre-fund inventory in WBNB (MODE=inventory). @@ -196,11 +204,11 @@ export async function atomicLiquidate(signer: Signer) { } // Bound the haircut: a garbage or oversized value would silently under-quote (and in flash mode // starve the principal + premium repay). The on-chain InsufficientOut still backstops it, but fail - // loudly here instead of after burning a Native quote. + // loudly here instead of after burning a hop-1 quote. if (!Number.isFinite(seizeBufferPct) || seizeBufferPct < 0 || seizeBufferPct >= 100) { throw new Error(`SEIZE_BUFFER must be a percent in [0, 100), got "${process.env.SEIZE_BUFFER}"`); } - // Minimum remaining Native-quote TTL (seconds) required just before submitting the settle tx. The + // Minimum remaining hop-1 quote TTL (seconds) required just before submitting the settle tx. The // two-hop AMM round-trip and on-chain reads after the initial TTL check consume wall-clock, so the // quote is re-verified against this margin to abort + refetch rather than burn gas on an on-chain // DeadlineExpired revert. @@ -355,8 +363,8 @@ export async function atomicLiquidate(signer: Signer) { // - other debt -> two hops: bStock->USDT (hop 1, from the source registry), then USDT->debt via an // allowlisted AMM/aggregator (hop 2, see lib/amm.ts). `minOut` is in the // debt asset across the whole chain; `amountOut` below is the FINAL debt out. - const nativeOut = ethers.utils.getAddress(process.env.USDT_ADDR || BSC_USDT); - const twoHop = debt.address.toLowerCase() !== nativeOut.toLowerCase(); + const usdtOut = ethers.utils.getAddress(process.env.USDT_ADDR || BSC_USDT); + const twoHop = debt.address.toLowerCase() !== usdtOut.toLowerCase(); let router: string; let swapCalldata: string; @@ -385,7 +393,7 @@ export async function atomicLiquidate(signer: Signer) { const [r2, data2] = process.env.MOCK_AMM.split(":"); router2 = ethers.utils.getAddress(r2); swapCalldata2 = data2; - intermediateToken = nativeOut; + intermediateToken = usdtOut; } } else { // Hop 1: bStock -> USDT (bStock pairs only with USDT on BSC). Sources come from the registry @@ -396,7 +404,7 @@ export async function atomicLiquidate(signer: Signer) { const hop1 = await pickHop1Source({ taker: liquidator.address, tokenIn: bStock.address, - usdtOut: nativeOut, + usdtOut, humanAmount: seizedHumanQuote, weiAmount: seizedForQuote, slippage, @@ -429,7 +437,7 @@ export async function atomicLiquidate(signer: Signer) { ? await getPsmSwap({ amountIn: midFloor, recipient: liquidator.address }, ethers.provider) : await getAmmSwap( { - tokenIn: nativeOut, + tokenIn: usdtOut, tokenOut: debt.address, amountIn: midFloor.toString(), recipient: liquidator.address, @@ -439,7 +447,7 @@ export async function atomicLiquidate(signer: Signer) { ); router2 = ethers.utils.getAddress(amm.router); swapCalldata2 = amm.calldata; - intermediateToken = nativeOut; + intermediateToken = usdtOut; amountOut = BigNumber.from(amm.expectedOut); minOutBasis = amountOut; // hop-2 expectedOut is already computed off the hop-1 floor console.log( @@ -496,7 +504,7 @@ export async function atomicLiquidate(signer: Signer) { } } - // Re-verify the Native quote's remaining TTL immediately before submission. Everything since the + // Re-verify the hop-1 quote's remaining TTL immediately before submission. Everything since the // initial TTL check — the two-hop AMM round-trip, the isRouter reads, the inventory check — consumes // real wall-clock, so the quote may have drifted close to (or past) expiry. Abort + refetch here // rather than relying solely on the on-chain DeadlineExpired backstop and wasting gas. Skipped when @@ -505,7 +513,7 @@ export async function atomicLiquidate(signer: Signer) { const remainingTtl = deadline.toNumber() - Math.floor(Date.now() / 1000); if (remainingTtl < settleTtlMarginSec) { throw new Error( - `Native quote TTL ${remainingTtl}s is below the ${settleTtlMarginSec}s safety margin before submit — refetch`, + `hop-1 quote TTL ${remainingTtl}s is below the ${settleTtlMarginSec}s safety margin before submit — refetch`, ); } } diff --git a/scripts/bstock/liquidation-flowchart.svg b/scripts/bstock/liquidation-flowchart.svg index f23486a5f..f787b4346 100644 --- a/scripts/bstock/liquidation-flowchart.svg +++ b/scripts/bstock/liquidation-flowchart.svg @@ -28,15 +28,14 @@ - + - alive · auto - - - down → - try LM - - + live · auto + + + all sources dead + + @@ -69,13 +68,13 @@ - 1 · native-smoke.ts - read-only, ~10s — is the Native quote alive? + 1 · native-smoke.ts · lm-smoke.ts + read-only, ~10s — which RFQ sources are live? - Native quote alive? - Native only · LM tried in atomic + Any RFQ source live? + native-smoke (Native) · lm-smoke (LM) diff --git a/scripts/bstock/lm-smoke.ts b/scripts/bstock/lm-smoke.ts new file mode 100644 index 000000000..1928ec710 --- /dev/null +++ b/scripts/bstock/lm-smoke.ts @@ -0,0 +1,81 @@ +/** + * Smoke test for the Liquid Mesh integration. + * + * The Liquid Mesh counterpart of native-smoke.ts: fetches a live `/quote` (bStock -> USDT) so we can + * eyeball price, output, price impact and the route split before an incident. READ-ONLY — it calls + * `/quote` only, never `/swap`. (An executable `/swap` blob and its on-chain fill are covered separately + * by verify-lm-fork.ts.) + * + * Unlike Native, Liquid Mesh exposes no orderbook endpoint, so the token is given by ADDRESS. The only + * chain read is a best-effort `symbol()` for a friendly label (falls back to the address if no RPC is + * reachable); the quote path itself is fully off-chain. bStock tokens are 18-decimals on BSC, so `AMOUNT` + * is treated as 1e18 units — no `decimals()` call. + * + * There is also no quote-level TTL: a Liquid Mesh `/quote` is indicative and only the built `/swap` order + * carries an `expiryTimestamp`. This probe just confirms the quote path is alive and priced. + * + * Usage: + * LM_API_KEY=... LM_PRIVATE_KEY_SEED=... npx hardhat run scripts/bstock/lm-smoke.ts + * + * Env: + * LM_API_KEY (required) Liquid Mesh API key + * LM_PRIVATE_KEY_SEED (required) Ed25519 private-key seed, base64url (decodes to 32 bytes) + * BSTOCK bStock token address to sell (default TSLAB 0x5b19…292f) + * AMOUNT amount of bStock to sell, human units (default 1) + * FROM taker address used for pricing (default 0x..dEaD) + * RPC_URL optional BSC RPC override for the symbol() label; otherwise reuses + * ARCHIVE_NODE_bscmainnet, then falls back to a public dataseed + */ +import { Contract, providers, utils } from "ethers"; + +import { LM_BSC_USDT, getQuote } from "./lib/liquidmesh"; + +const DEFAULT_BSTOCK = "0x5b1910eAaD6450E50f816082Aa078C41F10C292f"; // TSLAB +const DEFAULT_RPC = "https://bsc-dataseed.bnbchain.org"; + +// Best-effort token symbol for a readable label; fall back to the address if no RPC or no symbol(). +// Reuse the archive node the rest of the suite already uses; RPC_URL overrides, public dataseed is last. +async function symbolOf(addr: string): Promise { + try { + const rpc = process.env.RPC_URL || process.env.ARCHIVE_NODE_bscmainnet || DEFAULT_RPC; + const provider = new providers.JsonRpcProvider(rpc); + return await new Contract(addr, ["function symbol() view returns (string)"], provider).symbol(); + } catch { + return addr; + } +} + +async function main() { + const tokenIn = utils.getAddress(process.env.BSTOCK || DEFAULT_BSTOCK); + const amount = process.env.AMOUNT || "1"; + const from = process.env.FROM || "0x000000000000000000000000000000000000dEaD"; + const amountWei = utils.parseUnits(amount, 18).toString(); // bStock is 18-dec on BSC + const sym = await symbolOf(tokenIn); + + console.log(`\nLiquid Mesh quote: ${amount} ${sym} (${tokenIn}) -> USDT`); + const q = await getQuote({ userAddress: from, tokenIn, tokenOut: LM_BSC_USDT, amountWei }); + + const amtIn = Number(q.inputAmount) / 1e18; + const amtOut = Number(q.outputAmount) / 1e18; // USDT is 18 decimals on BSC + console.log(` amountIn : ${amtIn} ${sym}`); + console.log(` amountOut: ${amtOut} USDT`); + console.log(` px/token : ${amtIn ? (amtOut / amtIn).toFixed(4) : "?"} USDT`); + // indicative: an LM /quote is a routed estimate — it carries price-impact / mid-price / route split and + // an est. gas, but NO deadline (only the built /swap order gets one), unlike a firm Native RFQ. + console.log(` -- indicative --`); + if (q.priceImpactPct !== undefined) console.log(` priceImp : ${q.priceImpactPct}%`); + if (q.midPrice !== undefined) console.log(` midPrice : ${q.midPrice}`); + if (q.estimatedGas !== undefined) console.log(` est. gas : ${q.estimatedGas}`); + // Route split — which makers/dexes filled and at what weight (bps out of 10000). + const dexes = q.routePlans?.[0]?.subRouters?.[0]?.dexes ?? []; + const route = dexes.map(d => `${d.dex}:${(d.weight / 100).toFixed(0)}%`).join(" "); + console.log(` route : ${route || "(none reported)"}`); + console.log(` deadline : none at quote time (set only on the built /swap order)`); +} + +main() + .then(() => process.exit(0)) + .catch(e => { + console.error(e); + process.exit(1); + }); diff --git a/scripts/bstock/native-smoke.ts b/scripts/bstock/native-smoke.ts index 0523d1720..7ca53635a 100644 --- a/scripts/bstock/native-smoke.ts +++ b/scripts/bstock/native-smoke.ts @@ -1,8 +1,11 @@ /** * Smoke test for the Native Swap API integration (no chain interaction). * - * Prints the BSC orderbook entries for our bStock tokens and fetches a live - * firm-quote so we can eyeball price, spread, TTL and the returned txRequest. + * The Native counterpart of lm-smoke.ts: fetches a live firm-quote (bStock -> USDT) so we can eyeball + * price, TTL and the executable router before an incident. Prints the shared shape (amountIn/amountOut, + * px/token) plus a `firm-only` block (deadline/TTL, txRequest router, order count) that a firm MM-signed + * RFQ carries but an indicative Liquid Mesh quote does not. The token address is resolved from the live + * Native orderbook by symbol (`TOKEN`); the orderbook itself is not printed. * * Usage: * NATIVE_API_KEY=... npx hardhat run scripts/bstock/native-smoke.ts @@ -23,27 +26,23 @@ async function main() { const amount = process.env.AMOUNT || "1"; const from = process.env.FROM || "0x000000000000000000000000000000000000dEaD"; - // Resolve the token address from the live orderbook (the source of truth) rather than a hardcoded - // map, so this stays correct as Native lists more bStock tokens. `tokenIn` is the sell-side base - // (base = symbol, quote = USDT). - console.log(`\nOrderbook (bsc) entries for ${symbol}:`); + // Resolve the token address from the live orderbook (the source of truth) rather than a hardcoded map, + // so this stays correct as Native lists more bStock tokens. tokenIn is the sell-side base (quote = USDT). const ob = await getOrderbook("bsc"); - let tokenIn: string | undefined; - for (const r of ob) { - if (`${r.base_symbol}${r.quote_symbol}`.includes(symbol)) { - console.log(` ${r.base_symbol} <-> ${r.quote_symbol}`); - } - if (r.base_symbol === symbol && r.quote_symbol === "USDT") tokenIn = r.base_address; - } + const tokenIn = ob.find(r => r.base_symbol === symbol && r.quote_symbol === "USDT")?.base_address; if (!tokenIn) throw new Error(`No ${symbol}<->USDT pair in the Native bsc orderbook`); - console.log(`\nFirm quote: ${amount} ${symbol} -> USDT`); + // Shared shape with lm-smoke.ts (amountIn / amountOut / px/token), then the Native-firm-only extras. + console.log(`\nNative quote: ${amount} ${symbol} (${tokenIn}) -> USDT`); const q = await getFirmQuote({ fromAddress: from, tokenIn, tokenOut: BSC_USDT, amount, slippage: 0.5 }); const amtIn = Number(q.amountIn) / 1e18; const amtOut = Number(q.amountOut) / 1e18; // USDT is 18 decimals on BSC console.log(` amountIn : ${amtIn} ${symbol}`); console.log(` amountOut: ${amtOut} USDT`); - console.log(` px/token : ${(amtOut / amtIn).toFixed(4)} USDT`); + console.log(` px/token : ${amtIn ? (amtOut / amtIn).toFixed(4) : "?"} USDT`); + // firm-only: a Native RFQ is an MM-signed, single-fill order — so it carries an executable txRequest, + // a signed deadline, and the order objects (no route split / price-impact, unlike an indicative LM quote). + console.log(` -- firm-only --`); const dl = quoteDeadline(q); console.log(` deadline : ${dl} (${dl ? Math.max(0, dl - Math.floor(Date.now() / 1000)) : "?"}s TTL)`); console.log(` router : ${q.txRequest?.target}`); From 1507c56d0cad42338623cbc88fd75ceac146da3e Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 17 Jul 2026 18:57:56 +0530 Subject: [PATCH 71/78] fix: [L07] --- contracts/test/BStockLiquidationMocks.sol | 40 ++++++++++ scripts/bstock/atomic-liquidate.ts | 13 ++++ scripts/bstock/lib/vai-gate.ts | 89 ++++++++++++++++++++++ scripts/bstock/safe-fallback.ts | 13 ++++ tests/hardhat/BStockAtomicLiquidate.ts | 58 +++++++++++++- tests/hardhat/BStockSafeFallback.ts | 50 ++++++++++++ tests/hardhat/Fork/BStockLiquidatorFork.ts | 75 ++++++++++++++++++ 7 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 scripts/bstock/lib/vai-gate.ts diff --git a/contracts/test/BStockLiquidationMocks.sol b/contracts/test/BStockLiquidationMocks.sol index b355bb1b4..a7b596d86 100644 --- a/contracts/test/BStockLiquidationMocks.sol +++ b/contracts/test/BStockLiquidationMocks.sol @@ -40,14 +40,24 @@ interface IFlashReceiverLike { /// is resolved through `getVAIAddress()`, exactly as the real VAIController exposes it. contract MockVAIController { address public immutable vai; + mapping(address => uint256) public vaiRepayAmount; // borrower -> outstanding VAI debt constructor(address vai_) { vai = vai_; } + function setVAIRepayAmount(address borrower, uint256 amount) external { + vaiRepayAmount[borrower] = amount; + } + function getVAIAddress() external view returns (address) { return vai; } + + /// @dev Read by the gate's `_checkForceVAILiquidate` and by the scripts' VAI-gate pre-flight. + function getVAIRepayAmount(address borrower) external view returns (uint256) { + return vaiRepayAmount[borrower]; + } } /// @dev Minimal Comptroller surface the script + BStockLiquidator read, plus a flash lender. @@ -65,10 +75,27 @@ contract MockComptrollerLite { uint256 public treasuryPercent; // redeem fee, 1e18-scaled (default 0) address public vaiController; // a debt equal to this is VAI (no underlying(); see MockVAIController) + /// @dev Two of the gate's VAI-guard escape hatches (see Liquidator._checkForceVAILiquidate): either + /// one permits liquidating a non-VAI market regardless of the borrower's VAI debt. + mapping(address => bool) public isForcedLiquidationEnabled; // vToken -> forced liquidation on + mapping(address => mapping(uint8 => bool)) private _actionPaused; // market -> Action -> paused + function setVaiController(address v) external { vaiController = v; } + function setForcedLiquidation(address vToken, bool enabled) external { + isForcedLiquidationEnabled[vToken] = enabled; + } + + function setActionPaused(address market, uint8 action, bool paused) external { + _actionPaused[market][action] = paused; + } + + function actionPaused(address market, uint8 action) external view returns (bool) { + return _actionPaused[market][action]; + } + function setShortfall(uint256 s) external { shortfall = s; } @@ -407,6 +434,19 @@ contract MockVenusLiquidator { address public vBnb; // native BNB market; a repay for this vToken must arrive as msg.value address public vaiController; // VAI "market"; the repay is the VAI ERC20, resolved via getVAIAddress() + /// @dev The gate's VAI guard, mirrored for the scripts' pre-flight. `forceVAILiquidate` is FALSE on + /// BSC mainnet today, which short-circuits the guard — hence the default here. + bool public forceVAILiquidate; + uint256 public minLiquidatableVAI = 1000e18; + + function setForceVAILiquidate(bool v) external { + forceVAILiquidate = v; + } + + function setMinLiquidatableVAI(uint256 v) external { + minLiquidatableVAI = v; + } + function setTreasuryCut(uint256 m) external { treasuryCutMantissa = m; } diff --git a/scripts/bstock/atomic-liquidate.ts b/scripts/bstock/atomic-liquidate.ts index 0d59b7e70..c087a16f3 100644 --- a/scripts/bstock/atomic-liquidate.ts +++ b/scripts/bstock/atomic-liquidate.ts @@ -79,6 +79,7 @@ import { BSC_WBNB, getAmmSwap } from "./lib/amm"; import { BSC_USDT } from "./lib/native"; import { getPsmSwap } from "./lib/psm"; import { QuoteArgs, selectedSources } from "./lib/sources"; +import { assertVaiGateClear } from "./lib/vai-gate"; const PARAMS_TUPLE = "(address borrower,address vDebt,address vBStock,uint256 repayAmount,address router,bytes swapCalldata,uint256 minOut,address router2,bytes swapCalldata2,address intermediateToken,uint256 deadline)"; @@ -310,6 +311,18 @@ export async function atomicLiquidate(signer: Signer) { ); } + // The gate refuses to liquidate an unrelated market while the borrower's VAI debt is above the + // threshold (Liquidator._checkForceVAILiquidate). Surface that here — naming the VAI-first remedy — + // rather than burning a settle tx on an on-chain VAIDebtTooHigh revert. + await assertVaiGateClear({ + provider: ethers.provider, + gate, + comptroller: comptroller.address, + vaiController: vaiControllerAddr, + vDebt: vDebt.address, + borrower, + }); + // The gate keeps a treasury cut of the liquidation BONUS (see Liquidator._splitLiquidationIncentive), // so this contract receives fewer vTokens than `seizeTokens`. Deduct that cut, else the precomputed // amount overstates our holdings and the fixed-amountIn router pull reverts. On BSC mainnet today this diff --git a/scripts/bstock/lib/vai-gate.ts b/scripts/bstock/lib/vai-gate.ts new file mode 100644 index 000000000..2fc4f3f80 --- /dev/null +++ b/scripts/bstock/lib/vai-gate.ts @@ -0,0 +1,89 @@ +import { BigNumber, Contract, providers } from "ethers"; + +/** + * Pre-flight for the Venus Liquidator's VAI gate. + * + * `Liquidator._checkForceVAILiquidate` runs on EVERY `liquidateBorrow` and reverts `VAIDebtTooHigh` + * when a borrower's VAI debt is at/above `minLiquidatableVAI` — blocking the liquidation of an + * UNRELATED debt market until the VAI is cleared below the threshold first. Nothing in + * BStockLiquidator can see that ahead of time, so a blocked liquidation is only discovered as an + * on-chain revert (gas burned, retry needed mid-incident). + * + * This mirrors that check off-chain so the operator is told to clear VAI FIRST before a tx is sent. + * + * CRITICAL — the on-chain guard is a five-term OR that returns EARLY (i.e. permits the liquidation) + * if ANY term is true. It only reverts when ALL FIVE are false. Checking a subset would produce FALSE + * POSITIVES: refusing to build a liquidation that would in fact have succeeded, which mid-incident is + * worse than the wasted tx this guards against. Keep this in lockstep with Liquidator.sol: + * + * if (_isForcedLiquidationEnabled || // forced liquidation enabled on the debt market + * _isVAILiquidationPaused || // VAI liquidation action paused + * !forceVAILiquidate || // the global switch is off (the mainnet default today) + * _vaiDebt < minLiquidatableVAI || // borrower is under the threshold + * vToken_ == address(vaiController) // liquidating VAI ITSELF is never blocked + * ) return; + * revert VAIDebtTooHigh(_vaiDebt, minLiquidatableVAI); + */ + +const GATE_ABI = [ + "function forceVAILiquidate() view returns (bool)", + "function minLiquidatableVAI() view returns (uint256)", +]; +const COMPTROLLER_ABI = [ + "function isForcedLiquidationEnabled(address) view returns (bool)", + "function actionPaused(address,uint8) view returns (bool)", +]; +const VAI_CONTROLLER_ABI = ["function getVAIRepayAmount(address) view returns (uint256)"]; + +/// IComptroller.Action.LIQUIDATE — MINT, REDEEM, BORROW, REPAY, SEIZE, LIQUIDATE, ... +const ACTION_LIQUIDATE = 5; + +export interface VaiGateArgs { + provider: providers.Provider; + gate: string; // the Venus Liquidator (comptroller.liquidatorContract()) + comptroller: string; + vaiController: string; + vDebt: string; // the market being repaid + borrower: string; +} + +/** + * Throws when the VAI gate would reject this liquidation, naming the remedy. No-op otherwise. + * Terms are evaluated in cheapest-first order and short-circuit, so the common path (the global + * switch off) costs a single call. + */ +export async function assertVaiGateClear(a: VaiGateArgs): Promise { + // Term 5: liquidating VAI itself is unconditionally permitted — this is the very step the operator + // is told to run first, so it must never be blocked by our own pre-flight. + if (a.vDebt.toLowerCase() === a.vaiController.toLowerCase()) return; + + const gate = new Contract(a.gate, GATE_ABI, a.provider); + + // Term 3: the global switch. False on BSC mainnet today, so this returns for every real call and + // the gate cannot revert — check it first to keep the common path to one RPC round-trip. + if (!(await gate.forceVAILiquidate())) return; + + const comptroller = new Contract(a.comptroller, COMPTROLLER_ABI, a.provider); + + // Term 1: forced liquidation on the debt market bypasses the VAI gate entirely. + if (await comptroller.isForcedLiquidationEnabled(a.vDebt)) return; + + // Term 2: if VAI liquidation is paused, VAI cannot be cleared — so the gate lets everything else through. + if (await comptroller.actionPaused(a.vaiController, ACTION_LIQUIDATE)) return; + + // Term 4: the borrower's own VAI exposure against the threshold. + const vaiCtrl = new Contract(a.vaiController, VAI_CONTROLLER_ABI, a.provider); + const [vaiDebt, minLiquidatableVAI]: BigNumber[] = await Promise.all([ + vaiCtrl.getVAIRepayAmount(a.borrower), + gate.minLiquidatableVAI(), + ]); + if (vaiDebt.lt(minLiquidatableVAI)) return; + + // All five false — the gate WILL revert VAIDebtTooHigh. Fail here instead, with the fix. + throw new Error( + `Venus Liquidator VAI gate blocks this liquidation: borrower ${a.borrower} holds ` + + `${vaiDebt.toString()} VAI debt >= minLiquidatableVAI ${minLiquidatableVAI.toString()} while ` + + `forceVAILiquidate is enabled. Liquidate the VAI debt FIRST (VDEBT=${a.vaiController}), enough to ` + + `drop it below the threshold, then re-run this liquidation.`, + ); +} diff --git a/scripts/bstock/safe-fallback.ts b/scripts/bstock/safe-fallback.ts index 0631cd068..67b269eab 100644 --- a/scripts/bstock/safe-fallback.ts +++ b/scripts/bstock/safe-fallback.ts @@ -61,6 +61,7 @@ import { promises as fs } from "fs"; import * as path from "path"; import { buildBatch, call } from "./lib/safe"; +import { assertVaiGateClear } from "./lib/vai-gate"; const DEFAULT_SAFE = "0xdc6E047f665c3Db94292Bb7fB412B25370db2029"; const DEFAULT_RPC = "https://bsc-dataseed.bnbchain.org"; @@ -158,6 +159,18 @@ export async function buildSafeFallbackBatch(provider: providers.Provider) { const repaySpender = utils.getAddress(gate); console.log(`routing repay through Venus Liquidator ${gate}`); + // The gate refuses to liquidate an unrelated market while the borrower's VAI debt is above the + // threshold (Liquidator._checkForceVAILiquidate). Catch it while BUILDING the batch — a Safe batch + // that reverts on execution costs signer time and a fresh signing round, not just gas. + await assertVaiGateClear({ + provider, + gate, + comptroller: comptroller.address, + vaiController: vaiControllerAddr, + vDebt: vDebt.address, + borrower, + }); + const safeDebtBal: BigNumber = isBnb ? await provider.getBalance(safe) : await debt!.balanceOf(safe); if (safeDebtBal.lt(repay)) { console.warn( diff --git a/tests/hardhat/BStockAtomicLiquidate.ts b/tests/hardhat/BStockAtomicLiquidate.ts index 5a0e5f449..4cfacfafb 100644 --- a/tests/hardhat/BStockAtomicLiquidate.ts +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -51,6 +51,7 @@ describe("bStock atomic liquidation script", () => { let usdt: Contract, bStock: Contract; let comptroller: Contract, vBStock: Contract, vDebt: Contract, router: Contract, liq: Contract; let wbnb: Contract, vWBNB: Contract; + let venusLiq: Contract, vai: Contract, vaiController: Contract; async function deployLiquidator(comptrollerAddr: string) { const Factory = await ethers.getContractFactory("BStockLiquidator"); @@ -103,10 +104,16 @@ describe("bStock atomic liquidation script", () => { vWBNB = await (await ethers.getContractFactory("MockVTokenDebt")).deploy(wbnb.address, comptroller.address); // Core's pool-wide liquidator gate is always configured; every liquidation routes through it. - const venusLiq = await (await ethers.getContractFactory("MockVenusLiquidator")).deploy(); + venusLiq = await (await ethers.getContractFactory("MockVenusLiquidator")).deploy(); await comptroller.setLiquidatorContract(venusLiq.address); await venusLiq.setVBnb(VBNB); + // VAI wiring: needed by the script's VAI detection and by the gate's VAI-guard pre-flight. + vai = await (await ethers.getContractFactory("MockMintableERC20")).deploy("Venus VAI", "VAI", 18); + vaiController = await (await ethers.getContractFactory("MockVAIController")).deploy(vai.address); + await comptroller.setVaiController(vaiController.address); + await venusLiq.setVaiController(vaiController.address); + liq = await deployLiquidator(comptroller.address); await liq.connect(owner).setRouter(router.address, true); @@ -260,6 +267,55 @@ describe("bStock atomic liquidation script", () => { expect(await usdt.balanceOf(liq.address)).to.equal(REPAY); // untouched }); + // The gate blocks liquidating an UNRELATED market while the borrower's VAI debt is above the + // threshold (Liquidator._checkForceVAILiquidate). It is a five-term OR that permits the liquidation + // if ANY term holds, so the pre-flight must mirror all five: warning on a subset would refuse + // liquidations that would actually succeed. + describe("VAI gate pre-flight (non-VAI debt)", () => { + beforeEach(async () => { + await usdt.mint(liq.address, REPAY); + await vaiController.setVAIRepayAmount(borrower.address, U("5000")); // >= the 1000 default threshold + }); + + it("refuses a USDT liquidation and names the VAI-first remedy when the gate would block it", async () => { + await venusLiq.setForceVAILiquidate(true); // all five terms now false -> the gate WOULD revert + setEnv(); + + await expect(atomicLiquidate(owner)).to.be.rejectedWith(/liquidate the VAI debt first/i); + expect(await usdt.balanceOf(liq.address)).to.equal(REPAY); // nothing sent + }); + + it("does not fire while forceVAILiquidate is off (the mainnet default)", async () => { + setEnv(); // forceVAILiquidate defaults to false + await atomicLiquidate(owner); + expect(await usdt.balanceOf(liq.address)).to.equal(OUT); // settled normally + }); + + it("does not fire when forced liquidation is enabled on the debt market (escape hatch)", async () => { + await venusLiq.setForceVAILiquidate(true); + await comptroller.setForcedLiquidation(vDebt.address, true); // gate lets it through + setEnv(); + await atomicLiquidate(owner); + expect(await usdt.balanceOf(liq.address)).to.equal(OUT); + }); + + it("does not fire when VAI liquidation is paused (escape hatch)", async () => { + await venusLiq.setForceVAILiquidate(true); + await comptroller.setActionPaused(vaiController.address, 5, true); // Action.LIQUIDATE + setEnv(); + await atomicLiquidate(owner); + expect(await usdt.balanceOf(liq.address)).to.equal(OUT); + }); + + it("does not fire when the borrower's VAI debt is below the threshold", async () => { + await venusLiq.setForceVAILiquidate(true); + await vaiController.setVAIRepayAmount(borrower.address, U("999")); // < 1000 + setEnv(); + await atomicLiquidate(owner); + expect(await usdt.balanceOf(liq.address)).to.equal(OUT); + }); + }); + it("refuses a router that is not allowlisted on the contract", async () => { await usdt.mint(liq.address, REPAY); // Deploy a second router that was never allowlisted, and point the (mock) quote at it. diff --git a/tests/hardhat/BStockSafeFallback.ts b/tests/hardhat/BStockSafeFallback.ts index bf988412f..184dbf2f2 100644 --- a/tests/hardhat/BStockSafeFallback.ts +++ b/tests/hardhat/BStockSafeFallback.ts @@ -133,6 +133,56 @@ describe("BStock safe-fallback batch generator", () => { expect(vReceived).to.equal(SEIZE); // seize via the VAI 2-arg math, cut 0 }); + // The gate blocks liquidating an UNRELATED market while the borrower's VAI debt is above the + // threshold. Catching it at BUILD time matters more here than in the atomic script: a batch that + // reverts on execution costs a signing round, not just gas. + describe("VAI gate pre-flight (non-VAI debt)", () => { + let vaiController: Contract; + + beforeEach(async () => { + const vai = await (await ethers.getContractFactory("MockMintableERC20")).deploy("Venus VAI", "VAI", 18); + vaiController = await (await ethers.getContractFactory("MockVAIController")).deploy(vai.address); + await comptroller.setVaiController(vaiController.address); + await venusLiq.setVaiController(vaiController.address); + await vaiController.setVAIRepayAmount(borrower.address, U("5000")); // >= the 1000 default threshold + }); + + it("refuses to build the batch and names the VAI-first remedy when the gate would block it", async () => { + await venusLiq.setForceVAILiquidate(true); // all five terms false -> the gate WOULD revert + setEnv(); + await expect(buildSafeFallbackBatch(ethers.provider)).to.be.rejectedWith(/liquidate the VAI debt first/i); + }); + + it("does not fire while forceVAILiquidate is off (the mainnet default)", async () => { + setEnv(); + const { txs } = await buildSafeFallbackBatch(ethers.provider); + expect(txs).to.have.length(4); // built normally + }); + + it("does not fire when forced liquidation is enabled on the debt market (escape hatch)", async () => { + await venusLiq.setForceVAILiquidate(true); + await comptroller.setForcedLiquidation(vDebt.address, true); + setEnv(); + const { txs } = await buildSafeFallbackBatch(ethers.provider); + expect(txs).to.have.length(4); + }); + + it("does not fire when VAI liquidation is paused (escape hatch)", async () => { + await venusLiq.setForceVAILiquidate(true); + await comptroller.setActionPaused(vaiController.address, 5, true); // Action.LIQUIDATE + setEnv(); + const { txs } = await buildSafeFallbackBatch(ethers.provider); + expect(txs).to.have.length(4); + }); + + it("never blocks liquidating the VAI debt itself (the remedy step)", async () => { + await venusLiq.setForceVAILiquidate(true); + setEnv({ VDEBT: vaiController.address }); + const { txs } = await buildSafeFallbackBatch(ethers.provider); + expect(txs).to.have.length(4); // the VAI-first step must never be self-blocked + }); + }); + it("throws when the gate is unset, aligning with the on-chain liquidator", async () => { await comptroller.setLiquidatorContract(ethers.constants.AddressZero); setEnv(); diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts index df3b65bc0..048d1660a 100644 --- a/tests/hardhat/Fork/BStockLiquidatorFork.ts +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -34,6 +34,7 @@ import { ethers, upgrades } from "hardhat"; import { atomicLiquidate } from "../../../scripts/bstock/atomic-liquidate"; import { getPsmSwap } from "../../../scripts/bstock/lib/psm"; +import { assertVaiGateClear } from "../../../scripts/bstock/lib/vai-gate"; import { A, Action, @@ -44,6 +45,7 @@ import { TOK, VTOKEN_ABI, ZERO, + asTimelockWith, assertRolledBack, assertSettledFor, buildSingleHopMock, @@ -111,6 +113,7 @@ const B = { BNB: ethers.utils.getAddress("0x00000000000000000000000000000000ca110003"), VAI: ethers.utils.getAddress("0x00000000000000000000000000000000ca11000a"), VAI_SCRIPT: ethers.utils.getAddress("0x00000000000000000000000000000000ca11000b"), + VAI_GATE: ethers.utils.getAddress("0x00000000000000000000000000000000ca11000e"), EMODE: ethers.utils.getAddress("0x00000000000000000000000000000000ca11000d"), FLASH: ethers.utils.getAddress("0x00000000000000000000000000000000ca110004"), FORCED: ethers.utils.getAddress("0x00000000000000000000000000000000ca110005"), @@ -619,6 +622,78 @@ const test = () => { ); }); + // The scripts' VAI-gate pre-flight (lib/vai-gate.ts) is a hand-copied mirror of the Liquidator's + // PRIVATE _checkForceVAILiquidate. Unit tests can only assert it against our own mock — written from + // the same reading — so a misreading would leave both wrong in the same direction and still green + // (exactly how L04's overload bug survived). This pins the mirror to the REAL gate instead: for each + // state, the pre-flight must refuse IFF the live Liquidator would revert VAIDebtTooHigh. + it("VAI gate: the script pre-flight mirrors the REAL Liquidator guard in both directions", async () => { + await makeUnderwaterVaiBorrower(B.VAI_GATE, parseUnits("5000", 18)); // VAI debt >> minLiquidatableVAI + + const gateAddr: string = await new ethers.Contract( + A.COMPTROLLER, + ["function liquidatorContract() view returns (address)"], + owner, + ).liquidatorContract(); + const gate = new ethers.Contract( + gateAddr, + [ + "function liquidateBorrow(address,address,uint256,address) payable", + "function resumeForceVAILiquidate()", + "function forceVAILiquidate() view returns (bool)", + "error VAIDebtTooHigh(uint256 vaiDebt, uint256 minLiquidatableVAI)", + ], + owner, + ); + const args = { + provider: ethers.provider, + gate: gateAddr, + comptroller: A.COMPTROLLER, + vaiController: VAI_CONTROLLER, + vDebt: VUSDT, // an UNRELATED market — the one the VAI debt would block + borrower: B.VAI_GATE, + }; + // _checkForceVAILiquidate runs before any repay logic, so callStatic surfaces the guard itself + // without the borrower needing a live USDT borrow. + const probeGate = () => + gate.callStatic.liquidateBorrow(VUSDT, B.VAI_GATE, parseUnits("1", 18), mkt.vBStock.address); + + // BASELINE — mainnet has the switch OFF, so the guard cannot fire and the pre-flight permits. + expect(await gate.forceVAILiquidate()).to.equal(false); + await assertVaiGateClear(args); // does not throw + + // Flip the REAL switch through the REAL ACM + timelock (a governance-reachable state). + const tl = await asTimelockWith([[gateAddr, "resumeForceVAILiquidate()"]]); + await gate.connect(tl).resumeForceVAILiquidate(); + expect(await gate.forceVAILiquidate()).to.equal(true); + + // 1. The live gate now rejects the unrelated liquidation... + await expect(probeGate()).to.be.revertedWithCustomError(gate, "VAIDebtTooHigh"); + // ...and the pre-flight refuses the same state, naming the remedy. + await expect(assertVaiGateClear(args)).to.be.rejectedWith(/liquidate the VAI debt first/i); + + // 2. Liquidating VAI ITSELF is never blocked — the remedy step must stay reachable. + await assertVaiGateClear({ ...args, vDebt: VAI_CONTROLLER }); + + // 3. ESCAPE HATCH (forced liquidation on the debt market). This is the term a subset-check would + // miss: the live gate stops rejecting on VAI grounds, so the pre-flight MUST stop warning — + // otherwise it blocks a liquidation that would have succeeded. + const cTl = await asTimelockWith([[A.COMPTROLLER, "_setForcedLiquidation(address,bool)"]]); + await new ethers.Contract( + A.COMPTROLLER, + ["function _setForcedLiquidation(address,bool)"], + cTl, + )._setForcedLiquidation(VUSDT, true); + + // The live gate no longer fails on VAI grounds (it still fails later — no borrow to repay). + const err = await probeGate().then( + () => null, + (e: Error) => e, + ); + expect(err?.message ?? "").to.not.match(/VAIDebtTooHigh/); + await assertVaiGateClear(args); // pre-flight agrees: no warning + }); + it("effective-incentive divergence (ERC20 debt, non-core pool): script sizes the cut off effective and settles", async () => { // The divergence the VAI unit test can only MODEL is REAL here. A non-core e-mode pool lists // vBStock at 1.25x while core stays 1.1x, and the USDT borrower sits in that pool — so on the live From d7b3adbfcfe20be9d24cf215383bf1b59bb21fec Mon Sep 17 00:00:00 2001 From: Fred Date: Mon, 20 Jul 2026 12:19:49 +0800 Subject: [PATCH 72/78] =?UTF-8?q?test(bstock):=20fork-prove=20the=20full?= =?UTF-8?q?=20mode=20=C3=97=20hop-1=20source=20=C3=97=20debt=20scenario=20?= =?UTF-8?q?matrix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes BStockLiquidatorFork.ts coverage across {inventory, flash} × {native-style router-pulls, LM-style split-spender} × {USDT, CAKE (real PCS), BNB (WBNB unwrap), VAI (real PSM)}: - adds the 8 previously untested positive cells — inv×LM×{CAKE,BNB,VAI}, flash×native×{CAKE,BNB}, flash×LM×{USDT,CAKE,BNB} — plus an on-fork flash×VAI FlashNotSupportedForVai rejection check - flash×two-hop and flash×split-spender existed nowhere: these are the cells where the flash principal+premium repay must come out of the swap chain - hop-2 legs sized exactly like atomic-liquidate.ts (4-arg seize, gate cut off the effective incentive, redeem treasuryPercent), so flash settles at the real ~4% incentive margin instead of the main helper's blanket -10% - pins WBNB as a ChainlinkOracle direct price: fork clock drift stales the live 3-oracle BNB config and reverts any vBNB borrow/liquidity check (the main suite's BNB cell hits this at its default block) 10 passing at FORK_BSTOCK_BLOCK=110490000. Co-Authored-By: Claude Fable 5 --- tests/hardhat/Fork/BStockMatrixFork.ts | 604 +++++++++++++++++++++++++ 1 file changed, 604 insertions(+) create mode 100644 tests/hardhat/Fork/BStockMatrixFork.ts diff --git a/tests/hardhat/Fork/BStockMatrixFork.ts b/tests/hardhat/Fork/BStockMatrixFork.ts new file mode 100644 index 000000000..e172f29c3 --- /dev/null +++ b/tests/hardhat/Fork/BStockMatrixFork.ts @@ -0,0 +1,604 @@ +// ============================================================================================ +// BStockLiquidator — scenario MATRIX fork suite +// ============================================================================================ +// +// Completes BStockLiquidatorFork.ts across the full scenario matrix: +// +// mode {inventory, flash} × hop-1 {native-style (router pulls), LM-style (split spender pulls)} +// × debt {USDT single-hop, CAKE two-hop real-PCS, BNB (WBNB unwrap), VAI (real PSM)} +// +// The main suite covers inv×native×{USDT,CAKE,BNB,VAI}, inv×LM×USDT and flash×native×USDT; this +// file adds the remaining positive cells — inv×LM×{CAKE,BNB,VAI}, flash×native×{CAKE,BNB}, +// flash×LM×{USDT,CAKE,BNB} — plus an on-fork flash×VAI rejection check. The flash×two-hop and +// flash×split-spender combinations exist nowhere else: they are the ones where the flash +// principal+premium repay must be covered by the swap-chain proceeds, so they get their own cells. +// +// Mechanics mirror the main suite: real bStock market listed on the live diamond, real underwater +// positions (supply -> borrow -> oracle price gap), real gate / PancakeSwap / PSM; only the +// off-chain RFQ fill is mocked (MockNativeRouter = target-pulls, MockSplitRouter + MockSpender = +// split-spender pulls, i.e. the Liquid Mesh settlement shape). +// +// Hop-2 sizing mirrors atomic-liquidate.ts EXACTLY (borrower-aware 4-arg seize, gate treasury cut +// off the effective incentive, redeem treasuryPercent) instead of the main helper's blanket -10%, +// so the flash cells' principal+premium repay constraint is exercised at the REAL incentive margin +// (~4%: 10% per-market bonus, gate keeps 50%, redeem fee and flash fee both 0 live) — a -10% +// undershoot would starve the flash repay and could never settle. +// +// Run (recent block: flash-loan support + the whitelist setter are live there): +// FORK_BSTOCK_BLOCK=110490000 FORKED_NETWORK=bscmainnet npx hardhat test \ +// tests/hardhat/Fork/BStockMatrixFork.ts +import { setBalance } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { BigNumber, Contract } from "ethers"; +import { parseEther, parseUnits } from "ethers/lib/utils"; +import { ethers, upgrades } from "hardhat"; + +import { getPsmSwap } from "../../../scripts/bstock/lib/psm"; +import { + A, + BStockMarket, + BalanceSlot, + ERC20_ABI, + ONE, + TOK, + VTOKEN_ABI, + ZERO, + asTimelockWith, + assertSettledFor, + deployFundedMockNative, + findBalanceSlot, + listBStockMarket, + makeUnderwaterBorrower, + setTokenBalance, +} from "./helpers/bstock"; +import { FORK_MAINNET, forking, initMainnetUser } from "./utils"; + +const FORK_BLOCK = Number(process.env.FORK_BSTOCK_BLOCK || "110490000"); + +const VUSDT = "0xfD5840Cd36d94D7229439859C0112a4185BC0255"; +const VCAKE = "0x86aC3974e2BD0d60825230fa6F355fF11409df5c"; +const VAI_CONTROLLER = "0x004065D34C6b18cE4370ced1CeBDE94865DbFAFE"; +const VAI = "0x4BD17003473389A42DAF6a0a729f6Fdb328BbBd7"; +const PSM_USDT = "0xC138aa4E424D1A8539e8F38Af5a754a2B7c3Cc36"; + +const ACM_GIVE_ABI = ["function giveCallPermission(address,string,address)"]; +const PCS_ABI = [ + "function swapExactTokensForTokens(uint256,uint256,address[],address,uint256) returns (uint256[])", + "function getAmountsOut(uint256,address[]) view returns (uint256[])", +]; +const COMPTROLLER_ABI = [ + "function liquidateCalculateSeizeTokens(address,address,address,uint256) view returns (uint256,uint256)", + "function liquidateVAICalculateSeizeTokens(address,uint256) view returns (uint256,uint256)", + "function getEffectiveLiquidationIncentive(address,address) view returns (uint256)", + "function treasuryPercent() view returns (uint256)", + "function liquidatorContract() view returns (address)", + "function setWhiteListFlashLoanAccount(address,bool)", +]; +const VAI_CONTROLLER_ABI = [ + "function mintVAI(uint256) returns (uint256)", + "function getVAIRepayAmount(address) view returns (uint256)", + "function toggleOnlyPrimeHolderMint() returns (uint256)", +]; +const GATE_ABI = ["function treasuryPercentMantissa() view returns (uint256)"]; +const FLASH_VTOKEN_ABI = [ + "function isFlashLoanEnabled() view returns (bool)", + "function setFlashLoanEnabled(bool) returns (uint256)", +]; + +const P_HEALTHY = parseUnits("250", 18); +const P_CRASH = parseUnits("50", 18); + +// Codeless EOA borrowers, distinct from the original suite's 0xca11… range. +const M = { + LM_CAKE: ethers.utils.getAddress("0x00000000000000000000000000000000ca140001"), + LM_BNB: ethers.utils.getAddress("0x00000000000000000000000000000000ca140002"), + LM_VAI: ethers.utils.getAddress("0x00000000000000000000000000000000ca140003"), + FL_N_CAKE: ethers.utils.getAddress("0x00000000000000000000000000000000ca140004"), + FL_N_BNB: ethers.utils.getAddress("0x00000000000000000000000000000000ca140005"), + FL_LM_USDT: ethers.utils.getAddress("0x00000000000000000000000000000000ca140006"), + FL_LM_CAKE: ethers.utils.getAddress("0x00000000000000000000000000000000ca140007"), + FL_LM_BNB: ethers.utils.getAddress("0x00000000000000000000000000000000ca140008"), + FL_VAI: ethers.utils.getAddress("0x00000000000000000000000000000000ca140009"), +}; + +const test = () => { + describe("BStockLiquidator — scenario matrix (mode × hop-1 source × debt)", () => { + let owner: any; + let mkt: BStockMarket; + let liq: Contract; // fresh proxy on the current impl (routerSpender + VAI support) + let mock: Contract; // native-style hop-1: the router itself pulls + let lmRouter: Contract; // LM-style hop-1: pulls via a separate spender contract + let spender: Contract; + const flashReady: Record = {}; + let baseSnap: string; + + const slotCache: Record = {}; + async function fund(token: string, to: string, amount: BigNumber) { + if (slotCache[token] === undefined) { + const s = await findBalanceSlot(token); + if (s === null) throw new Error(`no balance slot for ${token}`); + slotCache[token] = s; + } + await setTokenBalance(token, to, amount, slotCache[token]); + } + + // Enable flash-loans on a market + whitelist the liquidator on the diamond, through the real + // ACM + timelock (governance-reachable state). Returns false when the infra is absent at this + // block (cells then log a skip, mirroring the original suite's documented-skip pattern). + async function enableFlash(vDebt: string): Promise { + try { + const tl = await initMainnetUser(A.TIMELOCK, parseEther("10")); + const acm = new ethers.Contract(A.ACM, ACM_GIVE_ABI, tl); + const v = new ethers.Contract(vDebt, FLASH_VTOKEN_ABI, owner); + if (!(await v.isFlashLoanEnabled())) { + await acm.giveCallPermission(vDebt, "setFlashLoanEnabled(bool)", owner.address); + await v.setFlashLoanEnabled(true); + } + await acm.giveCallPermission(A.COMPTROLLER, "setWhiteListFlashLoanAccount(address,bool)", A.TIMELOCK); + await new ethers.Contract(A.COMPTROLLER, COMPTROLLER_ABI, tl).setWhiteListFlashLoanAccount(liq.address, true); + return true; + } catch (e) { + console.log(` flash infra unavailable for ${vDebt} at this block: ${(e as Error).message.slice(0, 80)}`); + return false; + } + } + + // Hop-1 calldata: both mocks share the swapAll(tokenIn, tokenOut, to) shape; the difference under + // test is WHO pulls (router itself vs its spender — the latter requires routerSpender approval). + function hop1(src: "native" | "lm"): { router: string; calldata: string } { + const r = src === "native" ? mock : lmRouter; + return { + router: r.address, + calldata: r.interface.encodeFunctionData("swapAll", [mkt.bStock.address, TOK.USDT, liq.address]), + }; + } + + // Hop-2 (USDT -> debt) sizing, mirroring atomic-liquidate.ts exactly: borrower-aware 4-arg seize, + // gate treasury cut off the EFFECTIVE incentive, redeem treasuryPercent, then the mock's rate. + // x2 takes a 0.5% margin under the exact expected hop-1 delta so the fixed PCS/PSM pull always + // fits the on-chain approval. + async function hop1UsdtOut(borrower: string, vDebt: string, repay: BigNumber, isVai = false): Promise { + const c = new ethers.Contract(A.COMPTROLLER, COMPTROLLER_ABI, owner); + const [err, seizeTokens] = isVai + ? await c.liquidateVAICalculateSeizeTokens(mkt.vBStock.address, repay) + : await c.liquidateCalculateSeizeTokens(borrower, vDebt, mkt.vBStock.address, repay); + expect(err).to.equal(0); + const gate = new ethers.Contract(await c.liquidatorContract(), GATE_ABI, owner); + const tp: BigNumber = await gate.treasuryPercentMantissa(); + let vReceived: BigNumber = seizeTokens; + if (!tp.isZero()) { + const inc: BigNumber = await c.getEffectiveLiquidationIncentive(borrower, mkt.vBStock.address); + const bonus = seizeTokens.mul(inc.sub(ONE)).div(inc); + vReceived = seizeTokens.sub(bonus.mul(tp).div(ONE)); + } + const xr: BigNumber = await mkt.vBStock.exchangeRateStored(); + const redeemFee: BigNumber = await c.treasuryPercent(); + const heldBStock = vReceived.mul(xr).div(ONE).mul(ONE.sub(redeemFee)).div(ONE); + const usdtOut = heldBStock.mul(P_CRASH).div(ONE); // both mocks pay `rate` per unit pulled + return usdtOut.mul(995).div(1000); + } + + async function pcsHop2(x2: BigNumber, path: string[]): Promise<{ calldata: string; expectedOut: BigNumber }> { + const pcs = new ethers.Contract(A.PCS_ROUTER, PCS_ABI, owner); + const expectedOut: BigNumber = (await pcs.getAmountsOut(x2, path))[path.length - 1]; + return { + calldata: pcs.interface.encodeFunctionData("swapExactTokensForTokens", [ + x2, + 0, + path, + liq.address, + ethers.constants.MaxUint256, + ]), + expectedOut, + }; + } + + // Real VAI debt: prime-holder mint gate toggled through the real ACM, VAI minted on the real + // VAIController, then the bStock price gaps down (copy of the original suite's builder). + async function makeUnderwaterVaiBorrower(borrowerAddr: string, vaiDebt: BigNumber): Promise { + const tl = await initMainnetUser(A.TIMELOCK, parseEther("5")); + await new ethers.Contract(A.ACM, ACM_GIVE_ABI, tl).giveCallPermission( + VAI_CONTROLLER, + "toggleOnlyPrimeHolderMint()", + owner.address, + ); + const vaiCtrl = new ethers.Contract(VAI_CONTROLLER, VAI_CONTROLLER_ABI, owner); + await vaiCtrl.toggleOnlyPrimeHolderMint(); + const borrower = await initMainnetUser(borrowerAddr, parseUnits("10", 18)); + const COLLATERAL = parseUnits("100", 18); + await mkt.bStock.mint(borrowerAddr, COLLATERAL); + await mkt.bStock.connect(borrower).approve(mkt.vBStock.address, COLLATERAL); + await new ethers.Contract(mkt.vBStock.address, VTOKEN_ABI, borrower).mint(COLLATERAL); + await new ethers.Contract( + A.COMPTROLLER, + ["function enterMarkets(address[]) returns (uint256[])"], + borrower, + ).enterMarkets([mkt.vBStock.address]); + const mintCode: BigNumber = await vaiCtrl.connect(borrower).callStatic.mintVAI(vaiDebt); + if (!mintCode.eq(0)) throw new Error(`mintVAI returned code ${mintCode.toString()}`); + await vaiCtrl.connect(borrower).mintVAI(vaiDebt); + await mkt.setPrice(P_CRASH); + return vaiCtrl; + } + + async function crash() { + await mkt.setPrice(P_CRASH); + await mock.setRate(P_CRASH); + await lmRouter.setRate(P_CRASH); + } + + before(async () => { + [owner] = await ethers.getSigners(); + await setBalance(owner.address, parseEther("1000")); + + mkt = await listBStockMarket(owner, P_HEALTHY); + ({ mock } = await deployFundedMockNative(owner, P_HEALTHY)); + + spender = await (await ethers.getContractFactory("MockSpender")).deploy(); + lmRouter = await (await ethers.getContractFactory("MockSplitRouter")).deploy(spender.address); + await lmRouter.setRate(P_HEALTHY); + const usdtSlot = await findBalanceSlot(TOK.USDT); + if (usdtSlot === null) throw new Error("no USDT slot"); + slotCache[TOK.USDT] = usdtSlot; + await setTokenBalance(TOK.USDT, lmRouter.address, parseUnits("5000000", 18), usdtSlot); + + // Pin the BNB price as a DIRECT price on the live ChainlinkOracle (same pattern the suite uses + // for bStock): the live BNB feed's staleness window is short, so fork-time drift (every simulated + // tx advances the clock) makes the untouched 3-oracle config revert "invalid resilient oracle + // price" on any vBNB borrow/liquidity check by the time the BNB cells run. Direct prices skip the + // staleness/pivot validation; $564 matches the live price at the fork block. + { + const tl = await asTimelockWith([ + [A.RESILIENT_ORACLE, "setTokenConfig(TokenConfig)"], + [A.CHAINLINK_ORACLE, "setDirectPrice(address,uint256)"], + ]); + await new ethers.Contract( + A.RESILIENT_ORACLE, + [ + "function setTokenConfig(tuple(address asset, address[3] oracles, bool[3] enableFlagsForOracles, bool cachingEnabled))", + ], + tl, + ).setTokenConfig({ + asset: TOK.WBNB, + oracles: [A.CHAINLINK_ORACLE, ZERO, ZERO], + enableFlagsForOracles: [true, false, false], + cachingEnabled: false, + }); + await new ethers.Contract(A.CHAINLINK_ORACLE, ["function setDirectPrice(address,uint256)"], tl).setDirectPrice( + TOK.WBNB, + parseUnits("564", 18), + ); + } + + // Fresh proxy on the current implementation: the deployed bscmainnet proxy predates both + // routerSpender (LM cells) and the VAI branch, and prod requires a redeploy anyway (PR note). + const Factory = await ethers.getContractFactory("BStockLiquidator"); + liq = await upgrades.deployProxy(Factory, [owner.address], { + constructorArgs: [A.COMPTROLLER, A.VBNB, A.VWBNB, TOK.WBNB], + unsafeAllow: ["constructor", "state-variable-immutable"], + }); + await liq.connect(owner).setRouter(mock.address, true); + await liq.connect(owner).setRouter(lmRouter.address, true); + await liq.connect(owner).setRouterSpender(lmRouter.address, spender.address); + await liq.connect(owner).setRouter(A.PCS_ROUTER, true); + await liq.connect(owner).setRouter(PSM_USDT, true); + + // Flash infra once for every flash market used by the matrix. + for (const v of [VUSDT, VCAKE, A.VWBNB]) flashReady[v] = await enableFlash(v); + + baseSnap = await ethers.provider.send("evm_snapshot", []); + }); + + afterEach(async () => { + await ethers.provider.send("evm_revert", [baseSnap]); + baseSnap = await ethers.provider.send("evm_snapshot", []); + await mock.setRate(P_HEALTHY); + await lmRouter.setRate(P_HEALTHY); + }); + + /* ------------------------- inventory × LM split-spender ------------------------- */ + + it("inv × LM × CAKE: split-spender hop-1, real-PCS hop-2, settles", async () => { + await makeUnderwaterBorrower({ + mkt, + vDebt: VCAKE, + borrower: M.LM_CAKE, + collateralBStock: parseUnits("100", 18), + borrowAmount: parseUnits("4000", 18), + crashPriceTo: P_CRASH, + }); + await crash(); + const REPAY = parseUnits("1500", 18); + await fund(TOK.CAKE, liq.address, REPAY); + const x2 = await hop1UsdtOut(M.LM_CAKE, VCAKE, REPAY); + const h2 = await pcsHop2(x2, [TOK.USDT, TOK.CAKE]); + const h1 = hop1("lm"); + const params = { + borrower: M.LM_CAKE, + vDebt: VCAKE, + vBStock: mkt.vBStock.address, + repayAmount: REPAY, + router: h1.router, + swapCalldata: h1.calldata, + minOut: h2.expectedOut.mul(95).div(100), + router2: A.PCS_ROUTER, + swapCalldata2: h2.calldata, + intermediateToken: TOK.USDT, + deadline: ethers.constants.MaxUint256, + }; + const borrowBefore = await new ethers.Contract(VCAKE, VTOKEN_ABI, owner).borrowBalanceStored(M.LM_CAKE); + expect(await liq.connect(owner).callStatic.liquidate(params)).to.be.gte(params.minOut); + await liq.connect(owner).liquidate(params); + await assertSettledFor(liq, mkt, VCAKE, M.LM_CAKE, borrowBefore); + // No standing approval left for the split spender on either hop input. + const alw = ["function allowance(address,address) view returns (uint256)"]; + expect( + await new ethers.Contract(mkt.bStock.address, alw, owner).allowance(liq.address, spender.address), + ).to.equal(0); + }); + + it("inv × LM × BNB: split-spender hop-1, WBNB unwrap repay, settles", async () => { + await makeUnderwaterBorrower({ + mkt, + vDebt: A.VBNB, + borrower: M.LM_BNB, + collateralBStock: parseUnits("100", 18), + borrowAmount: parseEther("8"), + crashPriceTo: P_CRASH, + }); + await crash(); + const REPAY = parseEther("2"); + await fund(TOK.WBNB, liq.address, REPAY); + const x2 = await hop1UsdtOut(M.LM_BNB, A.VBNB, REPAY); + const h2 = await pcsHop2(x2, [TOK.USDT, TOK.WBNB]); + const h1 = hop1("lm"); + const params = { + borrower: M.LM_BNB, + vDebt: A.VBNB, + vBStock: mkt.vBStock.address, + repayAmount: REPAY, + router: h1.router, + swapCalldata: h1.calldata, + minOut: h2.expectedOut.mul(95).div(100), + router2: A.PCS_ROUTER, + swapCalldata2: h2.calldata, + intermediateToken: TOK.USDT, + deadline: ethers.constants.MaxUint256, + }; + const vBnb = new ethers.Contract(A.VBNB, VTOKEN_ABI, owner); + const borrowBefore = await vBnb.borrowBalanceStored(M.LM_BNB); + expect(await liq.connect(owner).callStatic.liquidate(params)).to.be.gte(params.minOut); + await liq.connect(owner).liquidate(params); + expect(await vBnb.borrowBalanceStored(M.LM_BNB)).to.be.lt(borrowBefore); + expect(await ethers.provider.getBalance(liq.address)).to.equal(0); + expect(await mkt.bStock.balanceOf(liq.address)).to.equal(0); + }); + + // The main suite's inv×native×BNB cell is block-sensitive (BNB-feed staleness under fork clock + // drift); re-proven here under the direct-price pin so the whole matrix row runs at any block. + it("inv × native × BNB: router-pulls hop-1, WBNB unwrap repay, settles", async () => { + const borrower = ethers.utils.getAddress("0x00000000000000000000000000000000ca14000a"); + await makeUnderwaterBorrower({ + mkt, + vDebt: A.VBNB, + borrower, + collateralBStock: parseUnits("100", 18), + borrowAmount: parseEther("8"), + crashPriceTo: P_CRASH, + }); + await crash(); + const REPAY = parseEther("2"); + await fund(TOK.WBNB, liq.address, REPAY); + const x2 = await hop1UsdtOut(borrower, A.VBNB, REPAY); + const h2 = await pcsHop2(x2, [TOK.USDT, TOK.WBNB]); + const h1 = hop1("native"); + const params = { + borrower, + vDebt: A.VBNB, + vBStock: mkt.vBStock.address, + repayAmount: REPAY, + router: h1.router, + swapCalldata: h1.calldata, + minOut: h2.expectedOut.mul(95).div(100), + router2: A.PCS_ROUTER, + swapCalldata2: h2.calldata, + intermediateToken: TOK.USDT, + deadline: ethers.constants.MaxUint256, + }; + const vBnb = new ethers.Contract(A.VBNB, VTOKEN_ABI, owner); + const borrowBefore = await vBnb.borrowBalanceStored(borrower); + expect(await liq.connect(owner).callStatic.liquidate(params)).to.be.gte(params.minOut); + await liq.connect(owner).liquidate(params); + expect(await vBnb.borrowBalanceStored(borrower)).to.be.lt(borrowBefore); + expect(await ethers.provider.getBalance(liq.address)).to.equal(0); + expect(await mkt.bStock.balanceOf(liq.address)).to.equal(0); + }); + + it("inv × LM × VAI: split-spender hop-1, real PSM hop-2 (script-built calldata), settles", async () => { + const vaiCtrl = await makeUnderwaterVaiBorrower(M.LM_VAI, parseUnits("4000", 18)); + await crash(); + const REPAY = parseUnits("1500", 18); + await fund(VAI, liq.address, REPAY); + const floor = await hop1UsdtOut(M.LM_VAI, VAI_CONTROLLER, REPAY, true); + const psmSwap = await getPsmSwap({ amountIn: floor, recipient: liq.address }, ethers.provider); + expect(psmSwap.router).to.equal(PSM_USDT); + const expectedOut = BigNumber.from(psmSwap.expectedOut); + const h1 = hop1("lm"); + const params = { + borrower: M.LM_VAI, + vDebt: VAI_CONTROLLER, + vBStock: mkt.vBStock.address, + repayAmount: REPAY, + router: h1.router, + swapCalldata: h1.calldata, + minOut: expectedOut.mul(999).div(1000), + router2: psmSwap.router, + swapCalldata2: psmSwap.calldata, + intermediateToken: TOK.USDT, + deadline: ethers.constants.MaxUint256, + }; + const debtBefore = await vaiCtrl.getVAIRepayAmount(M.LM_VAI); + expect(await liq.connect(owner).callStatic.liquidate(params)).to.be.gte(params.minOut); + await liq.connect(owner).liquidate(params); + expect(await vaiCtrl.getVAIRepayAmount(M.LM_VAI)).to.be.lt(debtBefore); + expect(await new ethers.Contract(VAI, ERC20_ABI, owner).balanceOf(liq.address)).to.equal(expectedOut); + expect(await mkt.bStock.balanceOf(liq.address)).to.equal(0); + }); + + /* ------------------------------- flash cells ------------------------------- */ + + interface FlashCell { + title: string; + src: "native" | "lm"; + vDebt: string; + borrower: string; + borrowAmount: BigNumber; + repay: BigNumber; + debtToken: string; // ERC20 the proceeds/premium settle in (WBNB for vBNB) + path?: string[]; // hop-2 PCS path; undefined = single-hop USDT debt + } + + const flashCells: FlashCell[] = [ + { + title: "flash × LM × USDT: split-spender single hop, repays principal + premium", + src: "lm", + vDebt: VUSDT, + borrower: M.FL_LM_USDT, + borrowAmount: parseUnits("5000", 18), + repay: parseUnits("2000", 18), + debtToken: TOK.USDT, + }, + { + title: "flash × native × CAKE: two-hop real PCS, repays principal + premium", + src: "native", + vDebt: VCAKE, + borrower: M.FL_N_CAKE, + borrowAmount: parseUnits("4000", 18), + repay: parseUnits("1500", 18), + debtToken: TOK.CAKE, + path: [TOK.USDT, TOK.CAKE], + }, + { + title: "flash × LM × CAKE: split-spender + two-hop real PCS, repays principal + premium", + src: "lm", + vDebt: VCAKE, + borrower: M.FL_LM_CAKE, + borrowAmount: parseUnits("4000", 18), + repay: parseUnits("1500", 18), + debtToken: TOK.CAKE, + path: [TOK.USDT, TOK.CAKE], + }, + { + title: "flash × native × BNB: vWBNB flash + unwrap, two-hop to WBNB, repays principal + premium", + src: "native", + vDebt: A.VBNB, + borrower: M.FL_N_BNB, + borrowAmount: parseEther("8"), + repay: parseEther("2"), + debtToken: TOK.WBNB, + path: [TOK.USDT, TOK.WBNB], + }, + { + title: "flash × LM × BNB: split-spender + vWBNB flash + unwrap, repays principal + premium", + src: "lm", + vDebt: A.VBNB, + borrower: M.FL_LM_BNB, + borrowAmount: parseEther("8"), + repay: parseEther("2"), + debtToken: TOK.WBNB, + path: [TOK.USDT, TOK.WBNB], + }, + ]; + + for (const cell of flashCells) { + it(cell.title, async () => { + // vBNB debt is flash-funded from vWBNB (vBNB cannot be flash-repaid). + const flashMarket = cell.vDebt === A.VBNB ? A.VWBNB : cell.vDebt; + if (!flashReady[flashMarket]) { + console.log(` SKIP: flash infra unavailable for ${flashMarket} at block ${FORK_BLOCK}`); + return; + } + await makeUnderwaterBorrower({ + mkt, + vDebt: cell.vDebt, + borrower: cell.borrower, + collateralBStock: parseUnits("100", 18), + borrowAmount: cell.borrowAmount, + crashPriceTo: P_CRASH, + }); + await crash(); + const h1 = hop1(cell.src); + let swapCalldata2 = "0x"; + let router2 = ZERO; + let intermediateToken = ZERO; + let minOut: BigNumber; + if (cell.path) { + const x2 = await hop1UsdtOut(cell.borrower, cell.vDebt, cell.repay); + const h2 = await pcsHop2(x2, cell.path); + swapCalldata2 = h2.calldata; + router2 = A.PCS_ROUTER; + intermediateToken = TOK.USDT; + minOut = h2.expectedOut.mul(95).div(100); + } else { + minOut = (await hop1UsdtOut(cell.borrower, cell.vDebt, cell.repay)).mul(99).div(100); + } + const params = { + borrower: cell.borrower, + vDebt: cell.vDebt, + vBStock: mkt.vBStock.address, + repayAmount: cell.repay, + router: h1.router, + swapCalldata: h1.calldata, + minOut, + router2, + swapCalldata2, + intermediateToken, + deadline: ethers.constants.MaxUint256, + }; + const vDebtC = new ethers.Contract(cell.vDebt, VTOKEN_ABI, owner); + const borrowBefore = await vDebtC.borrowBalanceStored(cell.borrower); + const debtErc20 = new ethers.Contract(cell.debtToken, ERC20_ABI, owner); + const invBefore: BigNumber = await debtErc20.balanceOf(liq.address); // 0 — flash needs no inventory + expect(invBefore).to.equal(0); + await liq.connect(owner).flashLiquidate(params); + expect(await vDebtC.borrowBalanceStored(cell.borrower)).to.be.lt(borrowBefore); + // Flash principal (+ premium) was repaid from proceeds; the contract keeps a positive profit + // in the debt-accounting token, holds no bStock, and strands no native BNB. + expect(await debtErc20.balanceOf(liq.address)).to.be.gt(0); + expect(await mkt.bStock.balanceOf(liq.address)).to.equal(0); + expect(await ethers.provider.getBalance(liq.address)).to.equal(0); + }); + } + + /* ------------------------------ negative: flash × VAI ------------------------------ */ + + it("flash × VAI (either source): rejected with FlashNotSupportedForVai before any swap", async () => { + const vaiCtrl = await makeUnderwaterVaiBorrower(M.FL_VAI, parseUnits("4000", 18)); + await crash(); + const h1 = hop1("lm"); + const params = { + borrower: M.FL_VAI, + vDebt: VAI_CONTROLLER, + vBStock: mkt.vBStock.address, + repayAmount: parseUnits("1500", 18), + router: h1.router, + swapCalldata: h1.calldata, + minOut: 1, + router2: PSM_USDT, + swapCalldata2: "0x", + intermediateToken: TOK.USDT, + deadline: ethers.constants.MaxUint256, + }; + await expect(liq.connect(owner).flashLiquidate(params)).to.be.revertedWithCustomError( + liq, + "FlashNotSupportedForVai", + ); + // Debt untouched. + expect(await vaiCtrl.getVAIRepayAmount(M.FL_VAI)).to.be.gte(parseUnits("4000", 18)); + }); + }); +}; + +if (FORK_MAINNET) { + forking(FORK_BLOCK, test); +} From 2ca908e36a6b3ba1ecbb30d490fcb2d4d991189e Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 20 Jul 2026 12:41:30 +0530 Subject: [PATCH 73/78] fix(bstock): pin vBNB oracle under the native sentinel, not WBNB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ResilientOracle maps the native market to NATIVE_TOKEN_ADDR (0xbBbB…BBbB), not WBNB (0xbb4CdB…095c), so the matrix suite's price pin never reached vBNB. Every vBNB read stayed on the live feed and reverted 'invalid resilient oracle price' once a run drifted past its ~30min staleness window, failing the BNB cells intermittently. --- tests/hardhat/Fork/BStockMatrixFork.ts | 37 ++++++++++++++++---------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/tests/hardhat/Fork/BStockMatrixFork.ts b/tests/hardhat/Fork/BStockMatrixFork.ts index e172f29c3..7d072b8bb 100644 --- a/tests/hardhat/Fork/BStockMatrixFork.ts +++ b/tests/hardhat/Fork/BStockMatrixFork.ts @@ -243,31 +243,40 @@ const test = () => { await setTokenBalance(TOK.USDT, lmRouter.address, parseUnits("5000000", 18), usdtSlot); // Pin the BNB price as a DIRECT price on the live ChainlinkOracle (same pattern the suite uses - // for bStock): the live BNB feed's staleness window is short, so fork-time drift (every simulated - // tx advances the clock) makes the untouched 3-oracle config revert "invalid resilient oracle - // price" on any vBNB borrow/liquidity check by the time the BNB cells run. Direct prices skip the - // staleness/pivot validation; $564 matches the live price at the fork block. + // for bStock): the live BNB feed's staleness window is short (~30 min), so fork-time drift (every + // simulated tx advances the clock) makes the untouched 3-oracle config revert "invalid resilient + // oracle price" on any vBNB borrow/liquidity check. A direct price short-circuits the feed read + // (ChainlinkOracle._getPriceInternal prefers `prices[asset]`), so it skips the staleness check. + // + // KEY: vBNB does NOT price under WBNB. ResilientOracle._getUnderlyingAsset maps the native market + // to the sentinel NATIVE_TOKEN_ADDR (0xbBbB…BBbB) — deceptively similar to WBNB (0xbb4CdB…095c) — + // because vBNB has no `underlying()`. Pinning WBNB alone leaves every vBNB read on the live feed, + // which is why the BNB cells failed intermittently once a run advanced the clock far enough. + // Both are pinned: the sentinel for vBNB, and WBNB itself for vWBNB (a normal market whose + // `underlying()` IS WBNB — the flash source for the BNB cells). $564 ≈ the live price at the block. { + const NATIVE_TOKEN_ADDR = ethers.utils.getAddress("0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"); const tl = await asTimelockWith([ [A.RESILIENT_ORACLE, "setTokenConfig(TokenConfig)"], [A.CHAINLINK_ORACLE, "setDirectPrice(address,uint256)"], ]); - await new ethers.Contract( + const resilient = new ethers.Contract( A.RESILIENT_ORACLE, [ "function setTokenConfig(tuple(address asset, address[3] oracles, bool[3] enableFlagsForOracles, bool cachingEnabled))", ], tl, - ).setTokenConfig({ - asset: TOK.WBNB, - oracles: [A.CHAINLINK_ORACLE, ZERO, ZERO], - enableFlagsForOracles: [true, false, false], - cachingEnabled: false, - }); - await new ethers.Contract(A.CHAINLINK_ORACLE, ["function setDirectPrice(address,uint256)"], tl).setDirectPrice( - TOK.WBNB, - parseUnits("564", 18), ); + const chainlink = new ethers.Contract(A.CHAINLINK_ORACLE, ["function setDirectPrice(address,uint256)"], tl); + for (const asset of [NATIVE_TOKEN_ADDR, TOK.WBNB]) { + await resilient.setTokenConfig({ + asset, + oracles: [A.CHAINLINK_ORACLE, ZERO, ZERO], + enableFlagsForOracles: [true, false, false], + cachingEnabled: false, + }); + await chainlink.setDirectPrice(asset, parseUnits("564", 18)); + } } // Fresh proxy on the current implementation: the deployed bscmainnet proxy predates both From 9d94d4db8baefe6e551abbdbbc1bdde247d8d92a Mon Sep 17 00:00:00 2001 From: Fred Date: Mon, 20 Jul 2026 19:31:29 +0800 Subject: [PATCH 74/78] test(bstock): harden scenario matrix --- .github/workflows/bstock-matrix-fork.yml | 51 ++++++++++++++++++++ tests/hardhat/Fork/BStockMatrixFork.ts | 59 +++++++++++------------- 2 files changed, 78 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/bstock-matrix-fork.yml diff --git a/.github/workflows/bstock-matrix-fork.yml b/.github/workflows/bstock-matrix-fork.yml new file mode 100644 index 000000000..6200b6e1d --- /dev/null +++ b/.github/workflows/bstock-matrix-fork.yml @@ -0,0 +1,51 @@ +name: bStock scenario matrix fork + +on: + workflow_dispatch: + inputs: + block: + description: BSC block to fork + required: true + default: "110490000" + type: string + +permissions: + contents: read + +concurrency: + group: bstock-matrix-fork-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Fork matrix at block ${{ inputs.block }} + runs-on: ubuntu-22.04 + timeout-minutes: 20 + env: + ARCHIVE_NODE_bscmainnet: ${{ secrets.ARCHIVE_NODE_BSCMAINNET }} + FORK_BSTOCK_BLOCK: ${{ inputs.block }} + FORKED_NETWORK: bscmainnet + NODE_OPTIONS: --max-old-space-size=4096 + + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: yarn + + - name: Require the BSC archive RPC secret + run: | + if [ -z "$ARCHIVE_NODE_bscmainnet" ]; then + echo "::error::Actions secret ARCHIVE_NODE_BSCMAINNET is required" + exit 1 + fi + + - name: Install dependencies + run: YARN_CHECKSUM_BEHAVIOR=update yarn + + - name: Run the bStock scenario matrix + run: yarn hardhat test tests/hardhat/Fork/BStockMatrixFork.ts diff --git a/tests/hardhat/Fork/BStockMatrixFork.ts b/tests/hardhat/Fork/BStockMatrixFork.ts index 7d072b8bb..22d781831 100644 --- a/tests/hardhat/Fork/BStockMatrixFork.ts +++ b/tests/hardhat/Fork/BStockMatrixFork.ts @@ -18,16 +18,17 @@ // off-chain RFQ fill is mocked (MockNativeRouter = target-pulls, MockSplitRouter + MockSpender = // split-spender pulls, i.e. the Liquid Mesh settlement shape). // -// Hop-2 sizing mirrors atomic-liquidate.ts EXACTLY (borrower-aware 4-arg seize, gate treasury cut -// off the effective incentive, redeem treasuryPercent) instead of the main helper's blanket -10%, -// so the flash cells' principal+premium repay constraint is exercised at the REAL incentive margin -// (~4%: 10% per-market bonus, gate keeps 50%, redeem fee and flash fee both 0 live) — a -10% -// undershoot would starve the flash repay and could never settle. +// Hop-2 sizing uses the same accounting inputs as atomic-liquidate.ts (borrower-aware 4-arg seize, +// gate treasury cut off the effective incentive, redeem treasuryPercent), with a conservative 0.5% +// margin instead of the main helper's blanket -10%. This exercises the flash principal+premium repay +// constraint at the REAL incentive margin (~4%: 10% per-market bonus, gate keeps 50%, redeem fee and +// flash fee both 0 live) — a -10% undershoot would starve the flash repay and could never settle. // // Run (recent block: flash-loan support + the whitelist setter are live there): // FORK_BSTOCK_BLOCK=110490000 FORKED_NETWORK=bscmainnet npx hardhat test \ // tests/hardhat/Fork/BStockMatrixFork.ts import { setBalance } from "@nomicfoundation/hardhat-network-helpers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { expect } from "chai"; import { BigNumber, Contract } from "ethers"; import { parseEther, parseUnits } from "ethers/lib/utils"; @@ -72,6 +73,7 @@ const COMPTROLLER_ABI = [ "function getEffectiveLiquidationIncentive(address,address) view returns (uint256)", "function treasuryPercent() view returns (uint256)", "function liquidatorContract() view returns (address)", + "function authorizedFlashLoan(address) view returns (bool)", "function setWhiteListFlashLoanAccount(address,bool)", ]; const VAI_CONTROLLER_ABI = [ @@ -103,13 +105,12 @@ const M = { const test = () => { describe("BStockLiquidator — scenario matrix (mode × hop-1 source × debt)", () => { - let owner: any; + let owner: SignerWithAddress; let mkt: BStockMarket; let liq: Contract; // fresh proxy on the current impl (routerSpender + VAI support) let mock: Contract; // native-style hop-1: the router itself pulls let lmRouter: Contract; // LM-style hop-1: pulls via a separate spender contract let spender: Contract; - const flashReady: Record = {}; let baseSnap: string; const slotCache: Record = {}; @@ -123,24 +124,23 @@ const test = () => { } // Enable flash-loans on a market + whitelist the liquidator on the diamond, through the real - // ACM + timelock (governance-reachable state). Returns false when the infra is absent at this - // block (cells then log a skip, mirroring the original suite's documented-skip pattern). - async function enableFlash(vDebt: string): Promise { - try { - const tl = await initMainnetUser(A.TIMELOCK, parseEther("10")); - const acm = new ethers.Contract(A.ACM, ACM_GIVE_ABI, tl); - const v = new ethers.Contract(vDebt, FLASH_VTOKEN_ABI, owner); - if (!(await v.isFlashLoanEnabled())) { - await acm.giveCallPermission(vDebt, "setFlashLoanEnabled(bool)", owner.address); - await v.setFlashLoanEnabled(true); - } - await acm.giveCallPermission(A.COMPTROLLER, "setWhiteListFlashLoanAccount(address,bool)", A.TIMELOCK); - await new ethers.Contract(A.COMPTROLLER, COMPTROLLER_ABI, tl).setWhiteListFlashLoanAccount(liq.address, true); - return true; - } catch (e) { - console.log(` flash infra unavailable for ${vDebt} at this block: ${(e as Error).message.slice(0, 80)}`); - return false; + // ACM + timelock (governance-reachable state). This suite pins a block where the required flash + // infrastructure is live, so any setup failure must fail the suite instead of turning flash cells + // into passing no-ops. + async function enableFlash(vDebt: string): Promise { + const tl = await initMainnetUser(A.TIMELOCK, parseEther("10")); + const acm = new ethers.Contract(A.ACM, ACM_GIVE_ABI, tl); + const v = new ethers.Contract(vDebt, FLASH_VTOKEN_ABI, owner); + if (!(await v.isFlashLoanEnabled())) { + await acm.giveCallPermission(vDebt, "setFlashLoanEnabled(bool)", owner.address); + await v.setFlashLoanEnabled(true); } + expect(await v.isFlashLoanEnabled()).to.equal(true); + + await acm.giveCallPermission(A.COMPTROLLER, "setWhiteListFlashLoanAccount(address,bool)", A.TIMELOCK); + const comptroller = new ethers.Contract(A.COMPTROLLER, COMPTROLLER_ABI, tl); + await comptroller.setWhiteListFlashLoanAccount(liq.address, true); + expect(await comptroller.authorizedFlashLoan(liq.address)).to.equal(true); } // Hop-1 calldata: both mocks share the swapAll(tokenIn, tokenOut, to) shape; the difference under @@ -293,7 +293,7 @@ const test = () => { await liq.connect(owner).setRouter(PSM_USDT, true); // Flash infra once for every flash market used by the matrix. - for (const v of [VUSDT, VCAKE, A.VWBNB]) flashReady[v] = await enableFlash(v); + for (const v of [VUSDT, VCAKE, A.VWBNB]) await enableFlash(v); baseSnap = await ethers.provider.send("evm_snapshot", []); }); @@ -339,11 +339,12 @@ const test = () => { expect(await liq.connect(owner).callStatic.liquidate(params)).to.be.gte(params.minOut); await liq.connect(owner).liquidate(params); await assertSettledFor(liq, mkt, VCAKE, M.LM_CAKE, borrowBefore); - // No standing approval left for the split spender on either hop input. + // No standing approval remains for either hop's input-token spender. const alw = ["function allowance(address,address) view returns (uint256)"]; expect( await new ethers.Contract(mkt.bStock.address, alw, owner).allowance(liq.address, spender.address), ).to.equal(0); + expect(await new ethers.Contract(TOK.USDT, alw, owner).allowance(liq.address, A.PCS_ROUTER)).to.equal(0); }); it("inv × LM × BNB: split-spender hop-1, WBNB unwrap repay, settles", async () => { @@ -521,12 +522,6 @@ const test = () => { for (const cell of flashCells) { it(cell.title, async () => { - // vBNB debt is flash-funded from vWBNB (vBNB cannot be flash-repaid). - const flashMarket = cell.vDebt === A.VBNB ? A.VWBNB : cell.vDebt; - if (!flashReady[flashMarket]) { - console.log(` SKIP: flash infra unavailable for ${flashMarket} at block ${FORK_BLOCK}`); - return; - } await makeUnderwaterBorrower({ mkt, vDebt: cell.vDebt, From 8441a7cd3dbe795d24e244ebcee18426a034793f Mon Sep 17 00:00:00 2001 From: Fred Date: Mon, 20 Jul 2026 19:33:51 +0800 Subject: [PATCH 75/78] test(bstock): remove standalone fork workflow --- .github/workflows/bstock-matrix-fork.yml | 51 ------------------------ 1 file changed, 51 deletions(-) delete mode 100644 .github/workflows/bstock-matrix-fork.yml diff --git a/.github/workflows/bstock-matrix-fork.yml b/.github/workflows/bstock-matrix-fork.yml deleted file mode 100644 index 6200b6e1d..000000000 --- a/.github/workflows/bstock-matrix-fork.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: bStock scenario matrix fork - -on: - workflow_dispatch: - inputs: - block: - description: BSC block to fork - required: true - default: "110490000" - type: string - -permissions: - contents: read - -concurrency: - group: bstock-matrix-fork-${{ github.ref }} - cancel-in-progress: true - -jobs: - test: - name: Fork matrix at block ${{ inputs.block }} - runs-on: ubuntu-22.04 - timeout-minutes: 20 - env: - ARCHIVE_NODE_bscmainnet: ${{ secrets.ARCHIVE_NODE_BSCMAINNET }} - FORK_BSTOCK_BLOCK: ${{ inputs.block }} - FORKED_NETWORK: bscmainnet - NODE_OPTIONS: --max-old-space-size=4096 - - steps: - - name: Check out code - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 18 - cache: yarn - - - name: Require the BSC archive RPC secret - run: | - if [ -z "$ARCHIVE_NODE_bscmainnet" ]; then - echo "::error::Actions secret ARCHIVE_NODE_BSCMAINNET is required" - exit 1 - fi - - - name: Install dependencies - run: YARN_CHECKSUM_BEHAVIOR=update yarn - - - name: Run the bStock scenario matrix - run: yarn hardhat test tests/hardhat/Fork/BStockMatrixFork.ts From 2536b362da9298dbcc493563366e85dc7b68a9df Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 20 Jul 2026 18:47:54 +0530 Subject: [PATCH 76/78] chore(bstock): redeploy BStockLiquidator to bscmainnet --- deployments/bscmainnet/.migrations.json | 2 +- deployments/bscmainnet/BStockLiquidator.json | 177 +++++++++++---- .../BStockLiquidator_Implementation.json | 201 +++++++++++++++--- .../bscmainnet/BStockLiquidator_Proxy.json | 84 ++++---- .../97b5bdb91b047141f90bacdfba4b885e.json | 142 +++++++++++++ 5 files changed, 491 insertions(+), 115 deletions(-) create mode 100644 deployments/bscmainnet/solcInputs/97b5bdb91b047141f90bacdfba4b885e.json diff --git a/deployments/bscmainnet/.migrations.json b/deployments/bscmainnet/.migrations.json index 00a282445..45d003e70 100644 --- a/deployments/bscmainnet/.migrations.json +++ b/deployments/bscmainnet/.migrations.json @@ -5,5 +5,5 @@ "configure_prime": 1697032901, "configure_xvs_vaults": 1697032901, "vai_controller_config": 40555052, - "bstock_liquidator_deploy": 1783076577 + "bstock_liquidator_deploy": 1784553138 } diff --git a/deployments/bscmainnet/BStockLiquidator.json b/deployments/bscmainnet/BStockLiquidator.json index a7077318a..bebc22c93 100644 --- a/deployments/bscmainnet/BStockLiquidator.json +++ b/deployments/bscmainnet/BStockLiquidator.json @@ -1,5 +1,5 @@ { - "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "abi": [ { "anonymous": false, @@ -151,6 +151,11 @@ "name": "DeadlineExpired", "type": "error" }, + { + "inputs": [], + "name": "FlashNotSupportedForVai", + "type": "error" + }, { "inputs": [ { @@ -209,6 +214,17 @@ "name": "RouterNotAllowed", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "SpenderNotContract", + "type": "error" + }, { "inputs": [], "name": "SwapFailed", @@ -348,6 +364,25 @@ "name": "OwnershipTransferred", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "PartialSwapLeftover", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -367,6 +402,25 @@ "name": "RouterSet", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "RouterSpenderSet", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -710,6 +764,25 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "routerSpender", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -746,6 +819,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "setRouterSpender", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -861,86 +952,86 @@ "type": "constructor" } ], - "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", "receipt": { "to": null, "from": "0x8D43F1776B4d5eB69cDbE2e79899520754a0cd9A", - "contractAddress": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", - "transactionIndex": 41, - "gasUsed": "797587", - "logsBloom": "0x000000040000000000000000000000004020000000000000008000000000000000000000000000000000000000000800200000000000000000000000000000000000000000000000000000000000020000010000000000000000000000000000000000000200000000000010000008000000008000000000000000801000004000000000000080000000000000000000000000000000c0000000000000800000000000000000010000000000000400000000000000400000000000000000000000000220000000000000000000040000000000000400000000000000000020000000000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db", - "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "contractAddress": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "transactionIndex": 81, + "gasUsed": "797497", + "logsBloom": "0x000000040000000000000000000000004020000000000000008000000000000000000000040000000000000000000800200000000000000000000000000000000000000000000000000000000000020000010000000000000000000000000000000000000200000000000000000008000000008000000000000000001000004000000000000000000000000000000000400000000000c0000000000000800020000000000000000000000000000400000000000000000000000000000000000000000020000000000000000000040000000001000400000000000800000020002000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58", + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", "logs": [ { - "transactionIndex": 41, - "blockNumber": 107817335, - "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", - "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "topics": [ "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", - "0x000000000000000000000000883c36bade4babe393fef2fcfcfc24a5e8e1a3d5" + "0x000000000000000000000000de9734dd7aead610dd41ffe9abc25c5ccf142487" ], "data": "0x", - "logIndex": 131, - "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + "logIndex": 390, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" }, { - "transactionIndex": 41, - "blockNumber": 107817335, - "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", - "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "topics": [ "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000008d43f1776b4d5eb69cdbe2e79899520754a0cd9a" ], "data": "0x", - "logIndex": 132, - "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + "logIndex": 391, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" }, { - "transactionIndex": 41, - "blockNumber": 107817335, - "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", - "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "topics": [ "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", "0x0000000000000000000000008d43f1776b4d5eb69cdbe2e79899520754a0cd9a", "0x00000000000000000000000083f426233b358a36953f6951161e76fb7c866a7a" ], "data": "0x", - "logIndex": 133, - "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + "logIndex": 392, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" }, { - "transactionIndex": 41, - "blockNumber": 107817335, - "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", - "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], "data": "0x0000000000000000000000000000000000000000000000000000000000000001", - "logIndex": 134, - "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + "logIndex": 393, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" }, { - "transactionIndex": 41, - "blockNumber": 107817335, - "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", - "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "topics": ["0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f"], "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001bb765b741a5f3c2a338369dab539385534e3343", - "logIndex": 135, - "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + "logIndex": 394, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" } ], - "blockNumber": 107817335, - "cumulativeGasUsed": "5080085", + "blockNumber": 111096720, + "cumulativeGasUsed": "15406242", "status": 1, "byzantium": true }, "args": [ - "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", + "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", "0x1BB765b741A5f3C2A338369DAb539385534E3343", "0xc4d66de800000000000000000000000083f426233b358a36953f6951161e76fb7c866a7a" ], @@ -953,7 +1044,7 @@ "methodName": "initialize", "args": ["0x83f426233B358A36953F6951161E76FB7c866a7A"] }, - "implementation": "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", + "implementation": "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", "devdoc": { "details": "This contract implements a proxy that is upgradeable by an admin. To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector clashing], which can potentially be used in an attack, this contract uses the https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two things that go hand in hand: 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if that call matches one of the admin functions exposed by the proxy itself. 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the implementation. If the admin tries to call a function on the implementation it will fail with an error that says \"admin cannot fallback to proxy target\". These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to call a function from the proxy implementation. Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.", "kind": "dev", diff --git a/deployments/bscmainnet/BStockLiquidator_Implementation.json b/deployments/bscmainnet/BStockLiquidator_Implementation.json index 442725f94..d6a58cdd3 100644 --- a/deployments/bscmainnet/BStockLiquidator_Implementation.json +++ b/deployments/bscmainnet/BStockLiquidator_Implementation.json @@ -1,5 +1,5 @@ { - "address": "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", + "address": "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", "abi": [ { "inputs": [ @@ -54,6 +54,11 @@ "name": "DeadlineExpired", "type": "error" }, + { + "inputs": [], + "name": "FlashNotSupportedForVai", + "type": "error" + }, { "inputs": [ { @@ -112,6 +117,17 @@ "name": "RouterNotAllowed", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "SpenderNotContract", + "type": "error" + }, { "inputs": [], "name": "SwapFailed", @@ -251,6 +267,25 @@ "name": "OwnershipTransferred", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "PartialSwapLeftover", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -270,6 +305,25 @@ "name": "RouterSet", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "RouterSpenderSet", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -613,6 +667,25 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "routerSpender", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -649,6 +722,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "setRouterSpender", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -747,30 +838,30 @@ "type": "receive" } ], - "transactionHash": "0xd47c6d5d424453aacc3a414e9b25efc624926c2d58a60896a0c92a6549a14b6d", + "transactionHash": "0x86bdd6c0de5e57a8f23ec9a936d7d9dc36cd92615dec7bde78f76e37efa2923a", "receipt": { "to": null, "from": "0x8D43F1776B4d5eB69cDbE2e79899520754a0cd9A", - "contractAddress": "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", - "transactionIndex": 75, - "gasUsed": "2362520", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000800000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0xbfd34df4c415f62f4e3c0c58c502e5b35dc26b191928c7288de6cbc68cb31349", - "transactionHash": "0xd47c6d5d424453aacc3a414e9b25efc624926c2d58a60896a0c92a6549a14b6d", + "contractAddress": "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", + "transactionIndex": 74, + "gasUsed": "2644510", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x82b3c1c03990f9eca06aad80ed06e2c65f9d552d09ecf9470096cde3ee81d840", + "transactionHash": "0x86bdd6c0de5e57a8f23ec9a936d7d9dc36cd92615dec7bde78f76e37efa2923a", "logs": [ { - "transactionIndex": 75, - "blockNumber": 107817331, - "transactionHash": "0xd47c6d5d424453aacc3a414e9b25efc624926c2d58a60896a0c92a6549a14b6d", - "address": "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", + "transactionIndex": 74, + "blockNumber": 111096716, + "transactionHash": "0x86bdd6c0de5e57a8f23ec9a936d7d9dc36cd92615dec7bde78f76e37efa2923a", + "address": "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], "data": "0x00000000000000000000000000000000000000000000000000000000000000ff", - "logIndex": 208, - "blockHash": "0xbfd34df4c415f62f4e3c0c58c502e5b35dc26b191928c7288de6cbc68cb31349" + "logIndex": 467, + "blockHash": "0x82b3c1c03990f9eca06aad80ed06e2c65f9d552d09ecf9470096cde3ee81d840" } ], - "blockNumber": 107817331, - "cumulativeGasUsed": "10381817", + "blockNumber": 111096716, + "cumulativeGasUsed": "11995332", "status": 1, "byzantium": true }, @@ -781,10 +872,10 @@ "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" ], "numDeployments": 1, - "solcInputHash": "b16b9fd9d739d9f7b43142447745f751", - "metadata": "{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"contract IComptroller\",\"name\":\"comptroller_\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"vBNB_\",\"type\":\"address\"},{\"internalType\":\"contract IVToken\",\"name\":\"vWBNB_\",\"type\":\"address\"},{\"internalType\":\"contract IWBNB\",\"name\":\"wbnb_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"}],\"name\":\"BadInitiator\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nowTs\",\"type\":\"uint256\"}],\"name\":\"DeadlineExpired\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"got\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minOut\",\"type\":\"uint256\"}],\"name\":\"InsufficientOut\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidIntermediate\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NativeTransferFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotOperator\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyComptroller\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"errCode\",\"type\":\"uint256\"}],\"name\":\"RedeemFailed\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"}],\"name\":\"RouterNotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SwapFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WrongFlashAsset\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddressNotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroMinOut\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"borrower\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"vBStock\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"vDebt\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"repayAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"seizedBStock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"debtOut\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"flash\",\"type\":\"bool\"}],\"name\":\"Liquidated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"OperatorSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"RouterSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Swept\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"SweptNative\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"comptroller\",\"outputs\":[{\"internalType\":\"contract IComptroller\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract VToken[]\",\"name\":\"vTokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"amounts\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"premiums\",\"type\":\"uint256[]\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"param\",\"type\":\"bytes\"}],\"name\":\"executeOperation\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"uint256[]\",\"name\":\"repayAmounts\",\"type\":\"uint256[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"borrower\",\"type\":\"address\"},{\"internalType\":\"contract IVBep20\",\"name\":\"vDebt\",\"type\":\"address\"},{\"internalType\":\"contract IVBep20\",\"name\":\"vBStock\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"repayAmount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"swapCalldata\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"minOut\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"router2\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"swapCalldata2\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"intermediateToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"internalType\":\"struct IBStockLiquidator.LiquidationParams\",\"name\":\"params\",\"type\":\"tuple\"}],\"name\":\"flashLiquidate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"initialOwner\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isOperator\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isRouter\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"borrower\",\"type\":\"address\"},{\"internalType\":\"contract IVBep20\",\"name\":\"vDebt\",\"type\":\"address\"},{\"internalType\":\"contract IVBep20\",\"name\":\"vBStock\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"repayAmount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"swapCalldata\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"minOut\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"router2\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"swapCalldata2\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"intermediateToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"internalType\":\"struct IBStockLiquidator.LiquidationParams\",\"name\":\"params\",\"type\":\"tuple\"}],\"name\":\"liquidate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"debtOut\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"setOperator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"setRouter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"sweep\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"sweepNative\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vBNB\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vWBNB\",\"outputs\":[{\"internalType\":\"contract IVToken\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"wbnb\",\"outputs\":[{\"internalType\":\"contract IWBNB\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"author\":\"Venus\",\"events\":{\"Initialized(uint8)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"},\"Liquidated(address,address,address,uint256,uint256,uint256,bool)\":{\"params\":{\"borrower\":\"The liquidated account.\",\"debtOut\":\"Debt-asset proceeds of the swap.\",\"flash\":\"True if funded by a flash loan, false if from inventory.\",\"repayAmount\":\"Debt underlying repaid.\",\"seizedBStock\":\"Raw bStock redeemed and sold.\",\"vBStock\":\"The seized bStock collateral market.\",\"vDebt\":\"The repaid debt market.\"}}},\"kind\":\"dev\",\"methods\":{\"acceptOwnership()\":{\"details\":\"The new owner accepts the ownership transfer.\"},\"constructor\":{\"custom:oz-upgrades-unsafe-allow\":\"constructor\",\"params\":{\"comptroller_\":\"Venus Core Comptroller (diamond) \\u2014 gates liquidation and provides flash loans.\",\"vBNB_\":\"Native BNB market; a debt equal to this address is settled in native BNB.\",\"vWBNB_\":\"WBNB market; the flash-borrow source for BNB debt (vBNB itself cannot be flash-repaid).\",\"wbnb_\":\"WBNB token; the debt-accounting asset for BNB debt, unwrapped for the native repay.\"}},\"executeOperation(address[],uint256[],uint256[],address,address,bytes)\":{\"details\":\"Implementation of this function must ensure at least the premium (fee) is repaid within the same transaction. Any unpaid balance (principal + premium - repaid amount) will be added to the onBehalf address's borrow balance.\",\"params\":{\"amounts\":\"The amounts of each underlying asset that were flash-borrowed.\",\"initiator\":\"The address that initiated the flash loan.\",\"onBehalf\":\"The address of the user whose debt position will be used for any unpaid flash loan balance.\",\"param\":\"Additional parameters encoded as bytes. These can be used to pass custom data to the receiver contract.\",\"premiums\":\"The premiums (fees) associated with each flash-borrowed asset.\",\"vTokens\":\"The vToken contracts corresponding to the flash-borrowed underlying assets.\"},\"returns\":{\"_0\":\"True if the operation succeeds (regardless of repayment amount), false if the operation fails.\",\"repayAmounts\":\"Array of uint256 representing the amounts to be repaid for each asset. The receiver contract must approve these amounts to the respective vToken contracts before this function returns.\"}},\"flashLiquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))\":{\"details\":\"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`.\",\"params\":{\"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).\"}},\"initialize(address)\":{\"params\":{\"initialOwner\":\"Address that owns the contract (admin + default operator).\"}},\"liquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))\":{\"details\":\"INVENTORY mode spends the contract's OWN debt-asset capital, so \\u2014 unlike FLASH mode, where `executeOperation` forces the swap proceeds to cover principal + premium \\u2014 there is no built-in floor tying `debtOut` to `repayAmount`. This asymmetry is intentional: a repay can legitimately out-cost its proceeds (e.g. the Venus Liquidator keeps a treasury cut of the seized collateral, so proceeds land a few % under the repay). `minOut` IS the operator's chosen loss floor for inventory mode \\u2014 set it to the lowest acceptable debt-asset return.\",\"params\":{\"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).\"},\"returns\":{\"debtOut\":\"Debt-asset proceeds realized by the swap chain.\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"pendingOwner()\":{\"details\":\"Returns the address of the pending owner.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"setOperator(address,bool)\":{\"params\":{\"allowed\":\"True to allow, false to remove.\",\"operator\":\"Address to allowlist or remove.\"}},\"setRouter(address,bool)\":{\"params\":{\"allowed\":\"True to allow, false to remove.\",\"router\":\"Address to allowlist or remove.\"}},\"sweep(address,address,uint256)\":{\"params\":{\"amount\":\"Amount to withdraw.\",\"to\":\"Recipient.\",\"token\":\"Token to withdraw.\"}},\"sweepNative(address,uint256)\":{\"params\":{\"amount\":\"Amount of native BNB to withdraw.\",\"to\":\"Recipient.\"}},\"transferOwnership(address)\":{\"details\":\"Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. Can only be called by the current owner.\"}},\"stateVariables\":{\"__gap\":{\"details\":\"Reserved storage to allow new state variables in future upgrades without layout clashes.\"},\"comptroller\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"},\"vBNB\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"},\"vWBNB\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"},\"wbnb\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"}},\"title\":\"BStockLiquidator\",\"version\":1},\"userdoc\":{\"errors\":{\"BadInitiator(address)\":[{\"notice\":\"Thrown when the flash-loan initiator is not this contract.\"}],\"DeadlineExpired(uint256,uint256)\":[{\"notice\":\"Thrown when the call is submitted after `params.deadline`.\"}],\"InsufficientOut(uint256,uint256)\":[{\"notice\":\"Thrown when swap proceeds are below `minOut`.\"}],\"InvalidIntermediate()\":[{\"notice\":\"Thrown when a two-hop `intermediateToken` is zero, or equals the debt or bStock token.\"}],\"NativeTransferFailed()\":[{\"notice\":\"Thrown when a native BNB transfer (the `sweepNative` payout) fails.\"}],\"NotOperator()\":[{\"notice\":\"Thrown when the caller is neither the owner nor an allowlisted operator.\"}],\"OnlyComptroller()\":[{\"notice\":\"Thrown when `executeOperation` is called by something other than the Comptroller.\"}],\"RedeemFailed(uint256)\":[{\"notice\":\"Thrown when `vBStock.redeem` returns a non-zero error code.\"}],\"RouterNotAllowed(address)\":[{\"notice\":\"Thrown when the supplied swap router is not allowlisted.\"}],\"SwapFailed()\":[{\"notice\":\"Thrown when the low-level call to the router reverts.\"}],\"WrongFlashAsset()\":[{\"notice\":\"Thrown when the flashed asset does not match `params.vDebt`.\"}],\"ZeroAddressNotAllowed()\":[{\"notice\":\"Thrown if the supplied address is a zero address where it is not allowed\"}],\"ZeroMinOut()\":[{\"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).\"}]},\"events\":{\"Liquidated(address,address,address,uint256,uint256,uint256,bool)\":{\"notice\":\"Emitted on a successful liquidation.\"},\"OperatorSet(address,bool)\":{\"notice\":\"Emitted when an operator is allowlisted or removed.\"},\"RouterSet(address,bool)\":{\"notice\":\"Emitted when a swap router is allowlisted or removed.\"},\"Swept(address,address,uint256)\":{\"notice\":\"Emitted when the owner withdraws a token.\"},\"SweptNative(address,uint256)\":{\"notice\":\"Emitted when the owner withdraws stuck native BNB.\"}},\"kind\":\"user\",\"methods\":{\"comptroller()\":{\"notice\":\"Core Comptroller (diamond): reads the liquidation gate and provides the flash loan via `executeFlashLoan`.\"},\"constructor\":{\"notice\":\"Constructor for the implementation contract. Sets the immutables and locks initializers.\"},\"executeOperation(address[],uint256[],uint256[],address,address,bytes)\":{\"notice\":\"Executes an operation after receiving the flash-borrowed assets.\"},\"flashLiquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))\":{\"notice\":\"Liquidate by flash-borrowing the repay amount from Venus, repaid (+ premium) in the same tx.\"},\"initialize(address)\":{\"notice\":\"Initializes the proxy: sets the owner and the reentrancy guard.\"},\"isOperator(address)\":{\"notice\":\"Addresses allowed to trigger a liquidation.\"},\"isRouter(address)\":{\"notice\":\"Routers allowed as the swap target (defends the low-level call).\"},\"liquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))\":{\"notice\":\"Liquidate using the contract's own debt-asset inventory.\"},\"setOperator(address,bool)\":{\"notice\":\"Allow or disallow an address to trigger liquidations.\"},\"setRouter(address,bool)\":{\"notice\":\"Allow or disallow a router as the swap target (e.g. the Native router).\"},\"sweep(address,address,uint256)\":{\"notice\":\"Withdraw any token (profit, leftover inventory, stuck dust) to `to`.\"},\"sweepNative(address,uint256)\":{\"notice\":\"Withdraw stuck native BNB (a stray transfer, or a gate refund) to `to`.\"},\"vBNB()\":{\"notice\":\"The native BNB market (vBNB). A debt equal to this address is settled with native BNB: WBNB is the debt-accounting token, and only the repay amount is unwrapped (see `_liquidate`).\"},\"vWBNB()\":{\"notice\":\"The WBNB market (vWBNB). BNB debt is flash-funded from here, NOT from vBNB: vBNB cannot be flash-repaid (its `doTransferIn` needs `msg.value`), whereas vWBNB's underlying is a plain ERC20 repaid via `transferFrom`.\"},\"wbnb()\":{\"notice\":\"WBNB token: the debt-accounting asset for BNB debt, unwrapped to native BNB for the repay.\"}},\"notice\":\"Atomic backstop liquidator for bStock (ERC-8056 tokenized stock) collateral. In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken, redeems it to the raw bStock, and sells that bStock to the debt asset in one or two hops. Hop 1 always sells bStock through the Native RFQ router using a pre-fetched, MM-signed firm-quote `txRequest`. For USDT debt that single hop lands the debt asset directly. For non-USDT debt an OPTIONAL hop 2 (Native quotes bStock->USDT only) converts the USDT to the debt asset through a second allowlisted router (an AMM/aggregator). Because seize and sell happen in the same tx there is no price-drift window, and the realized debt-asset amount must clear `minOut` or the whole call reverts \\u2014 the protocol never ends up holding the RFQ-only asset or the intermediate. Two funding modes share the same core (`_liquidate`): - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance. - FLASH: the debt asset is flash-borrowed from Venus (`Comptroller.executeFlashLoan`) and repaid (+ premium) within the same tx; no capital is locked in the contract. Native BNB debt (vBNB): supported in both modes with WBNB as the debt-accounting token. The repay must be native BNB, so exactly the repay amount of WBNB is unwrapped and forwarded to the gate's payable path; the two-hop swap lands WBNB (bStock->USDT->WBNB) and `minOut` is measured in WBNB (1:1 with BNB). FLASH mode borrows from vWBNB, NOT vBNB: vBNB cannot be flash-repaid (its `doTransferIn` requires `msg.value`), whereas vWBNB's underlying is a plain ERC20. Ownership / scope: this is Venus's OWN backstop tool, NOT a public utility \\u2014 `liquidate` and `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus path with their own funds and their own offload. This contract is intentionally gated because it custodies funds (debt-asset inventory / flash principal) and forwards a CALLER-SUPPLIED calldata blob to an external router \\u2014 the swap's recipient (`to`) lives inside that calldata, so an open entrypoint would let anyone route the proceeds to themselves and drain the contract. The router allowlist and `minOut` bound the blast radius but cannot replace operator-gating. Security model: - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator). - BOTH swap targets (`router` and, when set, `router2`) must be allowlisted (`isRouter`) \\u2014 defends the low-level `router.call(swapCalldata)` on each hop. - the approval to each router is the exact amount being sold on that hop, reset to 0 afterwards (bStock on hop 1; the measured intermediate balance delta on hop 2, so pre-existing inventory is never exposed). - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started). - the realized debt-asset amount must clear `minOut` or the tx reverts. Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`, which is always configured on the networks this contract targets. While it is set, a direct `vToken.liquidateBorrow` from an arbitrary caller reverts UNAUTHORIZED, so this contract reads the gate at call time and routes the repay through that Venus Liquidator (the permissionless entry anyone may call), reverting if the gate is ever unset. Routing through the gate needs no governance change, and no other Core market is affected. Note: setting THIS contract as `liquidatorContract` is NOT an option \\u2014 the gate is pool-wide, so every other market's liquidations would be forced through here.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/BStock/BStockLiquidator.sol\":\"BStockLiquidator\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./OwnableUpgradeable.sol\\\";\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Contract module which provides access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership} and {acceptOwnership}.\\n *\\n * This module is used through inheritance. It will make available all functions\\n * from parent (Ownable).\\n */\\nabstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {\\n address private _pendingOwner;\\n\\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\\n\\n function __Ownable2Step_init() internal onlyInitializing {\\n __Ownable_init_unchained();\\n }\\n\\n function __Ownable2Step_init_unchained() internal onlyInitializing {\\n }\\n /**\\n * @dev Returns the address of the pending owner.\\n */\\n function pendingOwner() public view virtual returns (address) {\\n return _pendingOwner;\\n }\\n\\n /**\\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual override onlyOwner {\\n _pendingOwner = newOwner;\\n emit OwnershipTransferStarted(owner(), newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual override {\\n delete _pendingOwner;\\n super._transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev The new owner accepts the ownership transfer.\\n */\\n function acceptOwnership() public virtual {\\n address sender = _msgSender();\\n require(pendingOwner() == sender, \\\"Ownable2Step: caller is not the new owner\\\");\\n _transferOwnership(sender);\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n}\\n\",\"keccak256\":\"0x9140dabc466abab21b48b72dbda26736b1183a310d0e677d3719d201df026510\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../utils/ContextUpgradeable.sol\\\";\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n function __Ownable_init() internal onlyInitializing {\\n __Ownable_init_unchained();\\n }\\n\\n function __Ownable_init_unchained() internal onlyInitializing {\\n _transferOwnership(_msgSender());\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n require(owner() == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby disabling any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n}\\n\",\"keccak256\":\"0x359a1ab89b46b9aba7bcad3fb651924baf4893d15153049b9976b0fc9be1358e\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)\\n\\npragma solidity ^0.8.2;\\n\\nimport \\\"../../utils/AddressUpgradeable.sol\\\";\\n\\n/**\\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\\n *\\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\\n * reused. This mechanism prevents re-execution of each \\\"step\\\" but allows the creation of new initialization steps in\\n * case an upgrade adds a module that needs to be initialized.\\n *\\n * For example:\\n *\\n * [.hljs-theme-light.nopadding]\\n * ```solidity\\n * contract MyToken is ERC20Upgradeable {\\n * function initialize() initializer public {\\n * __ERC20_init(\\\"MyToken\\\", \\\"MTK\\\");\\n * }\\n * }\\n *\\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\\n * function initializeV2() reinitializer(2) public {\\n * __ERC20Permit_init(\\\"MyToken\\\");\\n * }\\n * }\\n * ```\\n *\\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\\n *\\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\\n *\\n * [CAUTION]\\n * ====\\n * Avoid leaving a contract uninitialized.\\n *\\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\\n *\\n * [.hljs-theme-light.nopadding]\\n * ```\\n * /// @custom:oz-upgrades-unsafe-allow constructor\\n * constructor() {\\n * _disableInitializers();\\n * }\\n * ```\\n * ====\\n */\\nabstract contract Initializable {\\n /**\\n * @dev Indicates that the contract has been initialized.\\n * @custom:oz-retyped-from bool\\n */\\n uint8 private _initialized;\\n\\n /**\\n * @dev Indicates that the contract is in the process of being initialized.\\n */\\n bool private _initializing;\\n\\n /**\\n * @dev Triggered when the contract has been initialized or reinitialized.\\n */\\n event Initialized(uint8 version);\\n\\n /**\\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\\n * `onlyInitializing` functions can be used to initialize parent contracts.\\n *\\n * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a\\n * constructor.\\n *\\n * Emits an {Initialized} event.\\n */\\n modifier initializer() {\\n bool isTopLevelCall = !_initializing;\\n require(\\n (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),\\n \\\"Initializable: contract is already initialized\\\"\\n );\\n _initialized = 1;\\n if (isTopLevelCall) {\\n _initializing = true;\\n }\\n _;\\n if (isTopLevelCall) {\\n _initializing = false;\\n emit Initialized(1);\\n }\\n }\\n\\n /**\\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\\n * used to initialize parent contracts.\\n *\\n * A reinitializer may be used after the original initialization step. This is essential to configure modules that\\n * are added through upgrades and that require initialization.\\n *\\n * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`\\n * cannot be nested. If one is invoked in the context of another, execution will revert.\\n *\\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\\n * a contract, executing them in the right order is up to the developer or operator.\\n *\\n * WARNING: setting the version to 255 will prevent any future reinitialization.\\n *\\n * Emits an {Initialized} event.\\n */\\n modifier reinitializer(uint8 version) {\\n require(!_initializing && _initialized < version, \\\"Initializable: contract is already initialized\\\");\\n _initialized = version;\\n _initializing = true;\\n _;\\n _initializing = false;\\n emit Initialized(version);\\n }\\n\\n /**\\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\\n */\\n modifier onlyInitializing() {\\n require(_initializing, \\\"Initializable: contract is not initializing\\\");\\n _;\\n }\\n\\n /**\\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\\n * through proxies.\\n *\\n * Emits an {Initialized} event the first time it is successfully executed.\\n */\\n function _disableInitializers() internal virtual {\\n require(!_initializing, \\\"Initializable: contract is initializing\\\");\\n if (_initialized != type(uint8).max) {\\n _initialized = type(uint8).max;\\n emit Initialized(type(uint8).max);\\n }\\n }\\n\\n /**\\n * @dev Returns the highest version that has been initialized. See {reinitializer}.\\n */\\n function _getInitializedVersion() internal view returns (uint8) {\\n return _initialized;\\n }\\n\\n /**\\n * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.\\n */\\n function _isInitializing() internal view returns (bool) {\\n return _initializing;\\n }\\n}\\n\",\"keccak256\":\"0x89be10e757d242e9b18d5a32c9fbe2019f6d63052bbe46397a430a1d60d7f794\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)\\n\\npragma solidity ^0.8.0;\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Contract module that helps prevent reentrant calls to a function.\\n *\\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\\n * available, which can be applied to functions to make sure there are no nested\\n * (reentrant) calls to them.\\n *\\n * Note that because there is a single `nonReentrant` guard, functions marked as\\n * `nonReentrant` may not call one another. This can be worked around by making\\n * those functions `private`, and then adding `external` `nonReentrant` entry\\n * points to them.\\n *\\n * TIP: If you would like to learn more about reentrancy and alternative ways\\n * to protect against it, check out our blog post\\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\\n */\\nabstract contract ReentrancyGuardUpgradeable is Initializable {\\n // Booleans are more expensive than uint256 or any type that takes up a full\\n // word because each write operation emits an extra SLOAD to first read the\\n // slot's contents, replace the bits taken up by the boolean, and then write\\n // back. This is the compiler's defense against contract upgrades and\\n // pointer aliasing, and it cannot be disabled.\\n\\n // The values being non-zero value makes deployment a bit more expensive,\\n // but in exchange the refund on every call to nonReentrant will be lower in\\n // amount. Since refunds are capped to a percentage of the total\\n // transaction's gas, it is best to keep them low in cases like this one, to\\n // increase the likelihood of the full refund coming into effect.\\n uint256 private constant _NOT_ENTERED = 1;\\n uint256 private constant _ENTERED = 2;\\n\\n uint256 private _status;\\n\\n function __ReentrancyGuard_init() internal onlyInitializing {\\n __ReentrancyGuard_init_unchained();\\n }\\n\\n function __ReentrancyGuard_init_unchained() internal onlyInitializing {\\n _status = _NOT_ENTERED;\\n }\\n\\n /**\\n * @dev Prevents a contract from calling itself, directly or indirectly.\\n * Calling a `nonReentrant` function from another `nonReentrant`\\n * function is not supported. It is possible to prevent this from happening\\n * by making the `nonReentrant` function external, and making it call a\\n * `private` function that does the actual work.\\n */\\n modifier nonReentrant() {\\n _nonReentrantBefore();\\n _;\\n _nonReentrantAfter();\\n }\\n\\n function _nonReentrantBefore() private {\\n // On the first call to nonReentrant, _status will be _NOT_ENTERED\\n require(_status != _ENTERED, \\\"ReentrancyGuard: reentrant call\\\");\\n\\n // Any calls to nonReentrant after this point will fail\\n _status = _ENTERED;\\n }\\n\\n function _nonReentrantAfter() private {\\n // By storing the original value once again, a refund is triggered (see\\n // https://eips.ethereum.org/EIPS/eip-2200)\\n _status = _NOT_ENTERED;\\n }\\n\\n /**\\n * @dev Returns true if the reentrancy guard is currently set to \\\"entered\\\", which indicates there is a\\n * `nonReentrant` function in the call stack.\\n */\\n function _reentrancyGuardEntered() internal view returns (bool) {\\n return _status == _ENTERED;\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n}\\n\",\"keccak256\":\"0x2025ccf05f6f1f2fd4e078e552836f525a1864e3854ed555047cd732320ab29b\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20Upgradeable {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `from` to `to` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\\n}\\n\",\"keccak256\":\"0x0e1f0f5f62f67a881cd1a9597acbc0a5e4071f3c2c10449a183b922ae7272e3f\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\\n *\\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\\n * need to send a transaction, and thus is not required to hold Ether at all.\\n *\\n * ==== Security Considerations\\n *\\n * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature\\n * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be\\n * considered as an intention to spend the allowance in any specific way. The second is that because permits have\\n * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should\\n * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be\\n * generally recommended is:\\n *\\n * ```solidity\\n * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {\\n * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}\\n * doThing(..., value);\\n * }\\n *\\n * function doThing(..., uint256 value) public {\\n * token.safeTransferFrom(msg.sender, address(this), value);\\n * ...\\n * }\\n * ```\\n *\\n * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of\\n * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also\\n * {SafeERC20-safeTransferFrom}).\\n *\\n * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so\\n * contracts should have entry points that don't rely on permit.\\n */\\ninterface IERC20PermitUpgradeable {\\n /**\\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\\n * given ``owner``'s signed approval.\\n *\\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\\n * ordering also apply here.\\n *\\n * Emits an {Approval} event.\\n *\\n * Requirements:\\n *\\n * - `spender` cannot be the zero address.\\n * - `deadline` must be a timestamp in the future.\\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\\n * over the EIP712-formatted function arguments.\\n * - the signature must use ``owner``'s current nonce (see {nonces}).\\n *\\n * For more information on the signature format, see the\\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\\n * section].\\n *\\n * CAUTION: See Security Considerations above.\\n */\\n function permit(\\n address owner,\\n address spender,\\n uint256 value,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external;\\n\\n /**\\n * @dev Returns the current nonce for `owner`. This value must be\\n * included whenever a signature is generated for {permit}.\\n *\\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\\n * prevents a signature from being used multiple times.\\n */\\n function nonces(address owner) external view returns (uint256);\\n\\n /**\\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\\n */\\n // solhint-disable-next-line func-name-mixedcase\\n function DOMAIN_SEPARATOR() external view returns (bytes32);\\n}\\n\",\"keccak256\":\"0x07e881de3b9f6d2c07909f193f24b96c7fe4ea60013260f3f25aecd8bab3c2f8\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20Upgradeable.sol\\\";\\nimport \\\"../extensions/IERC20PermitUpgradeable.sol\\\";\\nimport \\\"../../../utils/AddressUpgradeable.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20Upgradeable {\\n using AddressUpgradeable for address;\\n\\n /**\\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n */\\n function safeTransfer(IERC20Upgradeable token, address to, uint256 value) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n /**\\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\\n */\\n function safeTransferFrom(IERC20Upgradeable token, address from, address to, uint256 value) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(IERC20Upgradeable token, address spender, uint256 value) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n /**\\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n */\\n function safeIncreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));\\n }\\n\\n /**\\n * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n */\\n function safeDecreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));\\n }\\n }\\n\\n /**\\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\\n * to be set to zero before setting it to a non-zero value, such as USDT.\\n */\\n function forceApprove(IERC20Upgradeable token, address spender, uint256 value) internal {\\n bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);\\n\\n if (!_callOptionalReturnBool(token, approvalCall)) {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));\\n _callOptionalReturn(token, approvalCall);\\n }\\n }\\n\\n /**\\n * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.\\n * Revert on invalid signature.\\n */\\n function safePermit(\\n IERC20PermitUpgradeable token,\\n address owner,\\n address spender,\\n uint256 value,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) internal {\\n uint256 nonceBefore = token.nonces(owner);\\n token.permit(owner, spender, value, deadline, v, r, s);\\n uint256 nonceAfter = token.nonces(owner);\\n require(nonceAfter == nonceBefore + 1, \\\"SafeERC20: permit did not succeed\\\");\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20Upgradeable token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n require(returndata.length == 0 || abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n *\\n * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.\\n */\\n function _callOptionalReturnBool(IERC20Upgradeable token, bytes memory data) private returns (bool) {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false\\n // and not revert is the subcall reverts.\\n\\n (bool success, bytes memory returndata) = address(token).call(data);\\n return\\n success && (returndata.length == 0 || abi.decode(returndata, (bool))) && AddressUpgradeable.isContract(address(token));\\n }\\n}\\n\",\"keccak256\":\"0x23b997be73d3dd46885262704f0f8cfc7273fdadfe303d37969a9561373972b5\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary AddressUpgradeable {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n *\\n * Furthermore, `isContract` will also return true if the target contract within\\n * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,\\n * which only has an effect at the end of a transaction.\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\\n *\\n * _Available since v4.8._\\n */\\n function verifyCallResultFromTarget(\\n address target,\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n if (success) {\\n if (returndata.length == 0) {\\n // only check isContract if the call was successful and the return data is empty\\n // otherwise we already know that it was a contract\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n }\\n return returndata;\\n } else {\\n _revert(returndata, errorMessage);\\n }\\n }\\n\\n /**\\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason or using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n _revert(returndata, errorMessage);\\n }\\n }\\n\\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n /// @solidity memory-safe-assembly\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n}\\n\",\"keccak256\":\"0x9c80f545915582e63fe206c6ce27cbe85a86fc10b9cd2a0e8c9488fb7c2ee422\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract ContextUpgradeable is Initializable {\\n function __Context_init() internal onlyInitializing {\\n }\\n\\n function __Context_init_unchained() internal onlyInitializing {\\n }\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[50] private __gap;\\n}\\n\",\"keccak256\":\"0x75097e35253e7fb282ee4d7f27a80eaacfa759923185bf17302a89cbc059c5ef\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/IAccessControl.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev External interface of AccessControl declared to support ERC165 detection.\\n */\\ninterface IAccessControl {\\n /**\\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\\n *\\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\\n * {RoleAdminChanged} not being emitted signaling this.\\n *\\n * _Available since v3.1._\\n */\\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\\n\\n /**\\n * @dev Emitted when `account` is granted `role`.\\n *\\n * `sender` is the account that originated the contract call, an admin role\\n * bearer except when using {AccessControl-_setupRole}.\\n */\\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Emitted when `account` is revoked `role`.\\n *\\n * `sender` is the account that originated the contract call:\\n * - if using `revokeRole`, it is the admin role bearer\\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\\n */\\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Returns `true` if `account` has been granted `role`.\\n */\\n function hasRole(bytes32 role, address account) external view returns (bool);\\n\\n /**\\n * @dev Returns the admin role that controls `role`. See {grantRole} and\\n * {revokeRole}.\\n *\\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\\n */\\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function grantRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function revokeRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from the calling account.\\n *\\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\\n * purpose is to provide a mechanism for accounts to lose their privileges\\n * if they are compromised (such as when a trusted device is misplaced).\\n *\\n * If the calling account had been granted `role`, emits a {RoleRevoked}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must be `account`.\\n */\\n function renounceRole(bytes32 role, address account) external;\\n}\\n\",\"keccak256\":\"0x59ce320a585d7e1f163cd70390a0ef2ff9cec832e2aa544293a00692465a7a57\",\"license\":\"MIT\"},\"@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\nimport \\\"@openzeppelin/contracts/access/IAccessControl.sol\\\";\\n\\n/**\\n * @title IAccessControlManagerV8\\n * @author Venus\\n * @notice Interface implemented by the `AccessControlManagerV8` contract.\\n */\\ninterface IAccessControlManagerV8 is IAccessControl {\\n function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external;\\n\\n function revokeCallPermission(\\n address contractAddress,\\n string calldata functionSig,\\n address accountToRevoke\\n ) external;\\n\\n function isAllowedToCall(address account, string calldata functionSig) external view returns (bool);\\n\\n function hasPermission(\\n address account,\\n address contractAddress,\\n string calldata functionSig\\n ) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xaa29b098440d0b3a131c5ecdf25ce548790c1b5ac7bf9b5c0264b6af6f7a1e0b\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\ninterface IDeviationBoundedOracle {\\n // --- Enums ---\\n\\n /// @notice Identifies whether a price bound is a minimum or maximum\\n enum PriceBoundType {\\n MIN,\\n MAX\\n }\\n\\n /// @notice Identifies which keeper action a single syncPriceBoundsAndProtections item performs\\n enum KeeperAction {\\n SetMinPrice,\\n SetMaxPrice,\\n ExitProtectionMode\\n }\\n\\n // --- Structs ---\\n\\n /// @notice Per-asset protection state tracking the min/max price window\\n struct MarketProtectionState {\\n /// @notice Lowest price observed in the current window (packed with maxPrice in one slot)\\n uint128 minPrice;\\n /// @notice Highest price observed in the current window\\n uint128 maxPrice;\\n /// @notice Whether protected price is currently being used\\n bool currentlyUsingProtectedPrice;\\n /// @notice Whether this market is whitelisted for bounded pricing\\n bool isBoundedPricingEnabled;\\n /// @notice Timestamp of the last protection trigger \\u2014 reset on every trigger\\n uint64 lastProtectionTriggeredAt;\\n /// @notice Minimum time protection stays active after last trigger\\n uint64 cooldownPeriod;\\n /// @notice The underlying asset address, used to verify initialization\\n address asset;\\n /// @notice Entry deviation threshold (mantissa, e.g. 0.1667e18 = 16.67%); packed with resetThreshold\\n uint128 triggerThreshold;\\n /// @notice Exit threshold (mantissa); window must converge below this for protection to be disabled\\n uint128 resetThreshold;\\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\\n bool cachingEnabled;\\n }\\n\\n /// @notice One item in an syncPriceBoundsAndProtections payload\\n /// @dev `value` is interpreted per-action: the new bound price for SetMinPrice / SetMaxPrice, ignored for ExitProtectionMode\\n struct KeeperActionItem {\\n address asset;\\n KeeperAction action;\\n uint256 value;\\n }\\n\\n /// @notice One item in a setTokenConfigs payload\\n struct TokenConfigInput {\\n /// @notice The underlying asset address\\n address asset;\\n /// @notice Minimum time protection stays active after the last trigger (seconds)\\n uint64 cooldownPeriod;\\n /// @notice Entry deviation threshold (mantissa). Must be between 5% and 50%.\\n uint256 triggerThreshold;\\n /// @notice Exit deviation threshold (mantissa). Must be non-zero and below triggerThreshold.\\n uint256 resetThreshold;\\n /// @notice Whether to enable bounded pricing immediately upon initialization\\n bool enableBoundedPricing;\\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\\n bool enableCaching;\\n }\\n\\n // --- Events ---\\n\\n /// @notice Emitted when protection is initialized for an asset\\n event ProtectionInitialized(\\n address indexed asset,\\n uint128 minPrice,\\n uint128 maxPrice,\\n uint64 cooldownPeriod,\\n uint256 triggerThreshold\\n );\\n\\n /// @notice Emitted when protection mode is triggered for an asset\\n event ProtectionTriggered(address indexed asset, uint256 spotPrice, uint128 minPrice, uint128 maxPrice);\\n\\n /// @notice Emitted when protection mode is disabled for an asset\\n event ProtectionModeExited(address indexed asset);\\n\\n /// @notice Emitted when the keeper updates the minimum price for an asset\\n event MinPriceUpdated(address indexed asset, uint128 oldMin, uint128 newMin);\\n\\n /// @notice Emitted when the keeper updates the maximum price for an asset\\n event MaxPriceUpdated(address indexed asset, uint128 oldMax, uint128 newMax);\\n\\n /// @notice Emitted when the entry threshold is updated for an asset\\n event TriggerThresholdSet(address indexed asset, uint256 oldThreshold, uint256 newThreshold);\\n\\n /// @notice Emitted when the exit threshold is updated for an asset\\n event ResetThresholdSet(address indexed asset, uint256 oldExitThreshold, uint256 newExitThreshold);\\n\\n /// @notice Emitted when the cooldown period is updated for an asset\\n event CooldownPeriodSet(address indexed asset, uint64 oldCooldown, uint64 newCooldown);\\n\\n /// @notice Emitted when an asset's whitelist status changes\\n event BoundedPricingWhitelistUpdated(address indexed asset, bool whitelisted);\\n\\n /// @notice Emitted when the per-asset transient caching flag is toggled\\n event CachingEnabledUpdated(address indexed asset, bool oldEnabled, bool newEnabled);\\n\\n // --- Errors ---\\n\\n /// @notice Thrown when trying to use or update protection for an asset that has not been initialized\\n error MarketNotInitialized(address asset);\\n\\n /// @notice Thrown when trying to initialize an already initialized market\\n error MarketAlreadyInitialized(address asset);\\n\\n /// @notice Thrown when trying to disable protection that is not active\\n error ProtectedPriceInactive(address asset);\\n\\n /// @notice Thrown when trying to disable protection before cooldown has elapsed\\n error CooldownNotElapsed(address asset, uint64 lastProtectionTriggeredAt, uint64 cooldownPeriod);\\n\\n /// @notice Thrown when trying to disable protection before price range has converged\\n error PriceRangeNotConverged(address asset, uint256 currentRangeRatio, uint256 resetThreshold);\\n\\n /// @notice Thrown when keeper tries to set minPrice above current spot\\n error InvalidMinPrice(address asset, uint128 newMin, uint256 currentSpot);\\n\\n /// @notice Thrown when keeper tries to set maxPrice below current spot\\n error InvalidMaxPrice(address asset, uint128 newMax, uint256 currentSpot);\\n\\n /// @notice Thrown when threshold is set below the minimum allowed value\\n error ThresholdBelowMinimum(uint256 threshold, uint256 minimum);\\n\\n /// @notice Thrown when threshold is set above the maximum allowed value\\n error ThresholdAboveMaximum(uint256 threshold, uint256 maximum);\\n\\n /// @notice Thrown when a price exceeds uint128 max\\n error PriceExceedsUint128(uint256 price);\\n\\n /// @notice Thrown when a zero price is provided where a non-zero price is required\\n error ZeroPriceNotAllowed();\\n\\n /// @notice Thrown when trying to initialize protection for VAI\\n error VAINotAllowed();\\n\\n /// @notice Thrown when trying to disable bounded pricing for an asset while protection is active\\n error ProtectedPriceActive(address asset);\\n\\n /// @notice Thrown when the lengths of the arrays are not equal\\n error InvalidArrayLength();\\n\\n /// @notice Thrown when the exit threshold is set at or above the trigger threshold\\n error InvalidResetThreshold(uint256 resetThreshold);\\n\\n /// @notice Thrown when an syncPriceBoundsAndProtections item carries an unsupported action enum value\\n error InvalidKeeperAction(uint8 action);\\n\\n // --- Non-view price functions (update window + trigger protection) ---\\n\\n /**\\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice);\\n\\n /**\\n * @notice Gets the bounded debt price for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice);\\n\\n /**\\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice);\\n\\n // --- State update (call before view price reads to populate transient cache) ---\\n\\n /**\\n * @notice Updates the protection state for a given vToken, caching the resolved collateral and debt prices\\n * @dev Called by PolicyFacet before liquidity calculations so subsequent view price\\n * reads in the same transaction are served from transient storage. The transient\\n * cache is only populated when the asset's `cachingEnabled` flag is `true`.\\n * @param vToken vToken address\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function updateProtectionState(address vToken) external;\\n\\n // --- View price functions (read stored/cached state only) ---\\n\\n /**\\n * @notice Gets the bounded collateral price for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return price The bounded collateral price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 price);\\n\\n /**\\n * @notice Gets the bounded debt price for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return price The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 price);\\n\\n /**\\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice);\\n\\n // --- Keeper functions ---\\n\\n /**\\n * @notice Updates the minimum price in the rolling window for a given asset\\n * @param asset The underlying asset address\\n * @param newMin The new minimum price; must be at or below the current spot and below maxPrice\\n * @custom:access Only authorized keeper addresses\\n * @custom:error ZeroPriceNotAllowed if newMin is zero\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error InvalidMinPrice if newMin exceeds the current spot or is at or above maxPrice\\n * @custom:event MinPriceUpdated\\n */\\n function updateMinPrice(address asset, uint128 newMin) external;\\n\\n /**\\n * @notice Updates the maximum price in the rolling window for a given asset\\n * @param asset The underlying asset address\\n * @param newMax The new maximum price; must be at or above the current spot and above minPrice\\n * @custom:access Only authorized keeper addresses\\n * @custom:error ZeroPriceNotAllowed if newMax is zero\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error InvalidMaxPrice if newMax is below the current spot or is at or below minPrice\\n * @custom:event MaxPriceUpdated\\n */\\n function updateMaxPrice(address asset, uint128 newMax) external;\\n\\n /**\\n * @notice Exits protection mode for a given asset once conditions are met\\n * @param asset The underlying asset address\\n * @custom:access Only authorized monitor/keeper addresses\\n * @custom:error ProtectedPriceInactive if protection is not currently active\\n * @custom:error CooldownNotElapsed if the cooldown period has not elapsed since the last trigger\\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\\n * @custom:event ProtectionModeExited\\n */\\n function exitProtectionMode(address asset) external;\\n\\n /**\\n * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\\n * @dev Each item is processed in array order; any item revert rolls back the whole batch.\\n * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode.\\n * Empty `actions` is a no-op success.\\n * @param actions The list of keeper actions to apply\\n * @custom:access Only authorized keeper addresses\\n * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value\\n * @custom:error PriceExceedsUint128 if a SetMin/SetMax item value overflows uint128\\n * @custom:error ZeroPriceNotAllowed if a SetMin/SetMax item value is zero\\n * @custom:error MarketNotInitialized if any referenced asset has not been initialized\\n * @custom:error InvalidMinPrice if a SetMinPrice item violates the spot/maxPrice constraints\\n * @custom:error InvalidMaxPrice if a SetMaxPrice item violates the spot/minPrice constraints\\n * @custom:error ProtectedPriceInactive if an ExitProtectionMode item targets an asset whose protection is not active\\n * @custom:error CooldownNotElapsed if an ExitProtectionMode item is submitted before cooldown elapsed\\n * @custom:error PriceRangeNotConverged if an ExitProtectionMode item is submitted before window convergence\\n * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\\n */\\n function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external;\\n\\n // --- Admin functions (governance-gated) ---\\n\\n /**\\n * @notice Initializes protection parameters for a new asset\\n * @dev Seeds the initial min/max window from the current ResilientOracle spot price,\\n * confirming the oracle is live for this asset before it is listed.\\n * @param tokenConfig_ Token config input for the asset\\n * @custom:access Only Governance\\n * @custom:error ZeroAddressNotAllowed if asset is the zero address\\n * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero\\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\\n * @custom:error VAINotAllowed if asset is the VAI token\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event ProtectionInitialized\\n * @custom:event BoundedPricingWhitelistUpdated\\n */\\n function setTokenConfig(TokenConfigInput calldata tokenConfig_) external;\\n\\n /**\\n * @notice Batch-initializes protection parameters for multiple assets in a single transaction\\n * @param tokenConfigs_ Array of token config inputs, one per asset\\n * @custom:access Only Governance\\n * @custom:error InvalidArrayLength if the input array is empty\\n * @custom:error ZeroAddressNotAllowed if any asset is the zero address\\n * @custom:error ZeroValueNotAllowed if any cooldownPeriod, triggerThreshold, or resetThreshold is zero\\n * @custom:error MarketAlreadyInitialized if any asset has already been initialized\\n * @custom:error ThresholdBelowMinimum if any triggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if any triggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if any resetThreshold is at or above its triggerThreshold\\n * @custom:error VAINotAllowed if any asset is the VAI token\\n * @custom:error PriceExceedsUint128 if the spot price for any asset overflows uint128\\n * @custom:event ProtectionInitialized for each asset\\n * @custom:event BoundedPricingWhitelistUpdated for each asset\\n */\\n function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external;\\n\\n /**\\n * @notice Sets the cooldown period for an asset\\n * @param asset The underlying asset address\\n * @param newCooldown The new cooldown period in seconds; must be non-zero\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:event CooldownPeriodSet\\n */\\n function setCooldownPeriod(address asset, uint64 newCooldown) external;\\n\\n /**\\n * @notice Sets the trigger and reset thresholds for an asset\\n * @param asset The underlying asset address\\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\\n * @custom:event TriggerThresholdSet if the trigger threshold changed\\n * @custom:event ResetThresholdSet if the reset threshold changed\\n */\\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external;\\n\\n /**\\n * @notice Sets whether bounded pricing is enabled for an asset\\n * @param asset The underlying asset address\\n * @param enabled Whether bounded pricing should be enabled for the asset\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\\n * @custom:event BoundedPricingWhitelistUpdated\\n */\\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external;\\n\\n /**\\n * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset\\n * @dev When disabled, each view/non-view price call recomputes bounded prices from the\\n * live spot instead of reading or writing the transient slots. The initial value is\\n * set via the `enableCaching` argument of `setTokenConfig`.\\n * @param asset The underlying asset address\\n * @param enabled Whether transient caching is enabled for this asset\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:event CachingEnabledUpdated\\n */\\n function setCachingEnabled(address asset, bool enabled) external;\\n\\n // --- View helpers ---\\n\\n /**\\n * @notice Returns the full protection state for an asset\\n * @param asset The underlying asset address\\n * @return minPrice Lowest price observed in the current window\\n * @return maxPrice Highest price observed in the current window\\n * @return currentlyUsingProtectedPrice Whether protected price is currently active\\n * @return isBoundedPricingEnabled Whether the asset is whitelisted for bounded pricing\\n * @return lastProtectionTriggeredAt Timestamp of the last protection trigger\\n * @return cooldownPeriod Minimum time protection stays active after last trigger\\n * @return assetAddr The underlying asset address stored in the struct\\n * @return triggerThreshold Entry deviation threshold (mantissa) that activates protection\\n * @return resetThreshold Exit deviation threshold (mantissa) below which protection can be disabled\\n * @return cachingEnabled Whether transient caching of the bounded pair is enabled for the asset\\n */\\n function assetProtectionConfig(\\n address asset\\n )\\n external\\n view\\n returns (\\n uint128 minPrice,\\n uint128 maxPrice,\\n bool currentlyUsingProtectedPrice,\\n bool isBoundedPricingEnabled,\\n uint64 lastProtectionTriggeredAt,\\n uint64 cooldownPeriod,\\n address assetAddr,\\n uint128 triggerThreshold,\\n uint128 resetThreshold,\\n bool cachingEnabled\\n );\\n\\n /**\\n * @notice Checks if an asset is whitelisted for bounded pricing\\n * @param asset The underlying asset address\\n * @return True if the asset is whitelisted\\n */\\n function isBoundedPricingEnabled(address asset) external view returns (bool);\\n\\n /**\\n * @notice Checks if the asset is currently using the protected (bounded) price\\n * @param asset The underlying asset address\\n * @return True if the asset is currently using the protected price instead of spot\\n */\\n function currentlyUsingProtectedPrice(address asset) external view returns (bool);\\n\\n /**\\n * @notice Checks if protection can be exited for a given asset\\n * @param asset The underlying asset address\\n * @return True if both the cooldown has elapsed and the price range has converged below the exit threshold\\n */\\n function canExitProtection(address asset) external view returns (bool);\\n\\n /**\\n * @notice Returns the initialized asset at the given index (auto-generated array getter)\\n * @param index Array index\\n * @return The asset address at the given index\\n */\\n function allAssets(uint256 index) external view returns (address);\\n\\n /**\\n * @notice Returns all currently whitelisted asset addresses\\n * @return result Array of whitelisted asset addresses\\n */\\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory result);\\n\\n /**\\n * @notice Returns all asset addresses that have ever been initialized\\n * @return Array of all initialized asset addresses\\n */\\n function getInitializedAssets() external view returns (address[] memory);\\n\\n /**\\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the keeper deadband\\n * @param assets Array of asset addresses to check\\n * @param proposedMins Keeper's proposed window minimum prices\\n * @param proposedMaxs Keeper's proposed window maximum prices\\n * @return needsMinUpdate Whether minPrice drift exceeds the deadband for each asset\\n * @return needsMaxUpdate Whether maxPrice drift exceeds the deadband for each asset\\n * @custom:error InvalidArrayLength if the input array lengths do not match\\n */\\n function checkAndGetWindowDrift(\\n address[] calldata assets,\\n uint128[] calldata proposedMins,\\n uint128[] calldata proposedMaxs\\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate);\\n}\\n\",\"keccak256\":\"0xe223d88c17c0d752fdb33fa223b63ce124a798512f3d69f3800e9508e7dff931\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\ninterface OracleInterface {\\n function getPrice(address asset) external view returns (uint256);\\n}\\n\\ninterface ResilientOracleInterface is OracleInterface {\\n function updatePrice(address vToken) external;\\n\\n function updateAssetPrice(address asset) external;\\n\\n function getUnderlyingPrice(address vToken) external view returns (uint256);\\n}\\n\\ninterface BoundValidatorInterface {\\n function validatePriceWithAnchorPrice(\\n address asset,\\n uint256 reporterPrice,\\n uint256 anchorPrice\\n ) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xd3bbb7c9eef19e8f467342df6034ef95399a00964646fb8c82b438968ae3a8c0\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/solidity-utilities/contracts/constants.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\n/// @dev Base unit for computations, usually used in scaling (multiplications, divisions)\\nuint256 constant EXP_SCALE = 1e18;\\n\\n/// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions\\nuint256 constant MANTISSA_ONE = EXP_SCALE;\\n\\n/// @dev The approximate number of seconds per year\\nuint256 constant SECONDS_PER_YEAR = 31_536_000;\\n\",\"keccak256\":\"0x14de93ead464da249af31bea0e3bcfb62ec693bea3475fb4d90f055ac81dc5eb\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/solidity-utilities/contracts/validators.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\n/// @notice Thrown if the supplied address is a zero address where it is not allowed\\nerror ZeroAddressNotAllowed();\\n\\n/// @notice Thrown if the supplied value is 0 where it is not allowed\\nerror ZeroValueNotAllowed();\\n\\n/// @notice Checks if the provided address is nonzero, reverts otherwise\\n/// @param address_ Address to check\\n/// @custom:error ZeroAddressNotAllowed is thrown if the provided address is a zero address\\nfunction ensureNonzeroAddress(address address_) pure {\\n if (address_ == address(0)) {\\n revert ZeroAddressNotAllowed();\\n }\\n}\\n\\n/// @notice Checks if the provided value is nonzero, reverts otherwise\\n/// @param value_ Value to check\\n/// @custom:error ZeroValueNotAllowed is thrown if the provided value is 0\\nfunction ensureNonzeroValue(uint256 value_) pure {\\n if (value_ == 0) {\\n revert ZeroValueNotAllowed();\\n }\\n}\\n\",\"keccak256\":\"0xdb88e14d50dd21889ca3329d755673d022c47e8da005b6a545c7f69c2c4b7b86\",\"license\":\"BSD-3-Clause\"},\"contracts/BStock/BStockLiquidator.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { IERC20Upgradeable } from \\\"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\\\";\\nimport { SafeERC20Upgradeable } from \\\"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\\\";\\nimport { Ownable2StepUpgradeable } from \\\"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\\\";\\nimport { ReentrancyGuardUpgradeable } from \\\"@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol\\\";\\nimport { ensureNonzeroAddress } from \\\"@venusprotocol/solidity-utilities/contracts/validators.sol\\\";\\n\\nimport { VToken } from \\\"../Tokens/VTokens/VToken.sol\\\";\\nimport { IFlashLoanReceiver } from \\\"../FlashLoan/interfaces/IFlashLoanReceiver.sol\\\";\\nimport { IWBNB } from \\\"../external/IWBNB.sol\\\";\\n\\n// Shared Venus interfaces: IComptroller (Core diamond \\u2014 liquidator gate + flash loan), IVToken\\n// (flash-loan asset array element), and ILiquidator (the pool-wide Venus Liquidator gate that pulls\\n// the repay and returns our share of the seized collateral).\\nimport { IComptroller, IVToken, ILiquidator } from \\\"../InterfacesV8.sol\\\";\\nimport { IBStockLiquidator } from \\\"./IBStockLiquidator.sol\\\";\\n\\n/**\\n * @title BStockLiquidator\\n * @author Venus\\n * @notice Atomic backstop liquidator for bStock (ERC-8056 tokenized stock) collateral.\\n *\\n * In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken,\\n * redeems it to the raw bStock, and sells that bStock to the debt asset in one or two hops. Hop 1\\n * always sells bStock through the Native RFQ router using a pre-fetched, MM-signed firm-quote\\n * `txRequest`. For USDT debt that single hop lands the debt asset directly. For non-USDT debt an\\n * OPTIONAL hop 2 (Native quotes bStock->USDT only) converts the USDT to the debt asset through a\\n * second allowlisted router (an AMM/aggregator). Because seize and sell happen in the same tx there\\n * is no price-drift window, and the realized debt-asset amount must clear `minOut` or the whole call\\n * reverts \\u2014 the protocol never ends up holding the RFQ-only asset or the intermediate.\\n *\\n * Two funding modes share the same core (`_liquidate`):\\n * - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance.\\n * - FLASH: the debt asset is flash-borrowed from Venus (`Comptroller.executeFlashLoan`) and repaid\\n * (+ premium) within the same tx; no capital is locked in the contract.\\n *\\n * Native BNB debt (vBNB): supported in both modes with WBNB as the debt-accounting token. The repay\\n * must be native BNB, so exactly the repay amount of WBNB is unwrapped and forwarded to the gate's\\n * payable path; the two-hop swap lands WBNB (bStock->USDT->WBNB) and `minOut` is measured in WBNB\\n * (1:1 with BNB). FLASH mode borrows from vWBNB, NOT vBNB: vBNB cannot be flash-repaid (its\\n * `doTransferIn` requires `msg.value`), whereas vWBNB's underlying is a plain ERC20.\\n *\\n * Ownership / scope: this is Venus's OWN backstop tool, NOT a public utility \\u2014 `liquidate` and\\n * `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock\\n * liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus\\n * path with their own funds and their own offload. This contract is intentionally gated because it\\n * custodies funds (debt-asset inventory / flash principal) and forwards a CALLER-SUPPLIED calldata blob to\\n * an external router \\u2014 the swap's recipient (`to`) lives inside that calldata, so an open entrypoint\\n * would let anyone route the proceeds to themselves and drain the contract. The router allowlist and\\n * `minOut` bound the blast radius but cannot replace operator-gating.\\n *\\n * Security model:\\n * - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator).\\n * - BOTH swap targets (`router` and, when set, `router2`) must be allowlisted (`isRouter`) \\u2014 defends\\n * the low-level `router.call(swapCalldata)` on each hop.\\n * - the approval to each router is the exact amount being sold on that hop, reset to 0 afterwards\\n * (bStock on hop 1; the measured intermediate balance delta on hop 2, so pre-existing inventory\\n * is never exposed).\\n * - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started).\\n * - the realized debt-asset amount must clear `minOut` or the tx reverts.\\n *\\n * Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`,\\n * which is always configured on the networks this contract targets. While it is set, a direct\\n * `vToken.liquidateBorrow` from an arbitrary caller reverts UNAUTHORIZED, so this contract reads the gate\\n * at call time and routes the repay through that Venus Liquidator (the permissionless entry anyone may\\n * call), reverting if the gate is ever unset. Routing through the gate needs no governance change, and no\\n * other Core market is affected. Note: setting THIS contract as `liquidatorContract` is NOT an option \\u2014\\n * the gate is pool-wide, so every other market's liquidations would be forced through here.\\n */\\ncontract BStockLiquidator is\\n Ownable2StepUpgradeable,\\n ReentrancyGuardUpgradeable,\\n IBStockLiquidator,\\n IFlashLoanReceiver\\n{\\n using SafeERC20Upgradeable for IERC20Upgradeable;\\n\\n /// @notice Core Comptroller (diamond): reads the liquidation gate and provides the flash loan\\n /// via `executeFlashLoan`.\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n IComptroller public immutable comptroller;\\n\\n /// @notice The native BNB market (vBNB). A debt equal to this address is settled with native BNB:\\n /// WBNB is the debt-accounting token, and only the repay amount is unwrapped (see `_liquidate`).\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n address public immutable vBNB;\\n\\n /// @notice The WBNB market (vWBNB). BNB debt is flash-funded from here, NOT from vBNB: vBNB cannot be\\n /// flash-repaid (its `doTransferIn` needs `msg.value`), whereas vWBNB's underlying is a plain\\n /// ERC20 repaid via `transferFrom`.\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n IVToken public immutable vWBNB;\\n\\n /// @notice WBNB token: the debt-accounting asset for BNB debt, unwrapped to native BNB for the repay.\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n IWBNB public immutable wbnb;\\n\\n /// @notice Addresses allowed to trigger a liquidation.\\n mapping(address => bool) public isOperator;\\n\\n /// @notice Routers allowed as the swap target (defends the low-level call).\\n mapping(address => bool) public isRouter;\\n\\n /// @dev Reserved storage to allow new state variables in future upgrades without layout clashes.\\n uint256[50] private __gap;\\n\\n modifier onlyOperator() {\\n if (msg.sender != owner() && !isOperator[msg.sender]) revert NotOperator();\\n _;\\n }\\n\\n /// @notice Constructor for the implementation contract. Sets the immutables and locks initializers.\\n /// @param comptroller_ Venus Core Comptroller (diamond) \\u2014 gates liquidation and provides flash loans.\\n /// @param vBNB_ Native BNB market; a debt equal to this address is settled in native BNB.\\n /// @param vWBNB_ WBNB market; the flash-borrow source for BNB debt (vBNB itself cannot be flash-repaid).\\n /// @param wbnb_ WBNB token; the debt-accounting asset for BNB debt, unwrapped for the native repay.\\n /// @custom:oz-upgrades-unsafe-allow constructor\\n constructor(IComptroller comptroller_, address vBNB_, IVToken vWBNB_, IWBNB wbnb_) {\\n ensureNonzeroAddress(address(comptroller_));\\n ensureNonzeroAddress(vBNB_);\\n ensureNonzeroAddress(address(vWBNB_));\\n ensureNonzeroAddress(address(wbnb_));\\n comptroller = comptroller_;\\n vBNB = vBNB_;\\n vWBNB = vWBNB_;\\n wbnb = wbnb_;\\n _disableInitializers();\\n }\\n\\n /// @notice Initializes the proxy: sets the owner and the reentrancy guard.\\n /// @param initialOwner Address that owns the contract (admin + default operator).\\n function initialize(address initialOwner) external initializer {\\n ensureNonzeroAddress(initialOwner);\\n __Ownable2Step_init();\\n __ReentrancyGuard_init();\\n _transferOwnership(initialOwner);\\n }\\n\\n /// @notice Accept native BNB. The expected inflow is `wbnb.withdraw` during a BNB liquidation (the\\n /// unwrapped repay is forwarded to the gate in the same call, so no BNB is retained on the\\n /// happy path). Left permissive (not restricted to `wbnb`) both to keep the receive body\\n /// minimal for WBNB's 2300-gas `.transfer` stipend and to tolerate a stray transfer or a\\n /// future gate refund \\u2014 any such balance is recoverable via `sweepNative`.\\n receive() external payable {}\\n\\n // --------------------------------------------------------------------- //\\n // Admin //\\n // --------------------------------------------------------------------- //\\n\\n /// @inheritdoc IBStockLiquidator\\n function setOperator(address operator, bool allowed) external override onlyOwner {\\n ensureNonzeroAddress(operator);\\n isOperator[operator] = allowed;\\n emit OperatorSet(operator, allowed);\\n }\\n\\n /// @inheritdoc IBStockLiquidator\\n function setRouter(address router, bool allowed) external override onlyOwner {\\n ensureNonzeroAddress(router);\\n isRouter[router] = allowed;\\n emit RouterSet(router, allowed);\\n }\\n\\n /// @inheritdoc IBStockLiquidator\\n function sweep(address token, address to, uint256 amount) external override onlyOwner {\\n ensureNonzeroAddress(token);\\n ensureNonzeroAddress(to);\\n IERC20Upgradeable(token).safeTransfer(to, amount);\\n emit Swept(token, to, amount);\\n }\\n\\n /// @inheritdoc IBStockLiquidator\\n function sweepNative(address to, uint256 amount) external override onlyOwner {\\n ensureNonzeroAddress(to);\\n (bool ok, ) = to.call{ value: amount }(\\\"\\\");\\n if (!ok) revert NativeTransferFailed();\\n emit SweptNative(to, amount);\\n }\\n\\n // --------------------------------------------------------------------- //\\n // INVENTORY mode //\\n // --------------------------------------------------------------------- //\\n\\n /// @inheritdoc IBStockLiquidator\\n /// @dev INVENTORY mode spends the contract's OWN debt-asset capital, so \\u2014 unlike FLASH mode, where\\n /// `executeOperation` forces the swap proceeds to cover principal + premium \\u2014 there is no\\n /// built-in floor tying `debtOut` to `repayAmount`. This asymmetry is intentional: a repay can\\n /// legitimately out-cost its proceeds (e.g. the Venus Liquidator keeps a treasury cut of the\\n /// seized collateral, so proceeds land a few % under the repay). `minOut` IS the operator's\\n /// chosen loss floor for inventory mode \\u2014 set it to the lowest acceptable debt-asset return.\\n function liquidate(\\n LiquidationParams calldata params\\n ) external override onlyOperator nonReentrant returns (uint256 debtOut) {\\n _validateRouters(params.router, params.router2);\\n\\n if (params.minOut == 0) revert ZeroMinOut();\\n if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp);\\n uint256 seizedBStock;\\n (debtOut, seizedBStock) = _liquidate(params);\\n emit Liquidated(\\n params.borrower,\\n address(params.vBStock),\\n address(params.vDebt),\\n params.repayAmount,\\n seizedBStock,\\n debtOut,\\n false\\n );\\n }\\n\\n // --------------------------------------------------------------------- //\\n // FLASH mode //\\n // --------------------------------------------------------------------- //\\n\\n /// @inheritdoc IBStockLiquidator\\n function flashLiquidate(LiquidationParams calldata params) external override onlyOperator nonReentrant {\\n _validateRouters(params.router, params.router2);\\n\\n if (params.minOut == 0) revert ZeroMinOut();\\n if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp);\\n\\n // BNB debt is flash-funded from vWBNB (an ERC20 market), not vBNB: vBNB cannot be flash-repaid.\\n // The flashed WBNB is unwrapped to native BNB for the repay inside `_liquidate` (see `executeOperation`).\\n IVToken[] memory vTokens = new IVToken[](1);\\n vTokens[0] = (address(params.vDebt) == vBNB) ? vWBNB : IVToken(address(params.vDebt));\\n uint256[] memory amounts = new uint256[](1);\\n amounts[0] = params.repayAmount;\\n\\n comptroller.executeFlashLoan(\\n payable(address(this)),\\n payable(address(this)),\\n vTokens,\\n amounts,\\n abi.encode(params)\\n );\\n }\\n\\n /// @inheritdoc IFlashLoanReceiver\\n function executeOperation(\\n VToken[] calldata vTokens,\\n uint256[] calldata amounts,\\n uint256[] calldata premiums,\\n address initiator,\\n address /* onBehalf */,\\n bytes calldata param\\n ) external override returns (bool, uint256[] memory repayAmounts) {\\n if (msg.sender != address(comptroller)) revert OnlyComptroller();\\n // initiator == this proves the flash was started by our own flashLiquidate: the FlashLoanFacet\\n // passes msg.sender (the executeFlashLoan caller) as `initiator`, and only flashLiquidate calls it.\\n if (initiator != address(this)) revert BadInitiator(initiator);\\n\\n LiquidationParams memory params = abi.decode(param, (LiquidationParams));\\n // For BNB debt the flash is drawn from vWBNB, not vBNB (see `flashLiquidate`). Scoped so the\\n // temporary doesn't count against the stack depth of the rest of the function.\\n {\\n address expectedFlash = address(params.vDebt) == vBNB ? address(vWBNB) : address(params.vDebt);\\n if (address(vTokens[0]) != expectedFlash) revert WrongFlashAsset();\\n }\\n\\n // Repay was just funded by the flash loan; run the liquidation + swap.\\n (uint256 debtOut, uint256 seizedBStock) = _liquidate(params);\\n\\n // The swap proceeds alone MUST cover principal + premium. Without this, any debt-asset inventory\\n // held by the contract would silently backfill an underwater swap (a real loss), since the\\n // flash repayment is pulled from the total balance, not just the swap output.\\n repayAmounts = new uint256[](1);\\n repayAmounts[0] = amounts[0] + premiums[0];\\n if (debtOut < repayAmounts[0]) revert InsufficientOut(debtOut, repayAmounts[0]);\\n\\n // Approve the flashed vToken to pull back principal + premium. For BNB debt the flashed asset is\\n // WBNB (from vWBNB); the ternary short-circuits so `underlying()` is never called on vBNB (it has none).\\n IERC20Upgradeable(address(params.vDebt) == vBNB ? address(wbnb) : params.vDebt.underlying()).forceApprove(\\n address(vTokens[0]),\\n repayAmounts[0]\\n );\\n\\n emit Liquidated(\\n params.borrower,\\n address(params.vBStock),\\n address(params.vDebt),\\n params.repayAmount,\\n seizedBStock,\\n debtOut,\\n true\\n );\\n return (true, repayAmounts);\\n }\\n\\n // --------------------------------------------------------------------- //\\n // Core //\\n // --------------------------------------------------------------------- //\\n\\n /// @dev Pre-flight: every swap router must be allowlisted. `router2` is optional (single-hop when\\n /// zero) so it is only checked when set. Liquidatability itself is not pre-checked here \\u2014 Core's\\n /// `liquidateBorrowAllowed` already enforces it, and pre-checking shortfall would wrongly block\\n /// forced liquidations (which liquidate healthy accounts).\\n function _validateRouters(address router, address router2) private view {\\n if (!isRouter[router]) revert RouterNotAllowed(router);\\n if (router2 != address(0) && !isRouter[router2]) revert RouterNotAllowed(router2);\\n }\\n\\n /// @dev One swap hop: approve the exact `amount` to the allowlisted `router`, forward the opaque\\n /// calldata via a low-level call, then reset the approval to 0. The approval caps what the\\n /// router can pull; if the calldata sells less, the remainder stays as inventory.\\n function _swap(IERC20Upgradeable token, address router, bytes memory data, uint256 amount) private {\\n token.forceApprove(router, amount);\\n (bool ok, ) = router.call(data);\\n if (!ok) revert SwapFailed();\\n token.forceApprove(router, 0); // never leave a standing approval\\n }\\n\\n /**\\n * @dev The atomic sequence shared by both funding modes:\\n * approve debt -> liquidateBorrow (seize vBStock) -> redeem (raw bStock)\\n * -> swap bStock to the debt asset (one hop, or two via an intermediate) -> assert minOut.\\n * A `calldata` struct from `liquidate` is copied to memory on entry; `executeOperation`\\n * already holds it in memory (decoded from the flash callback).\\n * @param params Liquidation parameters.\\n * @return debtOut Debt-asset proceeds realized by the swap chain (reverts if below `minOut`).\\n * @return seizedBStock Raw bStock redeemed and sold (balance delta).\\n */\\n function _liquidate(LiquidationParams memory params) private returns (uint256 debtOut, uint256 seizedBStock) {\\n // A debt equal to `vBNB` is native BNB. vBNB has no `underlying()`, so the `isBnb` check MUST come\\n // first: the ternary short-circuits and `underlying()` is never evaluated for vBNB. WBNB is the\\n // debt-accounting token throughout (1:1 with BNB), so the whole swap/minOut path below is reused.\\n // KEEP THIS ORDER \\u2014 hoisting `underlying()` above the check reverts every BNB liquidation.\\n bool isBnb = address(params.vDebt) == vBNB;\\n // Native RFQ only quotes bStock->USDT, so a BNB debt is inherently two-hop (...->WBNB). Reject a\\n // single-hop BNB config up front instead of failing opaquely later on a zero WBNB delta.\\n if (isBnb && params.router2 == address(0)) revert InvalidIntermediate();\\n IERC20Upgradeable debt = isBnb\\n ? IERC20Upgradeable(address(wbnb))\\n : IERC20Upgradeable(params.vDebt.underlying());\\n IERC20Upgradeable bStock = IERC20Upgradeable(params.vBStock.underlying());\\n\\n // 1. Repay the borrow, seizing the bStock vToken to this contract.\\n // Core has a POOL-WIDE liquidator gate (`liquidatorContract`), which is always configured on\\n // the networks this contract targets. While it is set, a direct `vToken.liquidateBorrow`\\n // reverts UNAUTHORIZED, so we route the repay through that Venus Liquidator (it pulls our\\n // repay and sends us our share of the seized collateral; treasury keeps a cut). The seized\\n // amount is read as a BALANCE DELTA, so the Liquidator's cut is handled correctly. We guard\\n // against an unset gate so a misconfig fails loudly instead of silently no-op'ing a call to\\n // address(0) (a low-level call to a codeless address returns success).\\n uint256 vBefore = params.vBStock.balanceOf(address(this));\\n address gate = comptroller.liquidatorContract();\\n ensureNonzeroAddress(gate);\\n if (isBnb) {\\n // Unwrap EXACTLY the repay (WBNB held as inventory or drawn from the vWBNB flash) and forward\\n // native BNB to the gate's vBNB branch (`{value:}`). Only the repay portion is unwrapped, so\\n // pre-existing WBNB inventory is untouched; the swap proceeds below stay as WBNB. No approval\\n // is granted (value is forwarded), so there is no standing allowance to reset.\\n wbnb.withdraw(params.repayAmount);\\n ILiquidator(gate).liquidateBorrow{ value: params.repayAmount }(\\n address(params.vDebt),\\n params.borrower,\\n params.repayAmount,\\n params.vBStock\\n );\\n } else {\\n debt.forceApprove(gate, params.repayAmount);\\n ILiquidator(gate).liquidateBorrow(\\n address(params.vDebt),\\n params.borrower,\\n params.repayAmount,\\n params.vBStock\\n );\\n // Reset the gate approval: if the Liquidator pulled less than `repayAmount` (e.g. a close-factor\\n // cap), the remainder would otherwise linger as a standing allowance. Same invariant as `_swap`.\\n debt.forceApprove(gate, 0);\\n }\\n uint256 seizedV = params.vBStock.balanceOf(address(this)) - vBefore;\\n\\n // 2. Redeem the seized vBStock for raw bStock. Measure by DELTA so any pre-existing bStock\\n // (dust or a stray transfer) is excluded \\u2014 we only sell what this redeem actually returned.\\n uint256 rawBefore = bStock.balanceOf(address(this));\\n uint256 redeemErr = params.vBStock.redeem(seizedV);\\n if (redeemErr != 0) revert RedeemFailed(redeemErr);\\n seizedBStock = bStock.balanceOf(address(this)) - rawBefore;\\n\\n // 3. Sell the bStock to the debt asset (one hop, or two via an intermediate) and assert minOut.\\n // Extracted into `_sellToDebt` to keep this frame within the EVM stack limit.\\n debtOut = _sellToDebt(debt, bStock, seizedBStock, params);\\n }\\n\\n /**\\n * @dev Sell `seizedBStock` to the debt asset and enforce `minOut`. Single hop by default\\n * (bStock -> debt via the Native router). When `params.router2` is set, two hops\\n * (bStock -> intermediate -> debt): hop 1 sells bStock to the intermediate (USDT) via the\\n * Native router, hop 2 converts that intermediate to the debt asset via a second allowlisted\\n * router (AMM/aggregator). `minOut` is measured in the debt asset across the whole chain.\\n * @return debtOut Debt-asset proceeds (balance delta), reverting if below `minOut`.\\n */\\n function _sellToDebt(\\n IERC20Upgradeable debt,\\n IERC20Upgradeable bStock,\\n uint256 seizedBStock,\\n LiquidationParams memory params\\n ) private returns (uint256 debtOut) {\\n uint256 debtBefore = debt.balanceOf(address(this));\\n if (params.router2 == address(0)) {\\n _swap(bStock, params.router, params.swapCalldata, seizedBStock);\\n } else {\\n // The intermediate must be a real token distinct from both endpoints: if it equals `debt`,\\n // hop 1 would inflate the balance `debtBefore` snapshots against (breaking the proceeds\\n // delta); if it equals `bStock`, the hop-1 sell shrinks the balance and the midDelta\\n // subtraction underflows.\\n if (\\n params.intermediateToken == address(0) ||\\n params.intermediateToken == address(debt) ||\\n params.intermediateToken == address(bStock)\\n ) revert InvalidIntermediate();\\n\\n IERC20Upgradeable mid = IERC20Upgradeable(params.intermediateToken);\\n uint256 midBefore = mid.balanceOf(address(this));\\n _swap(bStock, params.router, params.swapCalldata, seizedBStock); // hop 1: bStock -> intermediate\\n // Only the hop-1 proceeds are sold onward; any pre-existing intermediate inventory is excluded.\\n uint256 midDelta = mid.balanceOf(address(this)) - midBefore;\\n _swap(mid, params.router2, params.swapCalldata2, midDelta); // hop 2: intermediate -> debt\\n }\\n\\n debtOut = debt.balanceOf(address(this)) - debtBefore;\\n if (debtOut < params.minOut) revert InsufficientOut(debtOut, params.minOut);\\n }\\n}\\n\",\"keccak256\":\"0x006270b08a8c02aa9b478175d604a8d8fc1772c49f7ca6b5fe1af513427cf183\",\"license\":\"BSD-3-Clause\"},\"contracts/BStock/IBStockLiquidator.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { IVBep20 } from \\\"../InterfacesV8.sol\\\";\\n\\n/// @title IBStockLiquidator\\n/// @author Venus\\n/// @notice External API, events, errors and parameter struct for {BStockLiquidator}.\\n/// @dev The flash-loan callback `executeOperation` is intentionally NOT part of this interface \\u2014 it is\\n/// declared by {IFlashLoanReceiver} (owned by the Core flash-loan subsystem) and implemented directly\\n/// by {BStockLiquidator}, keeping this interface free of the concrete `VToken` dependency.\\ninterface IBStockLiquidator {\\n /// @notice Parameters for a single liquidation.\\n /// @dev The swap can be one or two hops. When `router2 == address(0)` it is a single hop\\n /// (bStock -> debt) and `swapCalldata2` / `intermediateToken` are ignored \\u2014 behavior is\\n /// identical to the single-hop version. When `router2` is set it is two hops\\n /// (bStock -> `intermediateToken` -> debt): hop 1 sells bStock via `router`, hop 2 converts\\n /// the intermediate to the debt asset via `router2`. `minOut` is always the FINAL debt-asset\\n /// floor across the whole chain. `deadline` is a unix-timestamp expiry: the call reverts once\\n /// `block.timestamp` passes it, so a stale tx cannot settle against an expired quote.\\n struct LiquidationParams {\\n address borrower; // account to liquidate\\n IVBep20 vDebt; // borrowed market to repay (e.g. vUSDT)\\n IVBep20 vBStock; // bStock collateral market to seize (e.g. vTSLAB)\\n uint256 repayAmount; // debt underlying to repay (its own decimals)\\n address router; // hop-1 router = Native firm-quote txRequest.target (must be allowlisted)\\n bytes swapCalldata; // hop-1 calldata (MM-signed Native order): bStock -> intermediate (or -> debt if single-hop)\\n uint256 minOut; // minimum FINAL debt-asset amount the swap chain must yield, else revert\\n address router2; // hop-2 router (AMM/aggregator): intermediate -> debt; address(0) = single-hop\\n bytes swapCalldata2; // hop-2 calldata; the swap recipient inside it MUST be this contract\\n address intermediateToken; // token hop 1 outputs and hop 2 consumes (e.g. USDT); required when router2 set\\n uint256 deadline; // unix timestamp after which the call reverts; guards a stale tx sitting in the mempool\\n }\\n\\n /// @notice Emitted when an operator is allowlisted or removed.\\n event OperatorSet(address indexed operator, bool allowed);\\n\\n /// @notice Emitted when a swap router is allowlisted or removed.\\n event RouterSet(address indexed router, bool allowed);\\n\\n /// @notice Emitted on a successful liquidation.\\n /// @param borrower The liquidated account.\\n /// @param vBStock The seized bStock collateral market.\\n /// @param vDebt The repaid debt market.\\n /// @param repayAmount Debt underlying repaid.\\n /// @param seizedBStock Raw bStock redeemed and sold.\\n /// @param debtOut Debt-asset proceeds of the swap.\\n /// @param flash True if funded by a flash loan, false if from inventory.\\n event Liquidated(\\n address indexed borrower,\\n address indexed vBStock,\\n address indexed vDebt,\\n uint256 repayAmount,\\n uint256 seizedBStock,\\n uint256 debtOut,\\n bool flash\\n );\\n\\n /// @notice Emitted when the owner withdraws a token.\\n event Swept(address indexed token, address indexed to, uint256 amount);\\n\\n /// @notice Emitted when the owner withdraws stuck native BNB.\\n event SweptNative(address indexed to, uint256 amount);\\n\\n /// @notice Thrown when the caller is neither the owner nor an allowlisted operator.\\n error NotOperator();\\n\\n /// @notice Thrown when the supplied swap router is not allowlisted.\\n error RouterNotAllowed(address router);\\n\\n /// @notice Thrown when `vBStock.redeem` returns a non-zero error code.\\n error RedeemFailed(uint256 errCode);\\n\\n /// @notice Thrown when the low-level call to the router reverts.\\n error SwapFailed();\\n\\n /// @notice Thrown when swap proceeds are below `minOut`.\\n error InsufficientOut(uint256 got, uint256 minOut);\\n\\n /// @notice Thrown when `minOut` is zero: a liquidation must set a non-zero debt-asset floor,\\n /// else it would silently accept any proceeds (including zero).\\n error ZeroMinOut();\\n\\n /// @notice Thrown when a two-hop `intermediateToken` is zero, or equals the debt or bStock token.\\n error InvalidIntermediate();\\n\\n /// @notice Thrown when `executeOperation` is called by something other than the Comptroller.\\n error OnlyComptroller();\\n\\n /// @notice Thrown when the flash-loan initiator is not this contract.\\n error BadInitiator(address initiator);\\n\\n /// @notice Thrown when the flashed asset does not match `params.vDebt`.\\n error WrongFlashAsset();\\n\\n /// @notice Thrown when the call is submitted after `params.deadline`.\\n error DeadlineExpired(uint256 deadline, uint256 nowTs);\\n\\n /// @notice Thrown when a native BNB transfer (the `sweepNative` payout) fails.\\n error NativeTransferFailed();\\n\\n /// @notice Allow or disallow an address to trigger liquidations.\\n /// @param operator Address to allowlist or remove.\\n /// @param allowed True to allow, false to remove.\\n function setOperator(address operator, bool allowed) external;\\n\\n /// @notice Allow or disallow a router as the swap target (e.g. the Native router).\\n /// @param router Address to allowlist or remove.\\n /// @param allowed True to allow, false to remove.\\n function setRouter(address router, bool allowed) external;\\n\\n /// @notice Withdraw any token (profit, leftover inventory, stuck dust) to `to`.\\n /// @param token Token to withdraw.\\n /// @param to Recipient.\\n /// @param amount Amount to withdraw.\\n function sweep(address token, address to, uint256 amount) external;\\n\\n /// @notice Withdraw stuck native BNB (a stray transfer, or a gate refund) to `to`.\\n /// @param to Recipient.\\n /// @param amount Amount of native BNB to withdraw.\\n function sweepNative(address to, uint256 amount) external;\\n\\n /// @notice Liquidate using the contract's own debt-asset inventory.\\n /// @dev The contract must already hold >= `repayAmount` of `vDebt.underlying()`.\\n /// Profit (proceeds - repay) stays in the contract; withdraw it with `sweep`.\\n /// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final\\n /// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt).\\n /// @return debtOut Debt-asset proceeds realized by the swap chain.\\n function liquidate(LiquidationParams calldata params) external returns (uint256 debtOut);\\n\\n /// @notice Liquidate by flash-borrowing the repay amount from Venus, repaid (+ premium) in the same tx.\\n /// @dev Requires this contract to be `authorizedFlashLoan` in the Comptroller and `vDebt` flash-enabled.\\n /// Profit (proceeds - repay - premium) stays in the contract; withdraw it with `sweep`.\\n /// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final\\n /// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt).\\n function flashLiquidate(LiquidationParams calldata params) external;\\n}\\n\",\"keccak256\":\"0xdb76c41c7b9bc5e1f979b54c6623c28490ba0a9d1c09f8c2a5fae6cdd07b8ce9\",\"license\":\"BSD-3-Clause\"},\"contracts/Comptroller/ComptrollerInterface.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { ResilientOracleInterface } from \\\"@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol\\\";\\nimport { IDeviationBoundedOracle } from \\\"@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol\\\";\\n\\nimport { VToken } from \\\"../Tokens/VTokens/VToken.sol\\\";\\nimport { VAIControllerInterface } from \\\"../Tokens/VAI/VAIControllerInterface.sol\\\";\\nimport { WeightFunction } from \\\"./Diamond/interfaces/IFacetBase.sol\\\";\\n\\nenum Action {\\n MINT,\\n REDEEM,\\n BORROW,\\n REPAY,\\n SEIZE,\\n LIQUIDATE,\\n TRANSFER,\\n ENTER_MARKET,\\n EXIT_MARKET\\n}\\n\\ninterface ComptrollerInterface {\\n /// @notice Indicator that this is a Comptroller contract (for inspection)\\n function isComptroller() external pure returns (bool);\\n\\n /*** Assets You Are In ***/\\n\\n function enterMarkets(address[] calldata vTokens) external returns (uint[] memory);\\n\\n function exitMarket(address vToken) external returns (uint);\\n\\n /*** Policy Hooks ***/\\n\\n function mintAllowed(address vToken, address minter, uint mintAmount) external returns (uint);\\n\\n function mintVerify(address vToken, address minter, uint mintAmount, uint mintTokens) external;\\n\\n function redeemAllowed(address vToken, address redeemer, uint redeemTokens) external returns (uint);\\n\\n function redeemVerify(address vToken, address redeemer, uint redeemAmount, uint redeemTokens) external;\\n\\n function borrowAllowed(address vToken, address borrower, uint borrowAmount) external returns (uint);\\n\\n function borrowVerify(address vToken, address borrower, uint borrowAmount) external;\\n\\n function executeFlashLoan(\\n address payable onBehalf,\\n address payable receiver,\\n VToken[] calldata vTokens,\\n uint256[] calldata underlyingAmounts,\\n bytes calldata param\\n ) external;\\n\\n function repayBorrowAllowed(\\n address vToken,\\n address payer,\\n address borrower,\\n uint repayAmount\\n ) external returns (uint);\\n\\n function repayBorrowVerify(\\n address vToken,\\n address payer,\\n address borrower,\\n uint repayAmount,\\n uint borrowerIndex\\n ) external;\\n\\n function liquidateBorrowAllowed(\\n address vTokenBorrowed,\\n address vTokenCollateral,\\n address liquidator,\\n address borrower,\\n uint repayAmount\\n ) external returns (uint);\\n\\n function liquidateBorrowVerify(\\n address vTokenBorrowed,\\n address vTokenCollateral,\\n address liquidator,\\n address borrower,\\n uint repayAmount,\\n uint seizeTokens\\n ) external;\\n\\n function seizeAllowed(\\n address vTokenCollateral,\\n address vTokenBorrowed,\\n address liquidator,\\n address borrower,\\n uint seizeTokens\\n ) external returns (uint);\\n\\n function seizeVerify(\\n address vTokenCollateral,\\n address vTokenBorrowed,\\n address liquidator,\\n address borrower,\\n uint seizeTokens\\n ) external;\\n\\n function transferAllowed(address vToken, address src, address dst, uint transferTokens) external returns (uint);\\n\\n function transferVerify(address vToken, address src, address dst, uint transferTokens) external;\\n\\n /*** Liquidity/Liquidation Calculations ***/\\n\\n function liquidateCalculateSeizeTokens(\\n address vTokenBorrowed,\\n address vTokenCollateral,\\n uint repayAmount\\n ) external view returns (uint, uint);\\n\\n function liquidateCalculateSeizeTokens(\\n address borrower,\\n address vTokenBorrowed,\\n address vTokenCollateral,\\n uint repayAmount\\n ) external view returns (uint, uint);\\n\\n function setMintedVAIOf(address owner, uint amount) external returns (uint);\\n\\n function liquidateVAICalculateSeizeTokens(\\n address vTokenCollateral,\\n uint repayAmount\\n ) external view returns (uint, uint);\\n\\n function getXVSAddress() external view returns (address);\\n\\n function markets(address) external view returns (bool, uint, bool, uint, uint, uint96, bool);\\n\\n function oracle() external view returns (ResilientOracleInterface);\\n\\n function deviationBoundedOracle() external view returns (IDeviationBoundedOracle);\\n\\n function getAccountLiquidity(address) external view returns (uint, uint, uint);\\n\\n function getAssetsIn(address) external view returns (VToken[] memory);\\n\\n function claimVenus(address) external;\\n\\n function venusAccrued(address) external view returns (uint);\\n\\n function venusSupplySpeeds(address) external view returns (uint);\\n\\n function venusBorrowSpeeds(address) external view returns (uint);\\n\\n function getAllMarkets() external view returns (VToken[] memory);\\n\\n function venusSupplierIndex(address, address) external view returns (uint);\\n\\n function venusInitialIndex() external view returns (uint224);\\n\\n function venusBorrowerIndex(address, address) external view returns (uint);\\n\\n function venusBorrowState(address) external view returns (uint224, uint32);\\n\\n function venusSupplyState(address) external view returns (uint224, uint32);\\n\\n function approvedDelegates(address borrower, address delegate) external view returns (bool);\\n\\n function vaiController() external view returns (VAIControllerInterface);\\n\\n function protocolPaused() external view returns (bool);\\n\\n function actionPaused(address market, Action action) external view returns (bool);\\n\\n function mintedVAIs(address user) external view returns (uint);\\n\\n function vaiMintRate() external view returns (uint);\\n\\n function authorizedFlashLoan(address account) external view returns (bool);\\n\\n function userPoolId(address account) external view returns (uint96);\\n\\n function getLiquidationIncentive(address vToken) external view returns (uint256);\\n\\n function getEffectiveLiquidationIncentive(address account, address vToken) external view returns (uint256);\\n\\n function getEffectiveLtvFactor(\\n address account,\\n address vToken,\\n WeightFunction weightingStrategy\\n ) external view returns (uint256);\\n\\n function lastPoolId() external view returns (uint96);\\n\\n function corePoolId() external pure returns (uint96);\\n\\n function pools(\\n uint96 poolId\\n ) external view returns (string memory label, bool isActive, bool allowCorePoolFallback);\\n\\n function getPoolVTokens(uint96 poolId) external view returns (address[] memory);\\n\\n function poolMarkets(\\n uint96 poolId,\\n address vToken\\n )\\n external\\n view\\n returns (\\n bool isListed,\\n uint256 collateralFactorMantissa,\\n bool isVenus,\\n uint256 liquidationThresholdMantissa,\\n uint256 liquidationIncentiveMantissa,\\n uint96 marketPoolId,\\n bool isBorrowAllowed\\n );\\n\\n function isFlashLoanPaused() external view returns (bool);\\n}\\n\\ninterface IVAIVault {\\n function updatePendingRewards() external;\\n}\\n\\ninterface IComptroller {\\n /*** Treasury Data ***/\\n function treasuryAddress() external view returns (address);\\n\\n function treasuryPercent() external view returns (uint);\\n}\\n\",\"keccak256\":\"0x79fb234214cebf97f6e85c2bbe013977a4cd38ff6146a7c405a2fa9521e0cd4a\",\"license\":\"BSD-3-Clause\"},\"contracts/Comptroller/Diamond/interfaces/IFacetBase.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { Action } from \\\"../../../Comptroller/ComptrollerInterface.sol\\\";\\nimport { PoolMarketId } from \\\"../../../Comptroller/Types/PoolMarketId.sol\\\";\\n\\nenum WeightFunction {\\n /// @notice Use the collateral factor of the asset for weighting\\n USE_COLLATERAL_FACTOR,\\n /// @notice Use the liquidation threshold of the asset for weighting\\n USE_LIQUIDATION_THRESHOLD\\n}\\n\\ninterface IFacetBase {\\n /**\\n * @notice The initial XVS rewards index for a market\\n */\\n function venusInitialIndex() external pure returns (uint224);\\n\\n /**\\n * @notice Checks if a certain action is paused on a market\\n * @param action Action id\\n * @param market vToken address\\n */\\n function actionPaused(address market, Action action) external view returns (bool);\\n\\n /**\\n * @notice Returns the XVS address\\n * @return The address of XVS token\\n */\\n function getXVSAddress() external view returns (address);\\n\\n function getPoolMarketIndex(uint96 poolId, address vToken) external pure returns (PoolMarketId);\\n\\n function corePoolId() external pure returns (uint96);\\n}\\n\",\"keccak256\":\"0x454a2e213a5f54fbe7991193b78ae23406234f465c9806f75ee7bf2fe6d1531c\",\"license\":\"BSD-3-Clause\"},\"contracts/Comptroller/Types/PoolMarketId.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\n/// @notice Strongly-typed identifier for pool markets mapping keys\\n/// @dev Underlying storage is bytes32: first 12 bytes (96 bits) = poolId, last 20 bytes = vToken address\\ntype PoolMarketId is bytes32;\\n\\n \",\"keccak256\":\"0xf68bde30ddd6f8bf08194b493991c2a2ebd3814972f93a804beb9b366004cbe3\",\"license\":\"BSD-3-Clause\"},\"contracts/FlashLoan/interfaces/IFlashLoanReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { VToken } from \\\"../../Tokens/VTokens/VToken.sol\\\";\\n\\n/// @title IFlashLoanReceiver\\n/// @notice Interface for flashLoan receiver contract, which executes custom logic with flash-borrowed assets.\\n/// @dev This interface defines the method that must be implemented by any contract wishing to interact with the flashLoan system.\\n/// Contracts must ensure they have the means to repay at least the premium (fee), with any unpaid balance becoming debt.\\ninterface IFlashLoanReceiver {\\n /**\\n * @notice Executes an operation after receiving the flash-borrowed assets.\\n * @dev Implementation of this function must ensure at least the premium (fee) is repaid within the same transaction.\\n * Any unpaid balance (principal + premium - repaid amount) will be added to the onBehalf address's borrow balance.\\n * @param vTokens The vToken contracts corresponding to the flash-borrowed underlying assets.\\n * @param amounts The amounts of each underlying asset that were flash-borrowed.\\n * @param premiums The premiums (fees) associated with each flash-borrowed asset.\\n * @param initiator The address that initiated the flash loan.\\n * @param onBehalf The address of the user whose debt position will be used for any unpaid flash loan balance.\\n * @param param Additional parameters encoded as bytes. These can be used to pass custom data to the receiver contract.\\n * @return success True if the operation succeeds (regardless of repayment amount), false if the operation fails.\\n * @return repayAmounts Array of uint256 representing the amounts to be repaid for each asset. The receiver contract\\n * must approve these amounts to the respective vToken contracts before this function returns.\\n */\\n function executeOperation(\\n VToken[] calldata vTokens,\\n uint256[] calldata amounts,\\n uint256[] calldata premiums,\\n address initiator,\\n address onBehalf,\\n bytes calldata param\\n ) external returns (bool success, uint256[] memory repayAmounts);\\n}\\n\",\"keccak256\":\"0xe8eb540e98551178726f9d2d24e7ae1ab12ab96349c22f7232bacd377a0b33dd\",\"license\":\"BSD-3-Clause\"},\"contracts/InterestRateModels/InterestRateModelV8.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\n/**\\n * @title Venus's InterestRateModelV8 Interface\\n * @author Venus\\n */\\nabstract contract InterestRateModelV8 {\\n /// @notice Indicator that this is an InterestRateModel contract (for inspection)\\n bool public constant isInterestRateModel = true;\\n\\n /**\\n * @notice Calculates the current borrow interest rate per block\\n * @param cash The total amount of cash the market has\\n * @param borrows The total amount of borrows the market has outstanding\\n * @param reserves The total amnount of reserves the market has\\n * @return The borrow rate per block (as a percentage, and scaled by 1e18)\\n */\\n function getBorrowRate(uint256 cash, uint256 borrows, uint256 reserves) external view virtual returns (uint256);\\n\\n /**\\n * @notice Calculates the current supply interest rate per block\\n * @param cash The total amount of cash the market has\\n * @param borrows The total amount of borrows the market has outstanding\\n * @param reserves The total amnount of reserves the market has\\n * @param reserveFactorMantissa The current reserve factor the market has\\n * @return The supply rate per block (as a percentage, and scaled by 1e18)\\n */\\n function getSupplyRate(\\n uint256 cash,\\n uint256 borrows,\\n uint256 reserves,\\n uint256 reserveFactorMantissa\\n ) external view virtual returns (uint256);\\n}\\n\",\"keccak256\":\"0x4d5595e761d50a1431c34b39e72dde6c09b0ebccdbe8c5c4e12c8a2ac7b796e1\",\"license\":\"BSD-3-Clause\"},\"contracts/InterfacesV8.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\nimport { IERC20Upgradeable } from \\\"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\\\";\\nimport { ResilientOracleInterface } from \\\"@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol\\\";\\n\\ninterface IVToken is IERC20Upgradeable {\\n function accrueInterest() external returns (uint256);\\n\\n function redeem(uint256 redeemTokens) external returns (uint256);\\n\\n function redeemUnderlying(uint256 redeemAmount) external returns (uint256);\\n\\n function borrowBalanceCurrent(address borrower) external returns (uint256);\\n\\n function balanceOfUnderlying(address owner) external returns (uint256);\\n\\n function comptroller() external view returns (IComptroller);\\n\\n function borrowBalanceStored(address account) external view returns (uint256);\\n}\\n\\ninterface IVBep20 is IVToken {\\n function borrowBehalf(address borrower, uint256 borrowAmount) external returns (uint256);\\n\\n function repayBorrowBehalf(address borrower, uint256 repayAmount) external returns (uint256);\\n\\n function liquidateBorrow(\\n address borrower,\\n uint256 repayAmount,\\n IVToken vTokenCollateral\\n ) external returns (uint256);\\n\\n function underlying() external view returns (address);\\n}\\n\\ninterface IVBNB is IVToken {\\n function repayBorrowBehalf(address borrower) external payable;\\n\\n function liquidateBorrow(address borrower, IVToken vTokenCollateral) external payable;\\n}\\n\\ninterface IVAIController {\\n function accrueVAIInterest() external;\\n\\n function liquidateVAI(\\n address borrower,\\n uint256 repayAmount,\\n IVToken vTokenCollateral\\n ) external returns (uint256, uint256);\\n\\n function repayVAIBehalf(address borrower, uint256 amount) external returns (uint256, uint256);\\n\\n function getVAIAddress() external view returns (address);\\n\\n function getVAIRepayAmount(address borrower) external view returns (uint256);\\n}\\n\\ninterface IComptroller {\\n enum Action {\\n MINT,\\n REDEEM,\\n BORROW,\\n REPAY,\\n SEIZE,\\n LIQUIDATE,\\n TRANSFER,\\n ENTER_MARKET,\\n EXIT_MARKET\\n }\\n\\n function _setActionsPaused(address[] calldata markets_, Action[] calldata actions_, bool paused_) external;\\n\\n function executeFlashLoan(\\n address payable onBehalf,\\n address payable receiver,\\n IVToken[] calldata vTokens,\\n uint256[] calldata underlyingAmounts,\\n bytes calldata param\\n ) external;\\n\\n function vaiController() external view returns (IVAIController);\\n\\n function liquidatorContract() external view returns (address);\\n\\n function getAccountLiquidity(address account) external view returns (uint256, uint256, uint256);\\n\\n function oracle() external view returns (ResilientOracleInterface);\\n\\n function actionPaused(address market, Action action) external view returns (bool);\\n\\n function markets(address) external view returns (bool, uint256, bool);\\n\\n function isForcedLiquidationEnabled(address) external view returns (bool);\\n\\n function getEffectiveLiquidationIncentive(address account, address vToken) external view returns (uint256);\\n}\\n\\ninterface ILiquidator {\\n function restrictLiquidation(address borrower) external;\\n\\n function unrestrictLiquidation(address borrower) external;\\n\\n function addToAllowlist(address borrower, address liquidator) external;\\n\\n function removeFromAllowlist(address borrower, address liquidator) external;\\n\\n function liquidateBorrow(\\n address vToken,\\n address borrower,\\n uint256 repayAmount,\\n IVToken vTokenCollateral\\n ) external payable;\\n\\n function setTreasuryPercent(uint256 newTreasuryPercentMantissa) external;\\n\\n function treasuryPercentMantissa() external view returns (uint256);\\n}\\n\",\"keccak256\":\"0x02c315f8a158e79d96a4befd4b90473eebb8af75580a27c1eb3b001b421389a0\",\"license\":\"BSD-3-Clause\"},\"contracts/Tokens/VAI/VAIControllerInterface.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { VTokenInterface } from \\\"../VTokens/VTokenInterfaces.sol\\\";\\n\\ninterface VAIControllerInterface {\\n function mintVAI(uint256 mintVAIAmount) external returns (uint256);\\n\\n function repayVAI(uint256 amount) external returns (uint256, uint256);\\n\\n function repayVAIBehalf(address borrower, uint256 amount) external returns (uint256, uint256);\\n\\n function liquidateVAI(\\n address borrower,\\n uint256 repayAmount,\\n VTokenInterface vTokenCollateral\\n ) external returns (uint256, uint256);\\n\\n function getMintableVAI(address minter) external view returns (uint256, uint256);\\n\\n function getVAIAddress() external view returns (address);\\n\\n function getVAIRepayAmount(address account) external view returns (uint256);\\n}\\n\",\"keccak256\":\"0x69d2f9e13b7fbf0a29885048503642372d9ba3d37f2427d4b9cffb87eddd925b\",\"license\":\"BSD-3-Clause\"},\"contracts/Tokens/VTokens/VToken.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { IAccessControlManagerV8 } from \\\"@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol\\\";\\nimport { IProtocolShareReserve } from \\\"../../external/IProtocolShareReserve.sol\\\";\\nimport { ComptrollerInterface, IComptroller } from \\\"../../Comptroller/ComptrollerInterface.sol\\\";\\nimport { TokenErrorReporter } from \\\"../../Utils/ErrorReporter.sol\\\";\\nimport { Exponential } from \\\"../../Utils/Exponential.sol\\\";\\nimport { InterestRateModelV8 } from \\\"../../InterestRateModels/InterestRateModelV8.sol\\\";\\nimport { VTokenInterface } from \\\"./VTokenInterfaces.sol\\\";\\nimport { MANTISSA_ONE } from \\\"@venusprotocol/solidity-utilities/contracts/constants.sol\\\";\\n\\n/**\\n * @title Venus's vToken Contract\\n * @notice Abstract base for vTokens\\n * @author Venus\\n */\\nabstract contract VToken is VTokenInterface, Exponential, TokenErrorReporter {\\n struct MintLocalVars {\\n MathError mathErr;\\n uint exchangeRateMantissa;\\n uint mintTokens;\\n uint totalSupplyNew;\\n uint accountTokensNew;\\n uint actualMintAmount;\\n }\\n\\n struct RedeemLocalVars {\\n MathError mathErr;\\n uint exchangeRateMantissa;\\n uint redeemTokens;\\n uint redeemAmount;\\n uint totalSupplyNew;\\n uint accountTokensNew;\\n }\\n\\n struct BorrowLocalVars {\\n MathError mathErr;\\n uint accountBorrows;\\n uint accountBorrowsNew;\\n uint totalBorrowsNew;\\n }\\n\\n struct RepayBorrowLocalVars {\\n Error err;\\n MathError mathErr;\\n uint repayAmount;\\n uint borrowerIndex;\\n uint accountBorrows;\\n uint accountBorrowsNew;\\n uint totalBorrowsNew;\\n uint actualRepayAmount;\\n }\\n\\n /*** Reentrancy Guard ***/\\n\\n /**\\n * @dev Prevents a contract from calling itself, directly or indirectly.\\n */\\n modifier nonReentrant() {\\n require(_notEntered, \\\"re-entered\\\");\\n _notEntered = false;\\n _;\\n _notEntered = true; // get a gas-refund post-Istanbul\\n }\\n\\n /**\\n * @notice Transfer `amount` tokens from `msg.sender` to `dst`\\n * @param dst The address of the destination account\\n * @param amount The number of tokens to transfer\\n * @return Whether or not the transfer succeeded\\n */\\n // @custom:event Emits Transfer event\\n function transfer(address dst, uint256 amount) external override nonReentrant returns (bool) {\\n return transferTokens(msg.sender, msg.sender, dst, amount) == uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Transfer `amount` tokens from `src` to `dst`\\n * @param src The address of the source account\\n * @param dst The address of the destination account\\n * @param amount The number of tokens to transfer\\n * @return Whether or not the transfer succeeded\\n */\\n // @custom:event Emits Transfer event\\n function transferFrom(address src, address dst, uint256 amount) external override nonReentrant returns (bool) {\\n return transferTokens(msg.sender, src, dst, amount) == uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Approve `spender` to transfer up to `amount` from `src`\\n * @dev This will overwrite the approval amount for `spender`\\n * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)\\n * @param spender The address of the account which may transfer tokens\\n * @param amount The number of tokens that are approved (type(uint256).max means infinite)\\n * @return Whether or not the approval succeeded\\n */\\n // @custom:event Emits Approval event on successful approve\\n function approve(address spender, uint256 amount) external override returns (bool) {\\n transferAllowances[msg.sender][spender] = amount;\\n emit Approval(msg.sender, spender, amount);\\n return true;\\n }\\n\\n /**\\n * @notice Get the underlying balance of the `owner`\\n * @dev This also accrues interest in a transaction\\n * @param owner The address of the account to query\\n * @return The amount of underlying owned by `owner`\\n */\\n function balanceOfUnderlying(address owner) external override returns (uint) {\\n Exp memory exchangeRate = Exp({ mantissa: exchangeRateCurrent() });\\n (MathError mErr, uint balance) = mulScalarTruncate(exchangeRate, accountTokens[owner]);\\n ensureNoMathError(mErr);\\n return balance;\\n }\\n\\n /**\\n * @notice Returns the current total borrows plus accrued interest\\n * @return The total borrows with interest\\n */\\n function totalBorrowsCurrent() external override nonReentrant returns (uint) {\\n require(accrueInterest() == uint(Error.NO_ERROR), \\\"accrue interest failed\\\");\\n return totalBorrows;\\n }\\n\\n /**\\n * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex\\n * @param account The address whose balance should be calculated after updating borrowIndex\\n * @return The calculated balance\\n */\\n function borrowBalanceCurrent(address account) external override nonReentrant returns (uint) {\\n require(accrueInterest() == uint(Error.NO_ERROR), \\\"accrue interest failed\\\");\\n return borrowBalanceStored(account);\\n }\\n\\n /**\\n * @notice Transfers collateral tokens (this market) to the liquidator.\\n * @dev Will fail unless called by another vToken during the process of liquidation.\\n * Its absolutely critical to use msg.sender as the borrowed vToken and not a parameter.\\n * @param liquidator The account receiving seized collateral\\n * @param borrower The account having collateral seized\\n * @param seizeTokens The number of vTokens to seize\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits Transfer event\\n function seize(\\n address liquidator,\\n address borrower,\\n uint seizeTokens\\n ) external override nonReentrant returns (uint) {\\n return seizeInternal(msg.sender, liquidator, borrower, seizeTokens);\\n }\\n\\n /**\\n * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.\\n * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.\\n * @param newPendingAdmin New pending admin.\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits NewPendingAdmin event with old and new admin addresses\\n function _setPendingAdmin(address payable newPendingAdmin) external override returns (uint) {\\n // Check caller = admin\\n ensureAdmin(msg.sender);\\n\\n // Save current value, if any, for inclusion in log\\n address oldPendingAdmin = pendingAdmin;\\n\\n // Store pendingAdmin with value newPendingAdmin\\n pendingAdmin = newPendingAdmin;\\n\\n // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin)\\n emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin\\n * @dev Admin function for pending admin to accept role and update admin\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits NewAdmin event on successful acceptance\\n // @custom:event Emits NewPendingAdmin event with null new pending admin\\n function _acceptAdmin() external override returns (uint) {\\n // Check caller is pendingAdmin\\n if (msg.sender != pendingAdmin) {\\n return fail(Error.UNAUTHORIZED, FailureInfo.ACCEPT_ADMIN_PENDING_ADMIN_CHECK);\\n }\\n\\n // Save current values for inclusion in log\\n address oldAdmin = admin;\\n address oldPendingAdmin = pendingAdmin;\\n\\n // Store admin with value pendingAdmin\\n admin = pendingAdmin;\\n\\n // Clear the pending value\\n pendingAdmin = payable(address(0));\\n\\n emit NewAdmin(oldAdmin, admin);\\n emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice accrues interest and sets a new reserve factor for the protocol using `_setReserveFactorFresh`\\n * @dev Governor function to accrue interest and set a new reserve factor\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits NewReserveFactor event\\n function _setReserveFactor(uint newReserveFactorMantissa_) external override nonReentrant returns (uint) {\\n ensureAllowed(\\\"_setReserveFactor(uint256)\\\");\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reserve factor change failed.\\n return fail(Error(error), FailureInfo.SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // _setReserveFactorFresh emits reserve-factor-specific logs on errors, so we don't need to.\\n return _setReserveFactorFresh(newReserveFactorMantissa_);\\n }\\n\\n /**\\n * @notice Sets the address of the access control manager of this contract\\n * @dev Admin function to set the access control address\\n * @param newAccessControlManagerAddress New address for the access control\\n * @return uint 0=success, otherwise will revert\\n */\\n function setAccessControlManager(address newAccessControlManagerAddress) external returns (uint) {\\n // Check caller is admin\\n ensureAdmin(msg.sender);\\n\\n ensureNonZeroAddress(newAccessControlManagerAddress);\\n\\n emit NewAccessControlManager(accessControlManager, newAccessControlManagerAddress);\\n accessControlManager = newAccessControlManagerAddress;\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Accrues interest and reduces reserves by transferring to protocol share reserve\\n * @param reduceAmount_ Amount of reduction to reserves\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits ReservesReduced event\\n function _reduceReserves(uint reduceAmount_) external virtual override nonReentrant returns (uint) {\\n ensureAllowed(\\\"_reduceReserves(uint256)\\\");\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed.\\n return fail(Error(error), FailureInfo.REDUCE_RESERVES_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // If reserves were reduced in accrueInterest\\n if (reduceReservesBlockNumber == block.number) return (uint(Error.NO_ERROR));\\n // _reduceReservesFresh emits reserve-reduction-specific logs on errors, so we don't need to.\\n return _reduceReservesFresh(reduceAmount_);\\n }\\n\\n /**\\n * @notice Get the current allowance from `owner` for `spender`\\n * @param owner The address of the account which owns the tokens to be spent\\n * @param spender The address of the account which may transfer tokens\\n * @return The number of tokens allowed to be spent (type(uint256).max means infinite)\\n */\\n function allowance(address owner, address spender) external view override returns (uint256) {\\n return transferAllowances[owner][spender];\\n }\\n\\n /**\\n * @notice Get the token balance of the `owner`\\n * @param owner The address of the account to query\\n * @return The number of tokens owned by `owner`\\n */\\n function balanceOf(address owner) external view override returns (uint256) {\\n return accountTokens[owner];\\n }\\n\\n /**\\n * @notice Get a snapshot of the account's balances, and the cached exchange rate\\n * @dev This is used by comptroller to more efficiently perform liquidity checks.\\n * @param account Address of the account to snapshot\\n * @return (possible error, token balance, borrow balance, exchange rate mantissa)\\n */\\n function getAccountSnapshot(address account) external view override returns (uint, uint, uint, uint) {\\n uint borrowBalance;\\n uint exchangeRateMantissa;\\n\\n MathError mErr;\\n\\n (mErr, borrowBalance) = borrowBalanceStoredInternal(account);\\n if (mErr != MathError.NO_ERROR) {\\n return (uint(Error.MATH_ERROR), 0, 0, 0);\\n }\\n\\n (mErr, exchangeRateMantissa) = exchangeRateStoredInternal();\\n if (mErr != MathError.NO_ERROR) {\\n return (uint(Error.MATH_ERROR), 0, 0, 0);\\n }\\n\\n return (uint(Error.NO_ERROR), accountTokens[account], borrowBalance, exchangeRateMantissa);\\n }\\n\\n /**\\n * @notice Returns the current per-block supply interest rate for this vToken\\n * @dev The calculation includes `flashLoanAmount` in the cash balance to account for active flash loans.\\n * @return The supply interest rate per block, scaled by 1e18\\n */\\n function supplyRatePerBlock() external view override returns (uint) {\\n return\\n interestRateModel.getSupplyRate(\\n _getCashPriorWithFlashLoan(),\\n totalBorrows,\\n totalReserves,\\n reserveFactorMantissa\\n );\\n }\\n\\n /**\\n * @notice Returns the current per-block borrow interest rate for this vToken\\n * @dev The calculation includes `flashLoanAmount` in the cash balance to account for active flash loans.\\n * @return The borrow interest rate per block, scaled by 1e18\\n */\\n function borrowRatePerBlock() external view override returns (uint) {\\n return interestRateModel.getBorrowRate(_getCashPriorWithFlashLoan(), totalBorrows, totalReserves);\\n }\\n\\n /**\\n * @notice Get cash balance of this vToken in the underlying asset\\n * @return The quantity of underlying asset owned by this contract\\n */\\n function getCash() external view override returns (uint) {\\n return getCashPrior();\\n }\\n\\n /**\\n * @notice Governance function to set new threshold of block difference after which funds will be sent to the protocol share reserve\\n * @param newReduceReservesBlockDelta_ block difference value\\n */\\n function setReduceReservesBlockDelta(uint256 newReduceReservesBlockDelta_) external {\\n require(newReduceReservesBlockDelta_ > 0, \\\"Invalid Input\\\");\\n ensureAllowed(\\\"setReduceReservesBlockDelta(uint256)\\\");\\n emit NewReduceReservesBlockDelta(reduceReservesBlockDelta, newReduceReservesBlockDelta_);\\n reduceReservesBlockDelta = newReduceReservesBlockDelta_;\\n }\\n\\n /**\\n * @notice Sets protocol share reserve contract address\\n * @param protcolShareReserve_ The address of protocol share reserve contract\\n */\\n function setProtocolShareReserve(address payable protcolShareReserve_) external {\\n // Check caller is admin\\n ensureAdmin(msg.sender);\\n ensureNonZeroAddress(protcolShareReserve_);\\n emit NewProtocolShareReserve(protocolShareReserve, protcolShareReserve_);\\n protocolShareReserve = protcolShareReserve_;\\n }\\n\\n /**\\n * @notice Transfers the underlying asset to the specified address for flash loan purposes.\\n * @dev Can only be called by the Comptroller contract. This function performs the actual transfer of the underlying\\n * asset by calling the `doTransferOut` internal function.\\n * - The caller must be the Comptroller contract.\\n * - Sets the flashLoanAmount to track the borrowed amount during the flash loan process.\\n * @param to The address to which the underlying asset is to be transferred.\\n * @param amount The amount of the underlying asset to transfer.\\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\\n * @custom:error FlashLoanAlreadyActive is thrown if there is already an active flash loan.\\n * @custom:error InsufficientCash is thrown when the vToken does not have enough cash to lend\\n * @custom:event Emits TransferOutUnderlyingFlashLoan event on successful transfer of amount to receiver\\n */\\n function transferOutUnderlyingFlashLoan(address payable to, uint256 amount) external nonReentrant {\\n if (msg.sender != address(comptroller)) {\\n revert InvalidComptroller();\\n }\\n\\n if (flashLoanAmount > 0) {\\n revert FlashLoanAlreadyActive();\\n }\\n\\n if (getCashPrior() < amount) {\\n revert InsufficientCash();\\n }\\n flashLoanAmount = amount;\\n doTransferOut(to, amount);\\n emit TransferOutUnderlyingFlashLoan(underlying, to, amount);\\n }\\n\\n /**\\n * @notice Transfers the underlying asset from the specified address during flash loan repayment.\\n * @dev Can only be called by the Comptroller contract. This function performs the actual transfer of the underlying\\n * asset by calling the `doTransferIn` internal function and handles protocol fee distribution.\\n * - The caller must be the Comptroller contract.\\n * - Transfers the protocol fee to the protocol share reserve.\\n * - Resets the flashLoanAmount to 0 to complete the flash loan cycle.\\n * @param from The address from which the underlying asset is to be transferred.\\n * @param repaymentAmount The amount of the underlying asset being repaid by the receiver.\\n * @param totalFee The total fee amount for the flash loan.\\n * @param protocolFee The protocol fee amount to be transferred to the protocol share reserve.\\n * @return actualAmountTransferred The actual amount transferred in from the receiver.\\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\\n * @custom:error InsufficientRepayment is thrown when the repayment amount is insufficient to cover the total fee\\n * @custom:event Emits TransferInUnderlyingFlashLoan event on successful transfer of amount from the receiver to the vToken\\n */\\n function transferInUnderlyingFlashLoan(\\n address payable from,\\n uint256 repaymentAmount,\\n uint256 totalFee,\\n uint256 protocolFee\\n ) external nonReentrant returns (uint256) {\\n if (msg.sender != address(comptroller)) {\\n revert InvalidComptroller();\\n }\\n\\n uint256 actualAmountTransferred = doTransferIn(from, repaymentAmount);\\n\\n if (actualAmountTransferred < totalFee) {\\n revert InsufficientRepayment(actualAmountTransferred, totalFee);\\n }\\n\\n // Transfer protocol fee to protocol share reserve\\n doTransferOut(protocolShareReserve, protocolFee);\\n\\n IProtocolShareReserve(protocolShareReserve).updateAssetsState(\\n address(comptroller),\\n underlying,\\n IProtocolShareReserve.IncomeType.FLASHLOAN\\n );\\n\\n // Reset flashLoanAmount to complete the flash loan cycle\\n flashLoanAmount = 0;\\n\\n emit TransferInUnderlyingFlashLoan(underlying, from, actualAmountTransferred, totalFee, protocolFee);\\n return actualAmountTransferred;\\n }\\n\\n /**\\n * @notice Sets flash loan status for the market\\n * @param enabled True to enable flash loans, false to disable\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n * @custom:access Only Governance\\n * @custom:event Emits FlashLoanStatusChanged event on success\\n */\\n function setFlashLoanEnabled(bool enabled) external returns (uint256) {\\n ensureAllowed(\\\"setFlashLoanEnabled(bool)\\\");\\n\\n if (isFlashLoanEnabled != enabled) {\\n emit FlashLoanStatusChanged(isFlashLoanEnabled, enabled);\\n isFlashLoanEnabled = enabled;\\n }\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Update flashLoan fee mantissa\\n * @param flashLoanFeeMantissa_ FlashLoan fee, scaled by 1e18\\n * @param flashLoanProtocolShare_ FlashLoan protocol fee share, transferred to protocol share reserve\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n * @custom:access Only Governance\\n * @custom:error FlashLoanFeeTooHigh is thrown when flash loan fee exceeds maximum allowed\\n * @custom:error FlashLoanProtocolShareTooHigh is thrown when flash loan fee protocol share exceeds maximum allowed\\n * @custom:event Emits FlashLoanFeeUpdated event on success\\n */\\n function setFlashLoanFeeMantissa(\\n uint256 flashLoanFeeMantissa_,\\n uint256 flashLoanProtocolShare_\\n ) external returns (uint256) {\\n ensureAllowed(\\\"setFlashLoanFeeMantissa(uint256,uint256)\\\");\\n\\n if (flashLoanFeeMantissa_ > MANTISSA_ONE) {\\n revert FlashLoanFeeTooHigh(flashLoanFeeMantissa_, MANTISSA_ONE);\\n }\\n\\n if (flashLoanProtocolShare_ > MANTISSA_ONE) {\\n revert FlashLoanProtocolShareTooHigh(flashLoanProtocolShare_, MANTISSA_ONE);\\n }\\n\\n // Only proceed if values are changing\\n if (\\n flashLoanFeeMantissa != flashLoanFeeMantissa_ || flashLoanProtocolShareMantissa != flashLoanProtocolShare_\\n ) {\\n emit FlashLoanFeeUpdated(\\n flashLoanFeeMantissa,\\n flashLoanFeeMantissa_,\\n flashLoanProtocolShareMantissa,\\n flashLoanProtocolShare_\\n );\\n\\n flashLoanFeeMantissa = flashLoanFeeMantissa_;\\n flashLoanProtocolShareMantissa = flashLoanProtocolShare_;\\n }\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Initialize the money market\\n * @param comptroller_ The address of the Comptroller\\n * @param interestRateModel_ The address of the interest rate model\\n * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18\\n * @param name_ EIP-20 name of this token\\n * @param symbol_ EIP-20 symbol of this token\\n * @param decimals_ EIP-20 decimal precision of this token\\n */\\n function initialize(\\n ComptrollerInterface comptroller_,\\n InterestRateModelV8 interestRateModel_,\\n uint initialExchangeRateMantissa_,\\n string memory name_,\\n string memory symbol_,\\n uint8 decimals_\\n ) public {\\n ensureAdmin(msg.sender);\\n require(accrualBlockNumber == 0 && borrowIndex == 0, \\\"market may only be initialized once\\\");\\n\\n // Set initial exchange rate\\n initialExchangeRateMantissa = initialExchangeRateMantissa_;\\n require(initialExchangeRateMantissa > 0, \\\"initial exchange rate must be greater than zero.\\\");\\n\\n // Set the comptroller\\n uint err = _setComptroller(comptroller_);\\n require(err == uint(Error.NO_ERROR), \\\"setting comptroller failed\\\");\\n\\n // Initialize block number and borrow index (block number mocks depend on comptroller being set)\\n accrualBlockNumber = block.number;\\n borrowIndex = mantissaOne;\\n\\n // Set the interest rate model (depends on block number / borrow index)\\n err = _setInterestRateModelFresh(interestRateModel_);\\n require(err == uint(Error.NO_ERROR), \\\"setting interest rate model failed\\\");\\n\\n name = name_;\\n symbol = symbol_;\\n decimals = decimals_;\\n\\n // The counter starts true to prevent changing it from zero to non-zero (i.e. smaller cost/refund)\\n _notEntered = true;\\n }\\n\\n /**\\n * @notice Accrue interest then return the up-to-date exchange rate\\n * @return Calculated exchange rate scaled by 1e18\\n */\\n function exchangeRateCurrent() public override nonReentrant returns (uint) {\\n require(accrueInterest() == uint(Error.NO_ERROR), \\\"accrue interest failed\\\");\\n return exchangeRateStored();\\n }\\n\\n /**\\n * @notice Applies accrued interest to total borrows and reserves\\n * @dev This calculates interest accrued from the last checkpointed block\\n * up to the current block and writes new checkpoint to storage and\\n * reduce spread reserves to protocol share reserve\\n * if currentBlock - reduceReservesBlockNumber >= blockDelta\\n */\\n // @custom:event Emits AccrueInterest event\\n function accrueInterest() public virtual override returns (uint) {\\n /* Remember the initial block number */\\n uint currentBlockNumber = block.number;\\n uint accrualBlockNumberPrior = accrualBlockNumber;\\n\\n /* Short-circuit accumulating 0 interest */\\n if (accrualBlockNumberPrior == currentBlockNumber) {\\n return uint(Error.NO_ERROR);\\n }\\n\\n /* Read the previous values out of storage */\\n uint cashPrior = _getCashPriorWithFlashLoan();\\n uint borrowsPrior = totalBorrows;\\n uint reservesPrior = totalReserves;\\n uint borrowIndexPrior = borrowIndex;\\n\\n /* Calculate the current borrow interest rate */\\n uint borrowRateMantissa = interestRateModel.getBorrowRate(cashPrior, borrowsPrior, reservesPrior);\\n require(borrowRateMantissa <= borrowRateMaxMantissa, \\\"borrow rate is absurdly high\\\");\\n\\n /* Calculate the number of blocks elapsed since the last accrual */\\n (MathError mathErr, uint blockDelta) = subUInt(currentBlockNumber, accrualBlockNumberPrior);\\n ensureNoMathError(mathErr);\\n\\n /*\\n * Calculate the interest accumulated into borrows and reserves and the new index:\\n * simpleInterestFactor = borrowRate * blockDelta\\n * interestAccumulated = simpleInterestFactor * totalBorrows\\n * totalBorrowsNew = interestAccumulated + totalBorrows\\n * totalReservesNew = interestAccumulated * reserveFactor + totalReserves\\n * borrowIndexNew = simpleInterestFactor * borrowIndex + borrowIndex\\n */\\n\\n Exp memory simpleInterestFactor;\\n uint interestAccumulated;\\n uint totalBorrowsNew;\\n uint totalReservesNew;\\n uint borrowIndexNew;\\n\\n (mathErr, simpleInterestFactor) = mulScalar(Exp({ mantissa: borrowRateMantissa }), blockDelta);\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n (mathErr, interestAccumulated) = mulScalarTruncate(simpleInterestFactor, borrowsPrior);\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n (mathErr, totalBorrowsNew) = addUInt(interestAccumulated, borrowsPrior);\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n (mathErr, totalReservesNew) = mulScalarTruncateAddUInt(\\n Exp({ mantissa: reserveFactorMantissa }),\\n interestAccumulated,\\n reservesPrior\\n );\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n (mathErr, borrowIndexNew) = mulScalarTruncateAddUInt(simpleInterestFactor, borrowIndexPrior, borrowIndexPrior);\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We write the previously calculated values into storage */\\n accrualBlockNumber = currentBlockNumber;\\n borrowIndex = borrowIndexNew;\\n totalBorrows = totalBorrowsNew;\\n totalReserves = totalReservesNew;\\n\\n (mathErr, blockDelta) = subUInt(currentBlockNumber, reduceReservesBlockNumber);\\n ensureNoMathError(mathErr);\\n if (blockDelta >= reduceReservesBlockDelta) {\\n reduceReservesBlockNumber = currentBlockNumber;\\n uint actualCash = getCashPrior();\\n if (actualCash < totalReservesNew) {\\n _reduceReservesFresh(actualCash);\\n } else {\\n _reduceReservesFresh(totalReservesNew);\\n }\\n }\\n\\n /* We emit an AccrueInterest event */\\n emit AccrueInterest(cashPrior, interestAccumulated, borrowIndexNew, totalBorrowsNew);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Sets a new comptroller for the market\\n * @dev Admin function to set a new comptroller\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits NewComptroller event\\n function _setComptroller(ComptrollerInterface newComptroller) public override returns (uint) {\\n // Check caller is admin\\n ensureAdmin(msg.sender);\\n\\n // Ensure invoke comptroller.isComptroller() returns true\\n require(newComptroller.isComptroller(), \\\"marker method returned false\\\");\\n\\n // Emit NewComptroller(oldComptroller, newComptroller)\\n emit NewComptroller(comptroller, newComptroller);\\n\\n // Set market's comptroller to newComptroller\\n comptroller = newComptroller;\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Accrues interest and updates the interest rate model using _setInterestRateModelFresh\\n * @dev Governance function to accrue interest and update the interest rate model\\n * @param newInterestRateModel_ The new interest rate model to use\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _setInterestRateModel(InterestRateModelV8 newInterestRateModel_) public override returns (uint) {\\n ensureAllowed(\\\"_setInterestRateModel(address)\\\");\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted change of interest rate model failed\\n return fail(Error(error), FailureInfo.SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // _setInterestRateModelFresh emits interest-rate-model-update-specific logs on errors, so we don't need to.\\n return _setInterestRateModelFresh(newInterestRateModel_);\\n }\\n\\n /**\\n * @notice Calculates the exchange rate from the underlying to the VToken\\n * @dev This function does not accrue interest before calculating the exchange rate\\n * @return Calculated exchange rate scaled by 1e18\\n */\\n function exchangeRateStored() public view override returns (uint) {\\n (MathError err, uint result) = exchangeRateStoredInternal();\\n ensureNoMathError(err);\\n return result;\\n }\\n\\n /**\\n * @notice Return the borrow balance of account based on stored data\\n * @param account The address whose balance should be calculated\\n * @return The calculated balance\\n */\\n function borrowBalanceStored(address account) public view override returns (uint) {\\n (MathError err, uint result) = borrowBalanceStoredInternal(account);\\n ensureNoMathError(err);\\n return result;\\n }\\n\\n /**\\n * @notice Opens a debt position for the borrower as part of flash loan repayment\\n * @dev This function is specifically called during flash loan operations when the repayment\\n * is insufficient to cover the full borrowed amount plus fees. It creates a debt position\\n * for the unpaid balance. The function checks if the borrow is allowed, accrues interest,\\n * and updates the borrower's balance. It also emits a Borrow event and calls the\\n * comptroller's borrowVerify function. It reverts if the borrow is not allowed or\\n * if the market's block number is not current.\\n * @param borrower The address of the borrower who will have the debt position created\\n * @param borrowAmount The amount of underlying asset that becomes debt (unpaid flash loan balance)\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\\n */\\n function flashLoanDebtPosition(address borrower, uint borrowAmount) external nonReentrant returns (uint256) {\\n // Reverts if the caller is not the comptroller\\n if (msg.sender != address(comptroller)) {\\n revert InvalidComptroller();\\n }\\n\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors.\\n return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // borrowFresh emits borrow-specific logs on errors, so we don't need to\\n return borrowFresh(borrower, payable(address(0)), borrowAmount, false);\\n }\\n\\n /**\\n * @notice Calculates the total fee and protocol fee for a flash loan..\\n * @param amount The amount of the flash loan.\\n * @return totalFee The total fee for the flash loan.\\n * @return protocolFee The portion of the total fee allocated to the protocol.\\n */\\n function calculateFlashLoanFee(uint256 amount) public view returns (uint256, uint256) {\\n MathError mErr;\\n uint256 totalFee;\\n uint256 protocolFee;\\n\\n (mErr, totalFee) = mulScalarTruncate(Exp({ mantissa: amount }), flashLoanFeeMantissa);\\n ensureNoMathError(mErr);\\n\\n (mErr, protocolFee) = mulScalarTruncate(Exp({ mantissa: totalFee }), flashLoanProtocolShareMantissa);\\n ensureNoMathError(mErr);\\n\\n return (totalFee, protocolFee);\\n }\\n\\n /**\\n * @notice Transfers `tokens` tokens from `src` to `dst` by `spender`\\n * @dev Called by both `transfer` and `transferFrom` internally\\n * @param spender The address of the account performing the transfer\\n * @param src The address of the source account\\n * @param dst The address of the destination account\\n * @param tokens The number of tokens to transfer\\n * @return Whether or not the transfer succeeded\\n */\\n function transferTokens(address spender, address src, address dst, uint tokens) internal returns (uint) {\\n /* Fail if transfer not allowed */\\n uint allowed = comptroller.transferAllowed(address(this), src, dst, tokens);\\n if (allowed != 0) {\\n return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.TRANSFER_COMPTROLLER_REJECTION, allowed);\\n }\\n\\n /* Do not allow self-transfers */\\n if (src == dst) {\\n return fail(Error.BAD_INPUT, FailureInfo.TRANSFER_NOT_ALLOWED);\\n }\\n\\n /* Get the allowance, infinite for the account owner */\\n uint startingAllowance = 0;\\n if (spender == src) {\\n startingAllowance = type(uint256).max;\\n } else {\\n startingAllowance = transferAllowances[src][spender];\\n }\\n\\n /* Do the calculations, checking for {under,over}flow */\\n MathError mathErr;\\n uint allowanceNew;\\n uint srvTokensNew;\\n uint dstTokensNew;\\n\\n (mathErr, allowanceNew) = subUInt(startingAllowance, tokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ALLOWED);\\n }\\n\\n (mathErr, srvTokensNew) = subUInt(accountTokens[src], tokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ENOUGH);\\n }\\n\\n (mathErr, dstTokensNew) = addUInt(accountTokens[dst], tokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_TOO_MUCH);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n accountTokens[src] = srvTokensNew;\\n accountTokens[dst] = dstTokensNew;\\n\\n /* Eat some of the allowance (if necessary) */\\n if (startingAllowance != type(uint256).max) {\\n transferAllowances[src][spender] = allowanceNew;\\n }\\n\\n /* We emit a Transfer event */\\n emit Transfer(src, dst, tokens);\\n\\n comptroller.transferVerify(address(this), src, dst, tokens);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Sender supplies assets into the market and receives vTokens in exchange\\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\\n * @param mintAmount The amount of the underlying asset to supply\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\\n */\\n function mintInternal(uint mintAmount) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted mint failed\\n return (fail(Error(error), FailureInfo.MINT_ACCRUE_INTEREST_FAILED), 0);\\n }\\n\\n // mintFresh emits the actual Mint event if successful and logs on errors, so we don't need to\\n return mintFresh(msg.sender, mintAmount);\\n }\\n\\n /**\\n * @notice User supplies assets into the market and receives vTokens in exchange\\n * @dev Assumes interest has already been accrued up to the current block\\n * @param minter The address of the account which is supplying the assets\\n * @param mintAmount The amount of the underlying asset to supply\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\\n */\\n function mintFresh(address minter, uint mintAmount) internal returns (uint, uint) {\\n /* Fail if mint not allowed */\\n uint allowed = comptroller.mintAllowed(address(this), minter, mintAmount);\\n if (allowed != 0) {\\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed), 0);\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.MINT_FRESHNESS_CHECK), 0);\\n }\\n\\n MintLocalVars memory vars;\\n\\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\\n if (vars.mathErr != MathError.NO_ERROR) {\\n return (failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)), 0);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /*\\n * We call `doTransferIn` for the minter and the mintAmount.\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * `doTransferIn` reverts if anything goes wrong, since we can't be sure if\\n * side-effects occurred. The function returns the amount actually transferred,\\n * in case of a fee. On success, the vToken holds an additional `actualMintAmount`\\n * of cash.\\n */\\n vars.actualMintAmount = doTransferIn(minter, mintAmount);\\n\\n /*\\n * We get the current exchange rate and calculate the number of vTokens to be minted:\\n * mintTokens = actualMintAmount / exchangeRate\\n */\\n\\n (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(\\n vars.actualMintAmount,\\n Exp({ mantissa: vars.exchangeRateMantissa })\\n );\\n ensureNoMathError(vars.mathErr);\\n\\n /*\\n * We calculate the new total supply of vTokens and minter token balance, checking for overflow:\\n * totalSupplyNew = totalSupply + mintTokens\\n * accountTokensNew = accountTokens[minter] + mintTokens\\n */\\n (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens);\\n ensureNoMathError(vars.mathErr);\\n (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[minter], vars.mintTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n /* We write previously calculated values into storage */\\n totalSupply = vars.totalSupplyNew;\\n accountTokens[minter] = vars.accountTokensNew;\\n\\n /* We emit a Mint event, and a Transfer event */\\n emit Mint(minter, vars.actualMintAmount, vars.mintTokens, vars.accountTokensNew);\\n emit Transfer(address(this), minter, vars.mintTokens);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.mintVerify(address(this), minter, vars.actualMintAmount, vars.mintTokens);\\n\\n return (uint(Error.NO_ERROR), vars.actualMintAmount);\\n }\\n\\n /**\\n * @notice Sender supplies assets into the market and receiver receives vTokens in exchange\\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\\n * @param receiver The address of the account which is receiving the vTokens\\n * @param mintAmount The amount of the underlying asset to supply\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\\n */\\n function mintBehalfInternal(address receiver, uint mintAmount) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted mintBehalf failed\\n return (fail(Error(error), FailureInfo.MINT_ACCRUE_INTEREST_FAILED), 0);\\n }\\n\\n // mintBehalfFresh emits the actual Mint event if successful and logs on errors, so we don't need to\\n return mintBehalfFresh(msg.sender, receiver, mintAmount);\\n }\\n\\n /**\\n * @notice Payer supplies assets into the market and receiver receives vTokens in exchange\\n * @dev Assumes interest has already been accrued up to the current block\\n * @param payer The address of the account which is paying the underlying token\\n * @param receiver The address of the account which is receiving vToken\\n * @param mintAmount The amount of the underlying asset to supply\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\\n */\\n function mintBehalfFresh(address payer, address receiver, uint mintAmount) internal returns (uint, uint) {\\n ensureNonZeroAddress(receiver);\\n /* Fail if mint not allowed */\\n uint allowed = comptroller.mintAllowed(address(this), receiver, mintAmount);\\n if (allowed != 0) {\\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed), 0);\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.MINT_FRESHNESS_CHECK), 0);\\n }\\n\\n MintLocalVars memory vars;\\n\\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\\n if (vars.mathErr != MathError.NO_ERROR) {\\n return (failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)), 0);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /*\\n * We call `doTransferIn` for the payer and the mintAmount.\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * `doTransferIn` reverts if anything goes wrong, since we can't be sure if\\n * side-effects occurred. The function returns the amount actually transferred,\\n * in case of a fee. On success, the vToken holds an additional `actualMintAmount`\\n * of cash.\\n */\\n vars.actualMintAmount = doTransferIn(payer, mintAmount);\\n\\n /*\\n * We get the current exchange rate and calculate the number of vTokens to be minted:\\n * mintTokens = actualMintAmount / exchangeRate\\n */\\n\\n (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(\\n vars.actualMintAmount,\\n Exp({ mantissa: vars.exchangeRateMantissa })\\n );\\n ensureNoMathError(vars.mathErr);\\n\\n /*\\n * We calculate the new total supply of vTokens and receiver token balance, checking for overflow:\\n * totalSupplyNew = totalSupply + mintTokens\\n * accountTokensNew = accountTokens[receiver] + mintTokens\\n */\\n (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[receiver], vars.mintTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n /* We write previously calculated values into storage */\\n totalSupply = vars.totalSupplyNew;\\n accountTokens[receiver] = vars.accountTokensNew;\\n\\n /* We emit a MintBehalf event, and a Transfer event */\\n emit MintBehalf(payer, receiver, vars.actualMintAmount, vars.mintTokens, vars.accountTokensNew);\\n emit Transfer(address(this), receiver, vars.mintTokens);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.mintVerify(address(this), receiver, vars.actualMintAmount, vars.mintTokens);\\n\\n return (uint(Error.NO_ERROR), vars.actualMintAmount);\\n }\\n\\n /**\\n * @notice Redeemer redeems vTokens in exchange for the underlying assets, transferred to the receiver. Redeemer and receiver can be the same\\n * address, or different addresses if the receiver was previously approved by the redeemer as a valid delegate (see MarketFacet.updateDelegate)\\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\\n * @param redeemer The address of the account which is redeeming the tokens\\n * @param receiver The receiver of the tokens\\n * @param redeemTokens The number of vTokens to redeem into underlying\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function redeemInternal(\\n address redeemer,\\n address payable receiver,\\n uint redeemTokens\\n ) internal nonReentrant returns (uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed\\n return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // redeemFresh emits redeem-specific logs on errors, so we don't need to\\n return redeemFresh(redeemer, receiver, redeemTokens, 0);\\n }\\n\\n /**\\n * @notice Sender redeems underlying assets on behalf of some other address. This function is only available\\n * for senders, explicitly marked as delegates of the supplier using `comptroller.updateDelegate`\\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\\n * @param redeemer The address of the account which is redeeming the tokens\\n * @param receiver The receiver of the tokens, if called by a delegate\\n * @param redeemAmount The amount of underlying to receive from redeeming vTokens\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function redeemUnderlyingInternal(\\n address redeemer,\\n address payable receiver,\\n uint redeemAmount\\n ) internal nonReentrant returns (uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed\\n return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // redeemFresh emits redeem-specific logs on errors, so we don't need to\\n return redeemFresh(redeemer, receiver, 0, redeemAmount);\\n }\\n\\n /**\\n * @notice Redeemer redeems vTokens in exchange for the underlying assets, transferred to the receiver. Redeemer and receiver can be the same\\n * address, or different addresses if the receiver was previously approved by the redeemer as a valid delegate (see MarketFacet.updateDelegate)\\n * @dev Assumes interest has already been accrued up to the current block\\n * @param redeemer The address of the account which is redeeming the tokens\\n * @param receiver The receiver of the tokens\\n * @param redeemTokensIn The number of vTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be non-zero)\\n * @param redeemAmountIn The number of underlying tokens to receive from redeeming vTokens (only one of redeemTokensIn or redeemAmountIn may be non-zero)\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // solhint-disable-next-line code-complexity\\n function redeemFresh(\\n address redeemer,\\n address payable receiver,\\n uint redeemTokensIn,\\n uint redeemAmountIn\\n ) internal returns (uint) {\\n require(redeemTokensIn == 0 || redeemAmountIn == 0, \\\"one of redeemTokensIn or redeemAmountIn must be zero\\\");\\n\\n RedeemLocalVars memory vars;\\n\\n /* exchangeRate = invoke Exchange Rate Stored() */\\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\\n ensureNoMathError(vars.mathErr);\\n\\n /* If redeemTokensIn > 0: */\\n if (redeemTokensIn > 0) {\\n /*\\n * We calculate the exchange rate and the amount of underlying to be redeemed:\\n * redeemTokens = redeemTokensIn\\n * redeemAmount = redeemTokensIn x exchangeRateCurrent\\n */\\n vars.redeemTokens = redeemTokensIn;\\n\\n (vars.mathErr, vars.redeemAmount) = mulScalarTruncate(\\n Exp({ mantissa: vars.exchangeRateMantissa }),\\n redeemTokensIn\\n );\\n ensureNoMathError(vars.mathErr);\\n } else {\\n /*\\n * We get the current exchange rate and calculate the amount to be redeemed:\\n * redeemTokens = redeemAmountIn / exchangeRate\\n * redeemAmount = redeemAmountIn\\n */\\n\\n (vars.mathErr, vars.redeemTokens) = divScalarByExpTruncate(\\n redeemAmountIn,\\n Exp({ mantissa: vars.exchangeRateMantissa })\\n );\\n ensureNoMathError(vars.mathErr);\\n\\n vars.redeemAmount = redeemAmountIn;\\n }\\n\\n /* Fail if redeem not allowed */\\n uint allowed = comptroller.redeemAllowed(address(this), redeemer, vars.redeemTokens);\\n if (allowed != 0) {\\n revert(\\\"math error\\\");\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n revert(\\\"math error\\\");\\n }\\n\\n /*\\n * We calculate the new total supply and redeemer balance, checking for underflow:\\n * totalSupplyNew = totalSupply - redeemTokens\\n * accountTokensNew = accountTokens[redeemer] - redeemTokens\\n */\\n (vars.mathErr, vars.totalSupplyNew) = subUInt(totalSupply, vars.redeemTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.accountTokensNew) = subUInt(accountTokens[redeemer], vars.redeemTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n /* Fail gracefully if protocol has insufficient cash */\\n if (getCashPrior() < vars.redeemAmount) {\\n revert(\\\"math error\\\");\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We write previously calculated values into storage */\\n totalSupply = vars.totalSupplyNew;\\n accountTokens[redeemer] = vars.accountTokensNew;\\n\\n /*\\n * We invoke doTransferOut for the receiver and the redeemAmount.\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * On success, the vToken has redeemAmount less of cash.\\n * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n */\\n\\n uint feeAmount;\\n uint remainedAmount;\\n if (IComptroller(address(comptroller)).treasuryPercent() != 0) {\\n (vars.mathErr, feeAmount) = mulUInt(\\n vars.redeemAmount,\\n IComptroller(address(comptroller)).treasuryPercent()\\n );\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, feeAmount) = divUInt(feeAmount, MANTISSA_ONE);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, remainedAmount) = subUInt(vars.redeemAmount, feeAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n address payable treasuryAddress = payable(IComptroller(address(comptroller)).treasuryAddress());\\n doTransferOut(treasuryAddress, feeAmount);\\n\\n emit RedeemFee(redeemer, feeAmount, vars.redeemTokens);\\n } else {\\n remainedAmount = vars.redeemAmount;\\n }\\n\\n doTransferOut(receiver, remainedAmount);\\n\\n /* We emit a Transfer event, and a Redeem event */\\n emit Transfer(redeemer, address(this), vars.redeemTokens);\\n emit Redeem(redeemer, remainedAmount, vars.redeemTokens, vars.accountTokensNew);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.redeemVerify(address(this), redeemer, vars.redeemAmount, vars.redeemTokens);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Receiver gets the borrow on behalf of the borrower address\\n * @param borrower The borrower, on behalf of whom to borrow\\n * @param receiver The account that would receive the funds (can be the same as the borrower)\\n * @param borrowAmount The amount of the underlying asset to borrow\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function borrowInternal(\\n address borrower,\\n address payable receiver,\\n uint borrowAmount\\n ) internal nonReentrant returns (uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\\n return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // borrowFresh emits borrow-specific logs on errors, so we don't need to\\n return borrowFresh(borrower, receiver, borrowAmount, true);\\n }\\n\\n /**\\n * @notice Receiver gets the borrow on behalf of the borrower address, controls whether to do the transfer\\n * @dev Before calling this function, ensure that the interest has been accrued\\n * @param borrower The borrower, on behalf of whom to borrow\\n * @param receiver The account that would receive the funds (can be the same as the borrower)\\n * @param borrowAmount The amount of the underlying asset to borrow\\n * @param shouldTransfer Whether to call doTransferOut for the receiver\\n * @return uint Returns 0 on success, otherwise revert (see ErrorReporter.sol for details).\\n */\\n function borrowFresh(\\n address borrower,\\n address payable receiver,\\n uint borrowAmount,\\n bool shouldTransfer\\n ) internal returns (uint) {\\n /* Revert if borrow not allowed */\\n uint allowed = comptroller.borrowAllowed(address(this), borrower, borrowAmount);\\n\\n if (allowed != 0) {\\n revert(\\\"math error\\\");\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n revert(\\\"math error\\\");\\n }\\n\\n /* Revert if protocol has insufficient underlying cash */\\n if (shouldTransfer && getCashPrior() < borrowAmount) {\\n revert(\\\"math error\\\");\\n }\\n\\n BorrowLocalVars memory vars;\\n\\n /*\\n * We calculate the new borrower and total borrow balances, failing on overflow:\\n * accountBorrowsNew = accountBorrows + borrowAmount\\n * totalBorrowsNew = totalBorrows + borrowAmount\\n */\\n (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.accountBorrowsNew) = addUInt(vars.accountBorrows, borrowAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.totalBorrowsNew) = addUInt(totalBorrows, borrowAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We write the previously calculated values into storage */\\n accountBorrows[borrower].principal = vars.accountBorrowsNew;\\n accountBorrows[borrower].interestIndex = borrowIndex;\\n totalBorrows = vars.totalBorrowsNew;\\n\\n if (shouldTransfer) {\\n /*\\n * We invoke doTransferOut for the receiver and the borrowAmount.\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * On success, the vToken borrowAmount less of cash.\\n * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n */\\n doTransferOut(receiver, borrowAmount);\\n }\\n\\n /* We emit a Borrow event */\\n emit Borrow(borrower, borrowAmount, vars.accountBorrowsNew, vars.totalBorrowsNew);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.borrowVerify(address(this), borrower, borrowAmount);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Sender repays their own borrow\\n * @param repayAmount The amount to repay\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n function repayBorrowInternal(uint repayAmount) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\\n return (fail(Error(error), FailureInfo.REPAY_BORROW_ACCRUE_INTEREST_FAILED), 0);\\n }\\n\\n // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to\\n return repayBorrowFresh(msg.sender, msg.sender, repayAmount);\\n }\\n\\n /**\\n * @notice Sender repays a borrow belonging to another borrowing account\\n * @param borrower The account with the debt being payed off\\n * @param repayAmount The amount to repay\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n function repayBorrowBehalfInternal(address borrower, uint repayAmount) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\\n return (fail(Error(error), FailureInfo.REPAY_BEHALF_ACCRUE_INTEREST_FAILED), 0);\\n }\\n\\n // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to\\n return repayBorrowFresh(msg.sender, borrower, repayAmount);\\n }\\n\\n /**\\n * @notice Borrows are repaid by another user (possibly the borrower).\\n * @param payer The account paying off the borrow\\n * @param borrower The account with the debt being payed off\\n * @param repayAmount The amount of undelrying tokens being returned\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n function repayBorrowFresh(address payer, address borrower, uint repayAmount) internal returns (uint, uint) {\\n /* Fail if repayBorrow not allowed */\\n uint allowed = comptroller.repayBorrowAllowed(address(this), payer, borrower, repayAmount);\\n if (allowed != 0) {\\n return (\\n failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REPAY_BORROW_COMPTROLLER_REJECTION, allowed),\\n 0\\n );\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.REPAY_BORROW_FRESHNESS_CHECK), 0);\\n }\\n\\n RepayBorrowLocalVars memory vars;\\n\\n /* We remember the original borrowerIndex for verification purposes */\\n vars.borrowerIndex = accountBorrows[borrower].interestIndex;\\n\\n /* We fetch the amount the borrower owes, with accumulated interest */\\n (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower);\\n if (vars.mathErr != MathError.NO_ERROR) {\\n return (\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\\n uint(vars.mathErr)\\n ),\\n 0\\n );\\n }\\n\\n // caps the repayAmount to the actual owed amount\\n vars.repayAmount = repayAmount >= vars.accountBorrows ? vars.accountBorrows : repayAmount;\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /*\\n * We call doTransferIn for the payer and the repayAmount\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * On success, the vToken holds an additional repayAmount of cash.\\n * doTransferIn reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n * it returns the amount actually transferred, in case of a fee.\\n */\\n vars.actualRepayAmount = doTransferIn(payer, vars.repayAmount);\\n\\n /*\\n * We calculate the new borrower and total borrow balances, failing on underflow:\\n * accountBorrowsNew = accountBorrows - actualRepayAmount\\n * totalBorrowsNew = totalBorrows - actualRepayAmount\\n */\\n (vars.mathErr, vars.accountBorrowsNew) = subUInt(vars.accountBorrows, vars.actualRepayAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.totalBorrowsNew) = subUInt(totalBorrows, vars.actualRepayAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n /* We write the previously calculated values into storage */\\n accountBorrows[borrower].principal = vars.accountBorrowsNew;\\n accountBorrows[borrower].interestIndex = borrowIndex;\\n totalBorrows = vars.totalBorrowsNew;\\n\\n /* We emit a RepayBorrow event */\\n emit RepayBorrow(payer, borrower, vars.actualRepayAmount, vars.accountBorrowsNew, vars.totalBorrowsNew);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.repayBorrowVerify(address(this), payer, borrower, vars.actualRepayAmount, vars.borrowerIndex);\\n\\n return (uint(Error.NO_ERROR), vars.actualRepayAmount);\\n }\\n\\n /**\\n * @notice The sender liquidates the borrowers collateral.\\n * The collateral seized is transferred to the liquidator.\\n * @param borrower The borrower of this vToken to be liquidated\\n * @param vTokenCollateral The market in which to seize collateral from the borrower\\n * @param repayAmount The amount of the underlying borrowed asset to repay\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n function liquidateBorrowInternal(\\n address borrower,\\n uint repayAmount,\\n VTokenInterface vTokenCollateral\\n ) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed\\n return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED), 0);\\n }\\n\\n error = vTokenCollateral.accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed\\n return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED), 0);\\n }\\n\\n // liquidateBorrowFresh emits borrow-specific logs on errors, so we don't need to\\n return liquidateBorrowFresh(msg.sender, borrower, repayAmount, vTokenCollateral);\\n }\\n\\n /**\\n * @notice The liquidator liquidates the borrowers collateral.\\n * The collateral seized is transferred to the liquidator.\\n * @param borrower The borrower of this vToken to be liquidated\\n * @param liquidator The address repaying the borrow and seizing collateral\\n * @param vTokenCollateral The market in which to seize collateral from the borrower\\n * @param repayAmount The amount of the underlying borrowed asset to repay\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n // solhint-disable-next-line code-complexity\\n function liquidateBorrowFresh(\\n address liquidator,\\n address borrower,\\n uint repayAmount,\\n VTokenInterface vTokenCollateral\\n ) internal returns (uint, uint) {\\n /* Fail if liquidate not allowed */\\n uint allowed = comptroller.liquidateBorrowAllowed(\\n address(this),\\n address(vTokenCollateral),\\n liquidator,\\n borrower,\\n repayAmount\\n );\\n if (allowed != 0) {\\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_COMPTROLLER_REJECTION, allowed), 0);\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_FRESHNESS_CHECK), 0);\\n }\\n\\n /* Verify vTokenCollateral market's block number equals current block number */\\n if (vTokenCollateral.accrualBlockNumber() != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_COLLATERAL_FRESHNESS_CHECK), 0);\\n }\\n\\n /* Fail if borrower = liquidator */\\n if (borrower == liquidator) {\\n return (fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_LIQUIDATOR_IS_BORROWER), 0);\\n }\\n\\n /* Fail if repayAmount = 0 */\\n if (repayAmount == 0) {\\n return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_ZERO), 0);\\n }\\n\\n /* Fail if repayAmount = type(uint256).max */\\n if (repayAmount == type(uint256).max) {\\n return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX), 0);\\n }\\n\\n /* Fail if repayBorrow fails */\\n (uint repayBorrowError, uint actualRepayAmount) = repayBorrowFresh(liquidator, borrower, repayAmount);\\n if (repayBorrowError != uint(Error.NO_ERROR)) {\\n return (fail(Error(repayBorrowError), FailureInfo.LIQUIDATE_REPAY_BORROW_FRESH_FAILED), 0);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We calculate the number of collateral tokens that will be seized */\\n (uint amountSeizeError, uint seizeTokens) = comptroller.liquidateCalculateSeizeTokens(\\n borrower,\\n address(this),\\n address(vTokenCollateral),\\n actualRepayAmount\\n );\\n\\n require(amountSeizeError == uint(Error.NO_ERROR), \\\"LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED\\\");\\n\\n /* Revert if borrower collateral token balance < seizeTokens */\\n require(vTokenCollateral.balanceOf(borrower) >= seizeTokens, \\\"LIQUIDATE_SEIZE_TOO_MUCH\\\");\\n\\n // If this is also the collateral, run seizeInternal to avoid re-entrancy, otherwise make an external call\\n uint seizeError;\\n if (address(vTokenCollateral) == address(this)) {\\n seizeError = seizeInternal(address(this), liquidator, borrower, seizeTokens);\\n } else {\\n seizeError = vTokenCollateral.seize(liquidator, borrower, seizeTokens);\\n }\\n\\n /* Revert if seize tokens fails (since we cannot be sure of side effects) */\\n require(seizeError == uint(Error.NO_ERROR), \\\"token seizure failed\\\");\\n\\n /* We emit a LiquidateBorrow event */\\n emit LiquidateBorrow(liquidator, borrower, actualRepayAmount, address(vTokenCollateral), seizeTokens);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.liquidateBorrowVerify(\\n address(this),\\n address(vTokenCollateral),\\n liquidator,\\n borrower,\\n actualRepayAmount,\\n seizeTokens\\n );\\n\\n return (uint(Error.NO_ERROR), actualRepayAmount);\\n }\\n\\n /**\\n * @notice Transfers collateral tokens (this market) to the liquidator.\\n * @dev Called only during an in-kind liquidation, or by liquidateBorrow during the liquidation of another vToken.\\n * Its absolutely critical to use msg.sender as the seizer vToken and not a parameter.\\n * @param seizerToken The contract seizing the collateral (i.e. borrowed vToken)\\n * @param liquidator The account receiving seized collateral\\n * @param borrower The account having collateral seized\\n * @param seizeTokens The number of vTokens to seize\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function seizeInternal(\\n address seizerToken,\\n address liquidator,\\n address borrower,\\n uint seizeTokens\\n ) internal returns (uint) {\\n /* Fail if seize not allowed */\\n uint allowed = comptroller.seizeAllowed(address(this), seizerToken, liquidator, borrower, seizeTokens);\\n if (allowed != 0) {\\n return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_SEIZE_COMPTROLLER_REJECTION, allowed);\\n }\\n\\n /* Fail if borrower = liquidator */\\n if (borrower == liquidator) {\\n return fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER);\\n }\\n\\n MathError mathErr;\\n uint borrowerTokensNew;\\n uint liquidatorTokensNew;\\n\\n /*\\n * We calculate the new borrower and liquidator token balances, failing on underflow/overflow:\\n * borrowerTokensNew = accountTokens[borrower] - seizeTokens\\n * liquidatorTokensNew = accountTokens[liquidator] + seizeTokens\\n */\\n (mathErr, borrowerTokensNew) = subUInt(accountTokens[borrower], seizeTokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED, uint(mathErr));\\n }\\n\\n (mathErr, liquidatorTokensNew) = addUInt(accountTokens[liquidator], seizeTokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED, uint(mathErr));\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We write the previously calculated values into storage */\\n accountTokens[borrower] = borrowerTokensNew;\\n accountTokens[liquidator] = liquidatorTokensNew;\\n\\n /* Emit a Transfer event */\\n emit Transfer(borrower, liquidator, seizeTokens);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.seizeVerify(address(this), seizerToken, liquidator, borrower, seizeTokens);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Sets a new reserve factor for the protocol (requires fresh interest accrual)\\n * @dev Governance function to set a new reserve factor\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _setReserveFactorFresh(uint newReserveFactorMantissa) internal returns (uint) {\\n // Verify market's block number equals current block number\\n if (accrualBlockNumber != block.number) {\\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_RESERVE_FACTOR_FRESH_CHECK);\\n }\\n\\n // Check newReserveFactor \\u2264 maxReserveFactor\\n if (newReserveFactorMantissa > reserveFactorMaxMantissa) {\\n return fail(Error.BAD_INPUT, FailureInfo.SET_RESERVE_FACTOR_BOUNDS_CHECK);\\n }\\n\\n uint oldReserveFactorMantissa = reserveFactorMantissa;\\n reserveFactorMantissa = newReserveFactorMantissa;\\n\\n emit NewReserveFactor(oldReserveFactorMantissa, newReserveFactorMantissa);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Accrues interest and adds reserves by transferring from `msg.sender`\\n * @param addAmount Amount of addition to reserves\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _addReservesInternal(uint addAmount) internal nonReentrant returns (uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed.\\n return fail(Error(error), FailureInfo.ADD_RESERVES_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // _addReservesFresh emits reserve-addition-specific logs on errors, so we don't need to.\\n (error, ) = _addReservesFresh(addAmount);\\n return error;\\n }\\n\\n /**\\n * @notice Add reserves by transferring from caller\\n * @dev Requires fresh interest accrual\\n * @param addAmount Amount of addition to reserves\\n * @return (uint, uint) An error code (0=success, otherwise a failure (see ErrorReporter.sol for details)) and the actual amount added, net token fees\\n */\\n function _addReservesFresh(uint addAmount) internal returns (uint, uint) {\\n // totalReserves + actualAddAmount\\n uint totalReservesNew;\\n uint actualAddAmount;\\n\\n // We fail gracefully unless market's block number equals current block number\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.ADD_RESERVES_FRESH_CHECK), actualAddAmount);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /*\\n * We call doTransferIn for the caller and the addAmount\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * On success, the vToken holds an additional addAmount of cash.\\n * doTransferIn reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n * it returns the amount actually transferred, in case of a fee.\\n */\\n\\n actualAddAmount = doTransferIn(msg.sender, addAmount);\\n\\n totalReservesNew = totalReserves + actualAddAmount;\\n\\n /* Revert on overflow */\\n require(totalReservesNew >= totalReserves, \\\"add reserves unexpected overflow\\\");\\n\\n // Store reserves[n+1] = reserves[n] + actualAddAmount\\n totalReserves = totalReservesNew;\\n\\n /* Emit NewReserves(admin, actualAddAmount, reserves[n+1]) */\\n emit ReservesAdded(msg.sender, actualAddAmount, totalReservesNew);\\n\\n /* Return (NO_ERROR, actualAddAmount) */\\n return (uint(Error.NO_ERROR), actualAddAmount);\\n }\\n\\n /**\\n * @notice Reduces reserves by transferring to protocol share reserve contract\\n * @dev Requires fresh interest accrual\\n * @param reduceAmount Amount of reduction to reserves\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _reduceReservesFresh(uint reduceAmount) internal virtual returns (uint) {\\n if (reduceAmount == 0) {\\n return uint(Error.NO_ERROR);\\n }\\n\\n // We fail gracefully unless market's block number equals current block number\\n if (accrualBlockNumber != block.number) {\\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDUCE_RESERVES_FRESH_CHECK);\\n }\\n\\n // Fail gracefully if protocol has insufficient underlying cash\\n if (getCashPrior() < reduceAmount) {\\n return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDUCE_RESERVES_CASH_NOT_AVAILABLE);\\n }\\n\\n // Check reduceAmount \\u2264 reserves[n] (totalReserves)\\n if (reduceAmount > totalReserves) {\\n return fail(Error.BAD_INPUT, FailureInfo.REDUCE_RESERVES_VALIDATION);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n // Store reserves[n+1] = reserves[n] - reduceAmount\\n totalReserves = totalReserves - reduceAmount;\\n\\n // doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n doTransferOut(protocolShareReserve, reduceAmount);\\n\\n IProtocolShareReserve(protocolShareReserve).updateAssetsState(\\n address(comptroller),\\n underlying,\\n IProtocolShareReserve.IncomeType.SPREAD\\n );\\n\\n emit ReservesReduced(protocolShareReserve, reduceAmount, totalReserves);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice updates the interest rate model (requires fresh interest accrual)\\n * @dev Governance function to update the interest rate model\\n * @param newInterestRateModel the new interest rate model to use\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _setInterestRateModelFresh(InterestRateModelV8 newInterestRateModel) internal returns (uint) {\\n // We fail gracefully unless market's block number equals current block number\\n if (accrualBlockNumber != block.number) {\\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_INTEREST_RATE_MODEL_FRESH_CHECK);\\n }\\n\\n // Ensure invoke newInterestRateModel.isInterestRateModel() returns true\\n require(newInterestRateModel.isInterestRateModel(), \\\"marker method returned false\\\");\\n\\n // Emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel)\\n emit NewMarketInterestRateModel(interestRateModel, newInterestRateModel);\\n\\n // Set the interest rate model to newInterestRateModel\\n interestRateModel = newInterestRateModel;\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /*** Safe Token ***/\\n\\n /**\\n * @dev Performs a transfer in, reverting upon failure. Returns the amount actually transferred to the protocol, in case of a fee.\\n * This may revert due to insufficient balance or insufficient allowance.\\n */\\n function doTransferIn(address from, uint amount) internal virtual returns (uint);\\n\\n /**\\n * @dev Performs a transfer out, ideally returning an explanatory error code upon failure rather than reverting.\\n * If caller has not called checked protocol's balance, may revert due to insufficient cash held in the contract.\\n * If caller has checked protocol's balance, and verified it is >= amount, this should not revert in normal conditions.\\n */\\n function doTransferOut(address payable to, uint amount) internal virtual;\\n\\n /**\\n * @notice Return the borrow balance of account based on stored data\\n * @param account The address whose balance should be calculated\\n * @return Tuple of error code and the calculated balance or 0 if error code is non-zero\\n */\\n function borrowBalanceStoredInternal(address account) internal view returns (MathError, uint) {\\n /* Note: we do not assert that the market is up to date */\\n MathError mathErr;\\n uint principalTimesIndex;\\n uint result;\\n\\n /* Get borrowBalance and borrowIndex */\\n BorrowSnapshot storage borrowSnapshot = accountBorrows[account];\\n\\n /* If borrowBalance = 0 then borrowIndex is likely also 0.\\n * Rather than failing the calculation with a division by 0, we immediately return 0 in this case.\\n */\\n if (borrowSnapshot.principal == 0) {\\n return (MathError.NO_ERROR, 0);\\n }\\n\\n /* Calculate new borrow balance using the interest index:\\n * recentBorrowBalance = borrower.borrowBalance * market.borrowIndex / borrower.borrowIndex\\n */\\n (mathErr, principalTimesIndex) = mulUInt(borrowSnapshot.principal, borrowIndex);\\n if (mathErr != MathError.NO_ERROR) {\\n return (mathErr, 0);\\n }\\n\\n (mathErr, result) = divUInt(principalTimesIndex, borrowSnapshot.interestIndex);\\n if (mathErr != MathError.NO_ERROR) {\\n return (mathErr, 0);\\n }\\n\\n return (MathError.NO_ERROR, result);\\n }\\n\\n /**\\n * @notice Calculates the exchange rate from the underlying to the vToken\\n * @dev This function does not accrue interest before calculating the exchange rate\\n * @return Tuple of error code and calculated exchange rate scaled by 1e18\\n */\\n function exchangeRateStoredInternal() internal view virtual returns (MathError, uint) {\\n uint _totalSupply = totalSupply;\\n if (_totalSupply == 0) {\\n /*\\n * If there are no tokens minted:\\n * exchangeRate = initialExchangeRate\\n */\\n return (MathError.NO_ERROR, initialExchangeRateMantissa);\\n } else {\\n /*\\n * Otherwise:\\n * exchangeRate = (totalCash + totalBorrows + flashLoanAmount - totalReserves) / totalSupply\\n */\\n uint totalCash = _getCashPriorWithFlashLoan();\\n uint cashPlusBorrowsMinusReserves;\\n Exp memory exchangeRate;\\n MathError mathErr;\\n\\n (mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(totalCash, totalBorrows, totalReserves);\\n if (mathErr != MathError.NO_ERROR) {\\n return (mathErr, 0);\\n }\\n\\n (mathErr, exchangeRate) = getExp(cashPlusBorrowsMinusReserves, _totalSupply);\\n if (mathErr != MathError.NO_ERROR) {\\n return (mathErr, 0);\\n }\\n\\n return (MathError.NO_ERROR, exchangeRate.mantissa);\\n }\\n }\\n\\n /**\\n * @notice Gets balance of this contract including active flash loans\\n * @return The quantity of underlying owned by this contract plus active flash loan amount\\n */\\n function _getCashPriorWithFlashLoan() internal view returns (uint) {\\n return getCashPrior() + flashLoanAmount;\\n }\\n\\n function ensureAllowed(string memory functionSig) private view {\\n require(\\n IAccessControlManagerV8(accessControlManager).isAllowedToCall(msg.sender, functionSig),\\n \\\"access denied\\\"\\n );\\n }\\n\\n function ensureAdmin(address caller_) private view {\\n require(caller_ == admin, \\\"Unauthorized\\\");\\n }\\n\\n function ensureNoMathError(MathError mErr) private pure {\\n require(mErr == MathError.NO_ERROR, \\\"math error\\\");\\n }\\n\\n function ensureNonZeroAddress(address address_) private pure {\\n require(address_ != address(0), \\\"zero address\\\");\\n }\\n\\n /*** Safe Token ***/\\n\\n /**\\n * @notice Gets balance of this contract in terms of the underlying\\n * @dev This excludes the value of the current message, if any\\n * @return The quantity of underlying owned by this contract\\n */\\n function getCashPrior() internal view virtual returns (uint);\\n}\\n\",\"keccak256\":\"0xa71b46dc3b3133980a99068c0e65a547317208f666196e28d38cc66a3808869b\",\"license\":\"BSD-3-Clause\"},\"contracts/Tokens/VTokens/VTokenInterfaces.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { ComptrollerInterface } from \\\"../../Comptroller/ComptrollerInterface.sol\\\";\\nimport { InterestRateModelV8 } from \\\"../../InterestRateModels/InterestRateModelV8.sol\\\";\\n\\ncontract VTokenStorageBase {\\n /**\\n * @notice Container for borrow balance information\\n * @member principal Total balance (with accrued interest), after applying the most recent balance-changing action\\n * @member interestIndex Global borrowIndex as of the most recent balance-changing action\\n */\\n struct BorrowSnapshot {\\n uint principal;\\n uint interestIndex;\\n }\\n\\n /**\\n * @dev Guard variable for re-entrancy checks\\n */\\n bool internal _notEntered;\\n\\n /**\\n * @notice EIP-20 token name for this token\\n */\\n string public name;\\n\\n /**\\n * @notice EIP-20 token symbol for this token\\n */\\n string public symbol;\\n\\n /**\\n * @notice EIP-20 token decimals for this token\\n */\\n uint8 public decimals;\\n\\n /**\\n * @notice Maximum borrow rate that can ever be applied (.0005% / block)\\n */\\n\\n uint internal constant borrowRateMaxMantissa = 0.0005e16;\\n\\n /**\\n * @notice Maximum fraction of interest that can be set aside for reserves\\n */\\n uint internal constant reserveFactorMaxMantissa = 1e18;\\n\\n /**\\n * @notice Administrator for this contract\\n */\\n address payable public admin;\\n\\n /**\\n * @notice Pending administrator for this contract\\n */\\n address payable public pendingAdmin;\\n\\n /**\\n * @notice Contract which oversees inter-vToken operations\\n */\\n ComptrollerInterface public comptroller;\\n\\n /**\\n * @notice Model which tells what the current interest rate should be\\n */\\n InterestRateModelV8 public interestRateModel;\\n\\n /**\\n * @notice Initial exchange rate used when minting the first VTokens (used when totalSupply = 0)\\n */\\n uint internal initialExchangeRateMantissa;\\n\\n /**\\n * @notice Fraction of interest currently set aside for reserves\\n */\\n uint public reserveFactorMantissa;\\n\\n /**\\n * @notice Block number that interest was last accrued at\\n */\\n uint public accrualBlockNumber;\\n\\n /**\\n * @notice Accumulator of the total earned interest rate since the opening of the market\\n */\\n uint public borrowIndex;\\n\\n /**\\n * @notice Total amount of outstanding borrows of the underlying in this market\\n */\\n uint public totalBorrows;\\n\\n /**\\n * @notice Total amount of reserves of the underlying held in this market\\n */\\n uint public totalReserves;\\n\\n /**\\n * @notice Total number of tokens in circulation\\n */\\n uint public totalSupply;\\n\\n /**\\n * @notice Official record of token balances for each account\\n */\\n mapping(address => uint) internal accountTokens;\\n\\n /**\\n * @notice Approved token transfer amounts on behalf of others\\n */\\n mapping(address => mapping(address => uint)) internal transferAllowances;\\n\\n /**\\n * @notice Mapping of account addresses to outstanding borrow balances\\n */\\n mapping(address => BorrowSnapshot) internal accountBorrows;\\n\\n /**\\n * @notice Underlying asset for this VToken\\n */\\n address public underlying;\\n\\n /**\\n * @notice Implementation address for this contract\\n */\\n address public implementation;\\n\\n /**\\n * @notice delta block after which reserves will be reduced\\n */\\n uint public reduceReservesBlockDelta;\\n\\n /**\\n * @notice last block number at which reserves were reduced\\n */\\n uint public reduceReservesBlockNumber;\\n\\n /**\\n * @notice address of protocol share reserve contract\\n */\\n address payable public protocolShareReserve;\\n\\n /**\\n * @notice address of accessControlManager\\n */\\n\\n address public accessControlManager;\\n}\\n\\ncontract VTokenStorage is VTokenStorageBase {\\n /**\\n * @notice flashLoan is enabled for this market or not\\n */\\n bool public isFlashLoanEnabled;\\n\\n /**\\n * @notice total fee percentage collected on flashLoan (scaled by 1e18)\\n */\\n uint256 public flashLoanFeeMantissa;\\n\\n /**\\n * @notice fee percentage of flashLoan that goes to protocol (scaled by 1e18)\\n */\\n uint256 public flashLoanProtocolShareMantissa;\\n\\n /**\\n * @notice Amount of flashLoan taken by the receiver\\n * @dev This is used to track the amount of flashLoan taken to correctly calculate the exchange rate\\n * during the flashLoan process. It is added to the total cash when calculating the exchange rate.\\n */\\n uint256 public flashLoanAmount;\\n\\n /**\\n * @notice Tracked internal cash balance, immune to direct token transfers (donation attacks)\\n * @dev Updated only via doTransferIn/doTransferOut. Must be initialized via sweepTokenAndSync() after upgrade.\\n */\\n uint256 public internalCash;\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[45] private __gap;\\n}\\n\\nabstract contract VTokenInterface is VTokenStorage {\\n /**\\n * @notice Indicator that this is a vToken contract (for inspection)\\n */\\n bool public constant isVToken = true;\\n\\n /*** Market Events ***/\\n\\n /**\\n * @notice Event emitted when interest is accrued\\n */\\n event AccrueInterest(uint cashPrior, uint interestAccumulated, uint borrowIndex, uint totalBorrows);\\n\\n /**\\n * @notice Event emitted when tokens are minted\\n */\\n event Mint(address minter, uint mintAmount, uint mintTokens, uint256 totalSupply);\\n\\n /**\\n * @notice Event emitted when tokens are minted behalf by payer to receiver\\n */\\n event MintBehalf(address payer, address receiver, uint mintAmount, uint mintTokens, uint256 totalSupply);\\n\\n /**\\n * @notice Event emitted when tokens are redeemed\\n */\\n event Redeem(address redeemer, uint redeemAmount, uint redeemTokens, uint256 totalSupply);\\n\\n /**\\n * @notice Event emitted when tokens are redeemed and fee is transferred\\n */\\n event RedeemFee(address redeemer, uint feeAmount, uint redeemTokens);\\n\\n /**\\n * @notice Event emitted when underlying is borrowed\\n */\\n event Borrow(address borrower, uint borrowAmount, uint accountBorrows, uint totalBorrows);\\n\\n /**\\n * @notice Event emitted when a borrow is repaid\\n */\\n event RepayBorrow(address payer, address borrower, uint repayAmount, uint accountBorrows, uint totalBorrows);\\n\\n /**\\n * @notice Event emitted when a borrow is liquidated\\n */\\n event LiquidateBorrow(\\n address liquidator,\\n address borrower,\\n uint repayAmount,\\n address vTokenCollateral,\\n uint seizeTokens\\n );\\n\\n /*** Admin Events ***/\\n\\n /**\\n * @notice Event emitted when pendingAdmin is changed\\n */\\n event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);\\n\\n /**\\n * @notice Event emitted when pendingAdmin is accepted, which means admin has been updated\\n */\\n event NewAdmin(address oldAdmin, address newAdmin);\\n\\n /**\\n * @notice Event emitted when comptroller is changed\\n */\\n event NewComptroller(ComptrollerInterface oldComptroller, ComptrollerInterface newComptroller);\\n\\n /**\\n * @notice Event emitted when interestRateModel is changed\\n */\\n event NewMarketInterestRateModel(\\n InterestRateModelV8 oldInterestRateModel,\\n InterestRateModelV8 newInterestRateModel\\n );\\n\\n /**\\n * @notice Event emitted when the reserve factor is changed\\n */\\n event NewReserveFactor(uint oldReserveFactorMantissa, uint newReserveFactorMantissa);\\n\\n /**\\n * @notice Event emitted when the reserves are added\\n */\\n event ReservesAdded(address benefactor, uint addAmount, uint newTotalReserves);\\n\\n /**\\n * @notice Event emitted when the reserves are reduced\\n */\\n event ReservesReduced(address protocolShareReserve, uint reduceAmount, uint newTotalReserves);\\n\\n /**\\n * @notice EIP20 Transfer event\\n */\\n event Transfer(address indexed from, address indexed to, uint amount);\\n\\n /**\\n * @notice EIP20 Approval event\\n */\\n event Approval(address indexed owner, address indexed spender, uint amount);\\n\\n /**\\n * @notice Event emitted when block delta for reduce reserves get updated\\n */\\n event NewReduceReservesBlockDelta(uint256 oldReduceReservesBlockDelta, uint256 newReduceReservesBlockDelta);\\n\\n /**\\n * @notice Event emitted when address of ProtocolShareReserve contract get updated\\n */\\n event NewProtocolShareReserve(address indexed oldProtocolShareReserve, address indexed newProtocolShareReserve);\\n\\n /**\\n * @notice Emitted when access control address is changed by admin\\n */\\n event NewAccessControlManager(address oldAccessControlAddress, address newAccessControlAddress);\\n\\n /**\\n * @notice Event emitted when flashLoanEnabled status is changed\\n */\\n event FlashLoanStatusChanged(bool previousStatus, bool newStatus);\\n\\n /**\\n * @notice Event emitted when asset is transferred to receiver\\n */\\n event TransferOutUnderlyingFlashLoan(address asset, address receiver, uint256 amount);\\n\\n /**\\n * @notice Event emitted when asset is transferred from sender and verified\\n */\\n event TransferInUnderlyingFlashLoan(\\n address indexed asset,\\n address indexed sender,\\n uint256 amount,\\n uint256 totalFee,\\n uint256 protocolFee\\n );\\n\\n /**\\n * @notice Event emitted when internalCash is synced with actual token balance\\n */\\n event CashSynced(uint256 oldInternalCash, uint256 newInternalCash);\\n\\n /**\\n * @notice Event emitted when excess tokens are swept by admin\\n */\\n event TokenSwept(address indexed recipient, uint256 amount);\\n\\n /**\\n * @notice Event emitted when flashLoan fee mantissa is updated\\n */\\n event FlashLoanFeeUpdated(\\n uint256 oldFlashLoanFeeMantissa,\\n uint256 newFlashLoanFeeMantissa,\\n uint256 oldFlashLoanProtocolShare,\\n uint256 newFlashLoanProtocolShare\\n );\\n\\n // @notice Thrown when comptroller is not valid\\n error InvalidComptroller();\\n\\n // @notice Thrown when there is already an active flashLoan\\n error FlashLoanAlreadyActive();\\n\\n /// @notice Thrown when flash loan fee exceeds maximum allowed\\n error FlashLoanFeeTooHigh(uint256 fee, uint256 maxFee);\\n\\n /// @notice Thrown when flash loan fee protocol share exceeds maximum allowed\\n error FlashLoanProtocolShareTooHigh(uint256 fee, uint256 maxFee);\\n\\n // @notice Thrown when the vToken does not have enough cash to lend\\n error InsufficientCash();\\n\\n /// @notice Thrown when the repayment amount is insufficient to cover the total fee\\n error InsufficientRepayment(uint256 actualAmount, uint256 requiredTotalFee);\\n\\n /*** User Interface ***/\\n\\n function transfer(address dst, uint amount) external virtual returns (bool);\\n\\n function transferFrom(address src, address dst, uint amount) external virtual returns (bool);\\n\\n function approve(address spender, uint amount) external virtual returns (bool);\\n\\n function balanceOfUnderlying(address owner) external virtual returns (uint);\\n\\n function totalBorrowsCurrent() external virtual returns (uint);\\n\\n function borrowBalanceCurrent(address account) external virtual returns (uint);\\n\\n function seize(address liquidator, address borrower, uint seizeTokens) external virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _setPendingAdmin(address payable newPendingAdmin) external virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _acceptAdmin() external virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _setReserveFactor(uint newReserveFactorMantissa) external virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _reduceReserves(uint reduceAmount) external virtual returns (uint);\\n\\n function balanceOf(address owner) external view virtual returns (uint);\\n\\n function allowance(address owner, address spender) external view virtual returns (uint);\\n\\n function getAccountSnapshot(address account) external view virtual returns (uint, uint, uint, uint);\\n\\n function borrowRatePerBlock() external view virtual returns (uint);\\n\\n function supplyRatePerBlock() external view virtual returns (uint);\\n\\n function getCash() external view virtual returns (uint);\\n\\n function exchangeRateCurrent() public virtual returns (uint);\\n\\n function accrueInterest() public virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _setComptroller(ComptrollerInterface newComptroller) public virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _setInterestRateModel(InterestRateModelV8 newInterestRateModel) public virtual returns (uint);\\n\\n function borrowBalanceStored(address account) public view virtual returns (uint);\\n\\n function exchangeRateStored() public view virtual returns (uint);\\n}\\n\\ninterface VBep20Interface {\\n /*** User Interface ***/\\n\\n function mint(uint mintAmount) external returns (uint);\\n\\n function mintBehalf(address receiver, uint mintAmount) external returns (uint);\\n\\n function redeem(uint redeemTokens) external returns (uint);\\n\\n function redeemUnderlying(uint redeemAmount) external returns (uint);\\n\\n function borrow(uint borrowAmount) external returns (uint);\\n\\n function repayBorrow(uint repayAmount) external returns (uint);\\n\\n function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint);\\n\\n function liquidateBorrow(\\n address borrower,\\n uint repayAmount,\\n VTokenInterface vTokenCollateral\\n ) external returns (uint);\\n\\n /*** Admin Functions ***/\\n\\n function _addReserves(uint addAmount) external returns (uint);\\n}\\n\\ninterface VDelegatorInterface {\\n /**\\n * @notice Emitted when implementation is changed\\n */\\n event NewImplementation(address oldImplementation, address newImplementation);\\n\\n /**\\n * @notice Called by the admin to update the implementation of the delegator\\n * @param implementation_ The address of the new implementation for delegation\\n * @param allowResign Flag to indicate whether to call _resignImplementation on the old implementation\\n * @param becomeImplementationData The encoded bytes data to be passed to _becomeImplementation\\n */\\n function _setImplementation(\\n address implementation_,\\n bool allowResign,\\n bytes memory becomeImplementationData\\n ) external;\\n}\\n\\ninterface VDelegateInterface {\\n /**\\n * @notice Called by the delegator on a delegate to initialize it for duty\\n * @dev Should revert if any issues arise which make it unfit for delegation\\n * @param data The encoded bytes data for any initialization\\n */\\n function _becomeImplementation(bytes memory data) external;\\n\\n /**\\n * @notice Called by the delegator on a delegate to forfeit its responsibility\\n */\\n function _resignImplementation() external;\\n}\\n\",\"keccak256\":\"0xa892e7d531228f2bdc92ddf37be5c43fc29a7d952335397dede149d3ac182017\",\"license\":\"BSD-3-Clause\"},\"contracts/Utils/CarefulMath.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\n/**\\n * @title Careful Math\\n * @author Venus\\n * @notice Derived from OpenZeppelin's SafeMath library\\n * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol\\n */\\ncontract CarefulMath {\\n /**\\n * @dev Possible error codes that we can return\\n */\\n enum MathError {\\n NO_ERROR,\\n DIVISION_BY_ZERO,\\n INTEGER_OVERFLOW,\\n INTEGER_UNDERFLOW\\n }\\n\\n /**\\n * @dev Multiplies two numbers, returns an error on overflow.\\n */\\n function mulUInt(uint a, uint b) internal pure returns (MathError, uint) {\\n if (a == 0) {\\n return (MathError.NO_ERROR, 0);\\n }\\n\\n uint c;\\n unchecked {\\n c = a * b;\\n }\\n\\n if (c / a != b) {\\n return (MathError.INTEGER_OVERFLOW, 0);\\n } else {\\n return (MathError.NO_ERROR, c);\\n }\\n }\\n\\n /**\\n * @dev Integer division of two numbers, truncating the quotient.\\n */\\n function divUInt(uint a, uint b) internal pure returns (MathError, uint) {\\n if (b == 0) {\\n return (MathError.DIVISION_BY_ZERO, 0);\\n }\\n\\n return (MathError.NO_ERROR, a / b);\\n }\\n\\n /**\\n * @dev Subtracts two numbers, returns an error on overflow (i.e. if subtrahend is greater than minuend).\\n */\\n function subUInt(uint a, uint b) internal pure returns (MathError, uint) {\\n if (b <= a) {\\n unchecked {\\n return (MathError.NO_ERROR, a - b);\\n }\\n } else {\\n return (MathError.INTEGER_UNDERFLOW, 0);\\n }\\n }\\n\\n /**\\n * @dev Adds two numbers, returns an error on overflow.\\n */\\n function addUInt(uint a, uint b) internal pure returns (MathError, uint) {\\n uint c;\\n unchecked {\\n c = a + b;\\n }\\n\\n if (c >= a) {\\n return (MathError.NO_ERROR, c);\\n } else {\\n return (MathError.INTEGER_OVERFLOW, 0);\\n }\\n }\\n\\n /**\\n * @dev add a and b and then subtract c\\n */\\n function addThenSubUInt(uint a, uint b, uint c) internal pure returns (MathError, uint) {\\n (MathError err0, uint sum) = addUInt(a, b);\\n\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, 0);\\n }\\n\\n return subUInt(sum, c);\\n }\\n}\\n\",\"keccak256\":\"0xb7ca049dc6f4a31c8994ad5fd2093b9f7f60c21495ff8386c7802ef13d9858a7\",\"license\":\"BSD-3-Clause\"},\"contracts/Utils/ErrorReporter.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { WeightFunction } from \\\"../Comptroller/Diamond/interfaces/IFacetBase.sol\\\";\\n\\ncontract ComptrollerErrorReporter {\\n /// @notice Thrown when You are already in the selected pool.\\n error AlreadyInSelectedPool();\\n\\n /// @notice Thrown when One or more of your assets are not compatible with the selected pool.\\n error IncompatibleBorrowedAssets();\\n\\n /// @notice Thrown when Switching to this pool would fail the liquidity check or lead to liquidation.\\n error LiquidityCheckFailed(uint256 errorCode, uint256 shortfall);\\n\\n /// @notice Thrown when trying to call pool-specific methods on the Core Pool\\n error InvalidOperationForCorePool();\\n\\n /// @notice Thrown when input array lengths do not match\\n error ArrayLengthMismatch();\\n\\n /// @notice Thrown when market trying to add in a pool is not listed in the core pool\\n error MarketNotListedInCorePool();\\n\\n /// @notice Thrown when market is not set in the _poolMarkets mapping\\n error MarketConfigNotFound();\\n\\n /// @notice Thrown when borrowing is not allowed in the selected pool for a given market.\\n error BorrowNotAllowedInPool();\\n\\n /// @notice Thrown when trying to remove a market that is not listed in the given pool.\\n error PoolMarketNotFound(uint96 poolId, address vToken);\\n\\n /// @notice Thrown when a given pool ID does not exist\\n error PoolDoesNotExist(uint96 poolId);\\n\\n /// @notice Thrown when the pool label is empty\\n error EmptyPoolLabel();\\n\\n /// @notice Thrown when a vToken is already listed in the specified pool\\n error MarketAlreadyListed(uint96 poolId, address vToken);\\n\\n /// @notice Thrown when an invalid weighting strategy is provided\\n error InvalidWeightingStrategy(WeightFunction strategy);\\n\\n // @notice Thrown when no assets are requested for flash loan\\n error NoAssetsRequested();\\n\\n // @notice Thrown when invalid flash loan parameters are provided\\n error InvalidFlashLoanParams();\\n\\n // @notice Thrown when flash loan is not enabled on the vToken\\n error FlashLoanNotEnabled();\\n\\n // @notice Thrown when the sender is not authorized to use flashloan onBehalfOf\\n error SenderNotAuthorizedForFlashLoan(address sender);\\n\\n // @notice Thrown when the onBehalfOf didn't approve the contract that receives flashloan\\n error NotAnApprovedDelegate();\\n\\n // @notice Thrown when executeOperation on the receiver contract fails\\n error ExecuteFlashLoanFailed();\\n\\n // @notice Thrown when the requested amount is zero\\n error InvalidAmount();\\n\\n // @notice Thrown when failing to create a debt position in mode 1\\n error FailedToCreateDebtPosition();\\n\\n /// @notice Thrown when attempting to interact with an inactive pool\\n error InactivePool(uint96 poolId);\\n\\n /// @notice Thrown when repayment amount is insufficient to cover the fee\\n error NotEnoughRepayment(uint256 repaid, uint256 required);\\n\\n /// @notice Thrown when the vToken market is not listed\\n error MarketNotListed(address vToken);\\n\\n /// @notice Thrown when flash loans are paused system-wide\\n error FlashLoanPausedSystemWide();\\n\\n /// @notice Thrown when too many assets are requested in a single flash loan\\n error TooManyAssetsRequested(uint256 requested, uint256 maximum);\\n\\n enum Error {\\n NO_ERROR,\\n UNAUTHORIZED,\\n COMPTROLLER_MISMATCH,\\n INSUFFICIENT_SHORTFALL,\\n INSUFFICIENT_LIQUIDITY,\\n INVALID_CLOSE_FACTOR,\\n INVALID_COLLATERAL_FACTOR,\\n INVALID_LIQUIDATION_INCENTIVE,\\n MARKET_NOT_ENTERED, // no longer possible\\n MARKET_NOT_LISTED,\\n MARKET_ALREADY_LISTED,\\n MATH_ERROR,\\n NONZERO_BORROW_BALANCE,\\n PRICE_ERROR,\\n REJECTION,\\n SNAPSHOT_ERROR,\\n TOO_MANY_ASSETS,\\n TOO_MUCH_REPAY,\\n INSUFFICIENT_BALANCE_FOR_VAI,\\n MARKET_NOT_COLLATERAL,\\n INVALID_LIQUIDATION_THRESHOLD\\n }\\n\\n enum FailureInfo {\\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\\n ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK,\\n EXIT_MARKET_BALANCE_OWED,\\n EXIT_MARKET_REJECTION,\\n SET_CLOSE_FACTOR_OWNER_CHECK,\\n SET_CLOSE_FACTOR_VALIDATION,\\n SET_COLLATERAL_FACTOR_OWNER_CHECK,\\n SET_COLLATERAL_FACTOR_NO_EXISTS,\\n SET_COLLATERAL_FACTOR_VALIDATION,\\n SET_COLLATERAL_FACTOR_WITHOUT_PRICE,\\n SET_IMPLEMENTATION_OWNER_CHECK,\\n SET_LIQUIDATION_INCENTIVE_OWNER_CHECK,\\n SET_LIQUIDATION_INCENTIVE_VALIDATION,\\n SET_MAX_ASSETS_OWNER_CHECK,\\n SET_PENDING_ADMIN_OWNER_CHECK,\\n SET_PENDING_IMPLEMENTATION_OWNER_CHECK,\\n SET_PRICE_ORACLE_OWNER_CHECK,\\n SUPPORT_MARKET_EXISTS,\\n SUPPORT_MARKET_OWNER_CHECK,\\n SET_PAUSE_GUARDIAN_OWNER_CHECK,\\n SET_VAI_MINT_RATE_CHECK,\\n SET_VAICONTROLLER_OWNER_CHECK,\\n SET_MINTED_VAI_REJECTION,\\n SET_TREASURY_OWNER_CHECK,\\n UNLIST_MARKET_NOT_LISTED,\\n SET_LIQUIDATION_THRESHOLD_VALIDATION,\\n COLLATERAL_FACTOR_GREATER_THAN_LIQUIDATION_THRESHOLD\\n }\\n\\n /**\\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\\n **/\\n event Failure(uint error, uint info, uint detail);\\n\\n /**\\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\\n */\\n function fail(Error err, FailureInfo info) internal returns (uint) {\\n emit Failure(uint(err), uint(info), 0);\\n\\n return uint(err);\\n }\\n\\n /**\\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\\n */\\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\\n emit Failure(uint(err), uint(info), opaqueError);\\n\\n return uint(err);\\n }\\n}\\n\\ncontract TokenErrorReporter {\\n enum Error {\\n NO_ERROR,\\n UNAUTHORIZED,\\n BAD_INPUT,\\n COMPTROLLER_REJECTION,\\n COMPTROLLER_CALCULATION_ERROR,\\n INTEREST_RATE_MODEL_ERROR,\\n INVALID_ACCOUNT_PAIR,\\n INVALID_CLOSE_AMOUNT_REQUESTED,\\n INVALID_COLLATERAL_FACTOR,\\n MATH_ERROR,\\n MARKET_NOT_FRESH,\\n MARKET_NOT_LISTED,\\n TOKEN_INSUFFICIENT_ALLOWANCE,\\n TOKEN_INSUFFICIENT_BALANCE,\\n TOKEN_INSUFFICIENT_CASH,\\n TOKEN_TRANSFER_IN_FAILED,\\n TOKEN_TRANSFER_OUT_FAILED,\\n TOKEN_PRICE_ERROR\\n }\\n\\n /*\\n * Note: FailureInfo (but not Error) is kept in alphabetical order\\n * This is because FailureInfo grows significantly faster, and\\n * the order of Error has some meaning, while the order of FailureInfo\\n * is entirely arbitrary.\\n */\\n enum FailureInfo {\\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\\n ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED,\\n ACCRUE_INTEREST_BORROW_RATE_CALCULATION_FAILED,\\n ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED,\\n ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED,\\n ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED,\\n ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED,\\n BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\\n BORROW_ACCRUE_INTEREST_FAILED,\\n BORROW_CASH_NOT_AVAILABLE,\\n BORROW_FRESHNESS_CHECK,\\n BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED,\\n BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED,\\n BORROW_MARKET_NOT_LISTED,\\n BORROW_COMPTROLLER_REJECTION,\\n LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED,\\n LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED,\\n LIQUIDATE_COLLATERAL_FRESHNESS_CHECK,\\n LIQUIDATE_COMPTROLLER_REJECTION,\\n LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED,\\n LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX,\\n LIQUIDATE_CLOSE_AMOUNT_IS_ZERO,\\n LIQUIDATE_FRESHNESS_CHECK,\\n LIQUIDATE_LIQUIDATOR_IS_BORROWER,\\n LIQUIDATE_REPAY_BORROW_FRESH_FAILED,\\n LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED,\\n LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED,\\n LIQUIDATE_SEIZE_COMPTROLLER_REJECTION,\\n LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER,\\n LIQUIDATE_SEIZE_TOO_MUCH,\\n MINT_ACCRUE_INTEREST_FAILED,\\n MINT_COMPTROLLER_REJECTION,\\n MINT_EXCHANGE_CALCULATION_FAILED,\\n MINT_EXCHANGE_RATE_READ_FAILED,\\n MINT_FRESHNESS_CHECK,\\n MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED,\\n MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED,\\n MINT_TRANSFER_IN_FAILED,\\n MINT_TRANSFER_IN_NOT_POSSIBLE,\\n REDEEM_ACCRUE_INTEREST_FAILED,\\n REDEEM_COMPTROLLER_REJECTION,\\n REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED,\\n REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED,\\n REDEEM_EXCHANGE_RATE_READ_FAILED,\\n REDEEM_FRESHNESS_CHECK,\\n REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED,\\n REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED,\\n REDEEM_TRANSFER_OUT_NOT_POSSIBLE,\\n REDUCE_RESERVES_ACCRUE_INTEREST_FAILED,\\n REDUCE_RESERVES_ADMIN_CHECK,\\n REDUCE_RESERVES_CASH_NOT_AVAILABLE,\\n REDUCE_RESERVES_FRESH_CHECK,\\n REDUCE_RESERVES_VALIDATION,\\n REPAY_BEHALF_ACCRUE_INTEREST_FAILED,\\n REPAY_BORROW_ACCRUE_INTEREST_FAILED,\\n REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\\n REPAY_BORROW_COMPTROLLER_REJECTION,\\n REPAY_BORROW_FRESHNESS_CHECK,\\n REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED,\\n REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED,\\n REPAY_BORROW_TRANSFER_IN_NOT_POSSIBLE,\\n SET_COLLATERAL_FACTOR_OWNER_CHECK,\\n SET_COLLATERAL_FACTOR_VALIDATION,\\n SET_COMPTROLLER_OWNER_CHECK,\\n SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED,\\n SET_INTEREST_RATE_MODEL_FRESH_CHECK,\\n SET_INTEREST_RATE_MODEL_OWNER_CHECK,\\n SET_MAX_ASSETS_OWNER_CHECK,\\n SET_ORACLE_MARKET_NOT_LISTED,\\n SET_PENDING_ADMIN_OWNER_CHECK,\\n SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED,\\n SET_RESERVE_FACTOR_ADMIN_CHECK,\\n SET_RESERVE_FACTOR_FRESH_CHECK,\\n SET_RESERVE_FACTOR_BOUNDS_CHECK,\\n TRANSFER_COMPTROLLER_REJECTION,\\n TRANSFER_NOT_ALLOWED,\\n TRANSFER_NOT_ENOUGH,\\n TRANSFER_TOO_MUCH,\\n ADD_RESERVES_ACCRUE_INTEREST_FAILED,\\n ADD_RESERVES_FRESH_CHECK,\\n ADD_RESERVES_TRANSFER_IN_NOT_POSSIBLE,\\n TOKEN_GET_UNDERLYING_PRICE_ERROR,\\n REPAY_VAI_COMPTROLLER_REJECTION,\\n REPAY_VAI_FRESHNESS_CHECK,\\n VAI_MINT_EXCHANGE_CALCULATION_FAILED,\\n SFT_MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED\\n }\\n\\n /**\\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\\n **/\\n event Failure(uint error, uint info, uint detail);\\n\\n /**\\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\\n */\\n function fail(Error err, FailureInfo info) internal returns (uint) {\\n emit Failure(uint(err), uint(info), 0);\\n\\n return uint(err);\\n }\\n\\n /**\\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\\n */\\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\\n emit Failure(uint(err), uint(info), opaqueError);\\n\\n return uint(err);\\n }\\n}\\n\\ncontract VAIControllerErrorReporter {\\n enum Error {\\n NO_ERROR,\\n UNAUTHORIZED, // The sender is not authorized to perform this action.\\n REJECTION, // The action would violate the comptroller, vaicontroller policy.\\n SNAPSHOT_ERROR, // The comptroller could not get the account borrows and exchange rate from the market.\\n PRICE_ERROR, // The comptroller could not obtain a required price of an asset.\\n MATH_ERROR, // A math calculation error occurred.\\n INSUFFICIENT_BALANCE_FOR_VAI // Caller does not have sufficient balance to mint VAI.\\n }\\n\\n enum FailureInfo {\\n SET_PENDING_ADMIN_OWNER_CHECK,\\n SET_PENDING_IMPLEMENTATION_OWNER_CHECK,\\n SET_COMPTROLLER_OWNER_CHECK,\\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\\n ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK,\\n VAI_MINT_REJECTION,\\n VAI_BURN_REJECTION,\\n VAI_LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED,\\n VAI_LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED,\\n VAI_LIQUIDATE_COLLATERAL_FRESHNESS_CHECK,\\n VAI_LIQUIDATE_COMPTROLLER_REJECTION,\\n VAI_LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED,\\n VAI_LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX,\\n VAI_LIQUIDATE_CLOSE_AMOUNT_IS_ZERO,\\n VAI_LIQUIDATE_FRESHNESS_CHECK,\\n VAI_LIQUIDATE_LIQUIDATOR_IS_BORROWER,\\n VAI_LIQUIDATE_REPAY_BORROW_FRESH_FAILED,\\n VAI_LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED,\\n VAI_LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED,\\n VAI_LIQUIDATE_SEIZE_COMPTROLLER_REJECTION,\\n VAI_LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER,\\n VAI_LIQUIDATE_SEIZE_TOO_MUCH,\\n MINT_FEE_CALCULATION_FAILED,\\n SET_TREASURY_OWNER_CHECK\\n }\\n\\n /**\\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\\n **/\\n event Failure(uint error, uint info, uint detail);\\n\\n /**\\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\\n */\\n function fail(Error err, FailureInfo info) internal returns (uint) {\\n emit Failure(uint(err), uint(info), 0);\\n\\n return uint(err);\\n }\\n\\n /**\\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\\n */\\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\\n emit Failure(uint(err), uint(info), opaqueError);\\n\\n return uint(err);\\n }\\n}\\n\",\"keccak256\":\"0x9b498db9a2d5f5178b48e5724849f7561c1c0100c1faef4d6dbd741f599861f2\",\"license\":\"BSD-3-Clause\"},\"contracts/Utils/Exponential.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { CarefulMath } from \\\"./CarefulMath.sol\\\";\\nimport { ExponentialNoError } from \\\"./ExponentialNoError.sol\\\";\\n\\n/**\\n * @title Exponential module for storing fixed-precision decimals\\n * @author Venus\\n * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places.\\n * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is:\\n * `Exp({mantissa: 5100000000000000000})`.\\n */\\ncontract Exponential is CarefulMath, ExponentialNoError {\\n /**\\n * @dev Creates an exponential from numerator and denominator values.\\n * Note: Returns an error if (`num` * 10e18) > MAX_INT,\\n * or if `denom` is zero.\\n */\\n function getExp(uint num, uint denom) internal pure returns (MathError, Exp memory) {\\n (MathError err0, uint scaledNumerator) = mulUInt(num, expScale);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n\\n (MathError err1, uint rational) = divUInt(scaledNumerator, denom);\\n if (err1 != MathError.NO_ERROR) {\\n return (err1, Exp({ mantissa: 0 }));\\n }\\n\\n return (MathError.NO_ERROR, Exp({ mantissa: rational }));\\n }\\n\\n /**\\n * @dev Adds two exponentials, returning a new exponential.\\n */\\n function addExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\\n (MathError error, uint result) = addUInt(a.mantissa, b.mantissa);\\n\\n return (error, Exp({ mantissa: result }));\\n }\\n\\n /**\\n * @dev Subtracts two exponentials, returning a new exponential.\\n */\\n function subExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\\n (MathError error, uint result) = subUInt(a.mantissa, b.mantissa);\\n\\n return (error, Exp({ mantissa: result }));\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, returning a new Exp.\\n */\\n function mulScalar(Exp memory a, uint scalar) internal pure returns (MathError, Exp memory) {\\n (MathError err0, uint scaledMantissa) = mulUInt(a.mantissa, scalar);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n\\n return (MathError.NO_ERROR, Exp({ mantissa: scaledMantissa }));\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer.\\n */\\n function mulScalarTruncate(Exp memory a, uint scalar) internal pure returns (MathError, uint) {\\n (MathError err, Exp memory product) = mulScalar(a, scalar);\\n if (err != MathError.NO_ERROR) {\\n return (err, 0);\\n }\\n\\n return (MathError.NO_ERROR, truncate(product));\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer.\\n */\\n function mulScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) internal pure returns (MathError, uint) {\\n (MathError err, Exp memory product) = mulScalar(a, scalar);\\n if (err != MathError.NO_ERROR) {\\n return (err, 0);\\n }\\n\\n return addUInt(truncate(product), addend);\\n }\\n\\n /**\\n * @dev Divide an Exp by a scalar, returning a new Exp.\\n */\\n function divScalar(Exp memory a, uint scalar) internal pure returns (MathError, Exp memory) {\\n (MathError err0, uint descaledMantissa) = divUInt(a.mantissa, scalar);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n\\n return (MathError.NO_ERROR, Exp({ mantissa: descaledMantissa }));\\n }\\n\\n /**\\n * @dev Divide a scalar by an Exp, returning a new Exp.\\n */\\n function divScalarByExp(uint scalar, Exp memory divisor) internal pure returns (MathError, Exp memory) {\\n /*\\n We are doing this as:\\n getExp(mulUInt(expScale, scalar), divisor.mantissa)\\n\\n How it works:\\n Exp = a / b;\\n Scalar = s;\\n `s / (a / b)` = `b * s / a` and since for an Exp `a = mantissa, b = expScale`\\n */\\n (MathError err0, uint numerator) = mulUInt(expScale, scalar);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n return getExp(numerator, divisor.mantissa);\\n }\\n\\n /**\\n * @dev Divide a scalar by an Exp, then truncate to return an unsigned integer.\\n */\\n function divScalarByExpTruncate(uint scalar, Exp memory divisor) internal pure returns (MathError, uint) {\\n (MathError err, Exp memory fraction) = divScalarByExp(scalar, divisor);\\n if (err != MathError.NO_ERROR) {\\n return (err, 0);\\n }\\n\\n return (MathError.NO_ERROR, truncate(fraction));\\n }\\n\\n /**\\n * @dev Multiplies two exponentials, returning a new exponential.\\n */\\n function mulExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\\n (MathError err0, uint doubleScaledProduct) = mulUInt(a.mantissa, b.mantissa);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n\\n // We add half the scale before dividing so that we get rounding instead of truncation.\\n // See \\\"Listing 6\\\" and text above it at https://accu.org/index.php/journals/1717\\n // Without this change, a result like 6.6...e-19 will be truncated to 0 instead of being rounded to 1e-18.\\n (MathError err1, uint doubleScaledProductWithHalfScale) = addUInt(halfExpScale, doubleScaledProduct);\\n if (err1 != MathError.NO_ERROR) {\\n return (err1, Exp({ mantissa: 0 }));\\n }\\n\\n (MathError err2, uint product) = divUInt(doubleScaledProductWithHalfScale, expScale);\\n // The only error `div` can return is MathError.DIVISION_BY_ZERO but we control `expScale` and it is not zero.\\n assert(err2 == MathError.NO_ERROR);\\n\\n return (MathError.NO_ERROR, Exp({ mantissa: product }));\\n }\\n\\n /**\\n * @dev Multiplies two exponentials given their mantissas, returning a new exponential.\\n */\\n function mulExp(uint a, uint b) internal pure returns (MathError, Exp memory) {\\n return mulExp(Exp({ mantissa: a }), Exp({ mantissa: b }));\\n }\\n\\n /**\\n * @dev Multiplies three exponentials, returning a new exponential.\\n */\\n function mulExp3(Exp memory a, Exp memory b, Exp memory c) internal pure returns (MathError, Exp memory) {\\n (MathError err, Exp memory ab) = mulExp(a, b);\\n if (err != MathError.NO_ERROR) {\\n return (err, ab);\\n }\\n return mulExp(ab, c);\\n }\\n\\n /**\\n * @dev Divides two exponentials, returning a new exponential.\\n * (a/scale) / (b/scale) = (a/scale) * (scale/b) = a/b,\\n * which we can scale as an Exp by calling getExp(a.mantissa, b.mantissa)\\n */\\n function divExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\\n return getExp(a.mantissa, b.mantissa);\\n }\\n}\\n\",\"keccak256\":\"0x6b9b293a09a3ec69ac16f58dce449a553b44121eb9aad666a777d8f6f4ae83f0\",\"license\":\"BSD-3-Clause\"},\"contracts/Utils/ExponentialNoError.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\n/**\\n * @title Exponential module for storing fixed-precision decimals\\n * @author Compound\\n * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places.\\n * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is:\\n * `Exp({mantissa: 5100000000000000000})`.\\n */\\ncontract ExponentialNoError {\\n uint internal constant expScale = 1e18;\\n uint internal constant doubleScale = 1e36;\\n uint internal constant halfExpScale = expScale / 2;\\n uint internal constant mantissaOne = expScale;\\n\\n struct Exp {\\n uint mantissa;\\n }\\n\\n struct Double {\\n uint mantissa;\\n }\\n\\n /**\\n * @dev Truncates the given exp to a whole number value.\\n * For example, truncate(Exp{mantissa: 15 * expScale}) = 15\\n */\\n function truncate(Exp memory exp) internal pure returns (uint) {\\n // Note: We are not using careful math here as we're performing a division that cannot fail\\n return exp.mantissa / expScale;\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer.\\n */\\n function mul_ScalarTruncate(Exp memory a, uint scalar) internal pure returns (uint) {\\n Exp memory product = mul_(a, scalar);\\n return truncate(product);\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer.\\n */\\n function mul_ScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) internal pure returns (uint) {\\n Exp memory product = mul_(a, scalar);\\n return add_(truncate(product), addend);\\n }\\n\\n /**\\n * @dev Checks if first Exp is less than second Exp.\\n */\\n function lessThanExp(Exp memory left, Exp memory right) internal pure returns (bool) {\\n return left.mantissa < right.mantissa;\\n }\\n\\n /**\\n * @dev Checks if left Exp <= right Exp.\\n */\\n function lessThanOrEqualExp(Exp memory left, Exp memory right) internal pure returns (bool) {\\n return left.mantissa <= right.mantissa;\\n }\\n\\n /**\\n * @dev Checks if left Exp > right Exp.\\n */\\n function greaterThanExp(Exp memory left, Exp memory right) internal pure returns (bool) {\\n return left.mantissa > right.mantissa;\\n }\\n\\n /**\\n * @dev returns true if Exp is exactly zero\\n */\\n function isZeroExp(Exp memory value) internal pure returns (bool) {\\n return value.mantissa == 0;\\n }\\n\\n function safe224(uint n, string memory errorMessage) internal pure returns (uint224) {\\n require(n < 2 ** 224, errorMessage);\\n return uint224(n);\\n }\\n\\n function safe32(uint n, string memory errorMessage) internal pure returns (uint32) {\\n require(n < 2 ** 32, errorMessage);\\n return uint32(n);\\n }\\n\\n function add_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: add_(a.mantissa, b.mantissa) });\\n }\\n\\n function add_(Double memory a, Double memory b) internal pure returns (Double memory) {\\n return Double({ mantissa: add_(a.mantissa, b.mantissa) });\\n }\\n\\n function add_(uint a, uint b) internal pure returns (uint) {\\n return add_(a, b, \\\"addition overflow\\\");\\n }\\n\\n function add_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\\n uint c = a + b;\\n require(c >= a, errorMessage);\\n return c;\\n }\\n\\n function sub_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: sub_(a.mantissa, b.mantissa) });\\n }\\n\\n function sub_(Double memory a, Double memory b) internal pure returns (Double memory) {\\n return Double({ mantissa: sub_(a.mantissa, b.mantissa) });\\n }\\n\\n function sub_(uint a, uint b) internal pure returns (uint) {\\n return sub_(a, b, \\\"subtraction underflow\\\");\\n }\\n\\n function sub_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\\n require(b <= a, errorMessage);\\n return a - b;\\n }\\n\\n function mul_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: mul_(a.mantissa, b.mantissa) / expScale });\\n }\\n\\n function mul_(Exp memory a, uint b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: mul_(a.mantissa, b) });\\n }\\n\\n function mul_(uint a, Exp memory b) internal pure returns (uint) {\\n return mul_(a, b.mantissa) / expScale;\\n }\\n\\n function mul_(Double memory a, Double memory b) internal pure returns (Double memory) {\\n return Double({ mantissa: mul_(a.mantissa, b.mantissa) / doubleScale });\\n }\\n\\n function mul_(Double memory a, uint b) internal pure returns (Double memory) {\\n return Double({ mantissa: mul_(a.mantissa, b) });\\n }\\n\\n function mul_(uint a, Double memory b) internal pure returns (uint) {\\n return mul_(a, b.mantissa) / doubleScale;\\n }\\n\\n function mul_(uint a, uint b) internal pure returns (uint) {\\n return mul_(a, b, \\\"multiplication overflow\\\");\\n }\\n\\n function mul_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\\n if (a == 0 || b == 0) {\\n return 0;\\n }\\n uint c = a * b;\\n require(c / a == b, errorMessage);\\n return c;\\n }\\n\\n function div_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: div_(mul_(a.mantissa, expScale), b.mantissa) });\\n }\\n\\n function div_(Exp memory a, uint b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: div_(a.mantissa, b) });\\n }\\n\\n function div_(uint a, Exp memory b) internal pure returns (uint) {\\n return div_(mul_(a, expScale), b.mantissa);\\n }\\n\\n function div_(Double memory a, Double memory b) internal pure returns (Double memory) {\\n return Double({ mantissa: div_(mul_(a.mantissa, doubleScale), b.mantissa) });\\n }\\n\\n function div_(Double memory a, uint b) internal pure returns (Double memory) {\\n return Double({ mantissa: div_(a.mantissa, b) });\\n }\\n\\n function div_(uint a, Double memory b) internal pure returns (uint) {\\n return div_(mul_(a, doubleScale), b.mantissa);\\n }\\n\\n function div_(uint a, uint b) internal pure returns (uint) {\\n return div_(a, b, \\\"divide by zero\\\");\\n }\\n\\n function div_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\\n require(b > 0, errorMessage);\\n return a / b;\\n }\\n\\n function fraction(uint a, uint b) internal pure returns (Double memory) {\\n return Double({ mantissa: div_(mul_(a, doubleScale), b) });\\n }\\n}\\n\",\"keccak256\":\"0xce75802a56763fb96b2046b374127f080a425cfe3634670767ec040ccb6ea7e0\",\"license\":\"BSD-3-Clause\"},\"contracts/external/IProtocolShareReserve.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\ninterface IProtocolShareReserve {\\n enum IncomeType {\\n SPREAD,\\n LIQUIDATION,\\n ERC4626_WRAPPER_REWARDS,\\n FLASHLOAN\\n }\\n\\n function updateAssetsState(address comptroller, address asset, IncomeType incomeType) external;\\n}\\n\",\"keccak256\":\"0x029861e068e9c4d802650eaf6dd8cfed0980d2ce9a45e6fcc1e1e628ecc1f523\",\"license\":\"BSD-3-Clause\"},\"contracts/external/IWBNB.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { IERC20Upgradeable } from \\\"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\\\";\\n\\ninterface IWBNB is IERC20Upgradeable {\\n function deposit() external payable;\\n\\n function withdraw(uint256 amount) external;\\n}\\n\",\"keccak256\":\"0x2602fa3bfe075d8ab4608464f02154788a09d0848b3b6e656b0712b176989193\",\"license\":\"BSD-3-Clause\"}},\"version\":1}", - "bytecode": "0x610100604052348015610010575f80fd5b50604051612b90380380612b9083398101604081905261002f9161017d565b61003884610083565b61004183610083565b61004a82610083565b61005381610083565b6001600160a01b0380851660805283811660a05282811660c052811660e05261007a6100ad565b505050506101d9565b6001600160a01b0381166100aa576040516342bcdf7f60e11b815260040160405180910390fd5b50565b5f54610100900460ff16156101185760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b5f5460ff90811614610167575f805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b6001600160a01b03811681146100aa575f80fd5b5f805f8060808587031215610190575f80fd5b845161019b81610169565b60208601519094506101ac81610169565b60408601519093506101bd81610169565b60608601519092506101ce81610169565b939692955090935050565b60805160a05160c05160e0516129306102605f395f81816102b801528181610fb7015281816112db015261148301525f818161012f01528181610adf0152610d6601525f818161017f01528181610a8a01528181610d1f01528181610f1101526111fb01525f81816101d301528181610b7201528181610c9b01526113d801526129305ff3fe608060405260043610610113575f3560e01c80638d72647e1161009d578063e30c397811610062578063e30c397814610354578063e58b51e914610371578063f2fde38b14610390578063f3d7d282146103af578063fc08f9f6146103dd575f80fd5b80638d72647e146102a75780638da5cb5b146102da5780639646f3ea146102f7578063c3c6467414610316578063c4d66de814610335575f80fd5b806360d6acc3116100e357806360d6acc3146101f557806362c06767146102225780636d70f7ae14610241578063715018a61461027f57806379ba509714610293575f80fd5b806314ba4a101461011e57806333e1567f1461016e578063558a7297146101a15780635fe3b567146101c2575f80fd5b3661011a57005b5f80fd5b348015610129575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b348015610179575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b3480156101ac575f80fd5b506101c06101bb3660046120e1565b61040a565b005b3480156101cd575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b348015610200575f80fd5b5061021461020f366004612118565b61047a565b604051908152602001610165565b34801561022d575f80fd5b506101c061023c366004612157565b610630565b34801561024c575f80fd5b5061026f61025b366004612195565b60c96020525f908152604090205460ff1681565b6040519015158152602001610165565b34801561028a575f80fd5b506101c06106b0565b34801561029e575f80fd5b506101c06106c3565b3480156102b2575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b3480156102e5575f80fd5b506033546001600160a01b0316610151565b348015610302575f80fd5b506101c06103113660046121b0565b61073d565b348015610321575f80fd5b506101c06103303660046120e1565b610806565b348015610340575f80fd5b506101c061034f366004612195565b61086e565b34801561035f575f80fd5b506065546001600160a01b0316610151565b34801561037c575f80fd5b506101c061038b366004612118565b610994565b34801561039b575f80fd5b506101c06103aa366004612195565b610c1c565b3480156103ba575f80fd5b5061026f6103c9366004612195565b60ca6020525f908152604090205460ff1681565b3480156103e8575f80fd5b506103fc6103f7366004612260565b610c8d565b60405161016592919061237b565b610412611080565b61041b826110da565b6001600160a01b0382165f81815260c96020908152604091829020805460ff191685151590811790915591519182527f1a594081ae893ab78e67d9b9e843547318164322d32c65369d78a96172d9dc8f91015b60405180910390a25050565b5f61048d6033546001600160a01b031690565b6001600160a01b0316336001600160a01b0316141580156104bd5750335f90815260c9602052604090205460ff16155b156104db57604051631f0853c160e21b815260040160405180910390fd5b6104e3611101565b61050c6104f660a0840160808501612195565b610507610100850160e08601612195565b61115a565b8160c001355f0361053057604051630a1c302560e21b815260040160405180910390fd5b816101400135421115610568576040516302a07ebf60e31b815261014083013560048201524260248201526044015b60405180910390fd5b5f61057a61057584612552565b6111f6565b909250905061058f6040840160208501612195565b6001600160a01b03166105a86060850160408601612195565b6001600160a01b03166105be6020860186612195565b6001600160a01b03167fbfd65b4425967651bb217826b6511e83ef29c5c5ad92f273bb22e7507ed16afb866060013585875f6040516106189493929190938452602084019290925260408301521515606082015260800190565b60405180910390a45061062b6001609755565b919050565b610638611080565b610641836110da565b61064a826110da565b61065e6001600160a01b0384168383611829565b816001600160a01b0316836001600160a01b03167f7b09c29f9106defeccc9ac3b823f3aad0b470d120e5df7aed033b5c43a4bf718836040516106a391815260200190565b60405180910390a3505050565b6106b8611080565b6106c15f611891565b565b60655433906001600160a01b031681146107315760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b606482015260840161055f565b61073a81611891565b50565b610745611080565b61074e826110da565b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f8114610797576040519150601f19603f3d011682016040523d82523d5f602084013e61079c565b606091505b50509050806107be57604051633d2cec6f60e21b815260040160405180910390fd5b826001600160a01b03167f1aedae30ba52b882825adc6b3ed1febc9a717c20eaca0e2c50fd510e4bdc4cd6836040516107f991815260200190565b60405180910390a2505050565b61080e611080565b610817826110da565b6001600160a01b0382165f81815260ca6020908152604091829020805460ff191685151590811790915591519182527f8443a265af33599abe6c9ed0c3cabd8e2a6c5b8ea35c1d1ed40c513242f95d45910161046e565b5f54610100900460ff161580801561088c57505f54600160ff909116105b806108a55750303b1580156108a557505f5460ff166001145b6109085760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161055f565b5f805460ff191660011790558015610929575f805461ff0019166101001790555b610932826110da565b61093a6118aa565b6109426118d8565b61094b82611891565b8015610990575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6033546001600160a01b031633148015906109be5750335f90815260c9602052604090205460ff16155b156109dc57604051631f0853c160e21b815260040160405180910390fd5b6109e4611101565b610a086109f760a0830160808401612195565b610507610100840160e08501612195565b8060c001355f03610a2c57604051630a1c302560e21b815260040160405180910390fd5b806101400135421115610a5f576040516302a07ebf60e31b8152610140820135600482015242602482015260440161055f565b6040805160018082528183019092525f91602080830190803683370190505090506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016610aba6040840160208501612195565b6001600160a01b031614610add57610ad86040830160208401612195565b610aff565b7f00000000000000000000000000000000000000000000000000000000000000005b815f81518110610b1157610b1161255d565b6001600160a01b0392909216602092830291909101909101526040805160018082528183019092525f918160200160208202803683370190505090508260600135815f81518110610b6457610b6461255d565b6020026020010181815250507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316635544ed9c3030858588604051602001610bb491906125db565b6040516020818303038152906040526040518663ffffffff1660e01b8152600401610be3959493929190612743565b5f604051808303815f87803b158015610bfa575f80fd5b505af1158015610c0c573d5f803e3d5ffd5b50505050505061073a6001609755565b610c24611080565b606580546001600160a01b0383166001600160a01b03199091168117909155610c556033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b5f6060336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610cd95760405163880ed0f360e01b815260040160405180910390fd5b6001600160a01b0386163014610d0d576040516322c7df1960e21b81526001600160a01b038716600482015260240161055f565b5f610d1a848601866127ce565b90505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031682602001516001600160a01b031614610d64578160200151610d86565b7f00000000000000000000000000000000000000000000000000000000000000005b9050806001600160a01b03168e8e5f818110610da457610da461255d565b9050602002016020810190610db99190612195565b6001600160a01b031614610de05760405163e60249cb60e01b815260040160405180910390fd5b505f80610dec836111f6565b604080516001808252818301909252929450909250602080830190803683370190505093508a8a5f818110610e2357610e2361255d565b905060200201358d8d5f818110610e3c57610e3c61255d565b90506020020135610e4d9190612814565b845f81518110610e5f57610e5f61255d565b602002602001018181525050835f81518110610e7d57610e7d61255d565b6020026020010151821015610ecb5781845f81518110610e9f57610e9f61255d565b602002602001015160405163f447a23960e01b815260040161055f929190918252602082015260400190565b610fe78f8f5f818110610ee057610ee061255d565b9050602002016020810190610ef59190612195565b855f81518110610f0757610f0761255d565b60200260200101517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031686602001516001600160a01b031614610fb55785602001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f8c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fb09190612827565b610fd7565b7f00000000000000000000000000000000000000000000000000000000000000005b6001600160a01b03169190611906565b82602001516001600160a01b031683604001516001600160a01b0316845f01516001600160a01b03167fbfd65b4425967651bb217826b6511e83ef29c5c5ad92f273bb22e7507ed16afb8660600151858760016040516110629493929190938452602084019290925260408301521515606082015260800190565b60405180910390a4600194505050509a509a98505050505050505050565b6033546001600160a01b031633146106c15760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161055f565b6001600160a01b03811661073a576040516342bcdf7f60e11b815260040160405180910390fd5b6002609754036111535760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015260640161055f565b6002609755565b6001600160a01b0382165f90815260ca602052604090205460ff1661119d5760405163c665406f60e01b81526001600160a01b038316600482015260240161055f565b6001600160a01b038116158015906111cd57506001600160a01b0381165f90815260ca602052604090205460ff16155b156109905760405163c665406f60e01b81526001600160a01b038216600482015260240161055f565b5f805f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031684602001516001600160a01b031614905080801561124c575060e08401516001600160a01b0316155b1561126a57604051631a7a563960e21b815260040160405180910390fd5b5f816112d95784602001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112b0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112d49190612827565b6112fb565b7f00000000000000000000000000000000000000000000000000000000000000005b90505f85604001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa15801561133e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113629190612827565b60408088015190516370a0823160e01b81523060048201529192505f916001600160a01b03909116906370a0823190602401602060405180830381865afa1580156113af573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113d39190612842565b90505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316639bb27d626040518163ffffffff1660e01b8152600401602060405180830381865afa158015611432573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114569190612827565b9050611461816110da565b8415611566576060880151604051632e1a7d4d60e01b815260048101919091527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690632e1a7d4d906024015f604051808303815f87803b1580156114cc575f80fd5b505af11580156114de573d5f803e3d5ffd5b505050506060880151602089015189516040808c01519051630c9fae0f60e31b81526001600160a01b03938416600482015291831660248301526044820184905282166064820152908316916364fd7078916084015f604051808303818588803b15801561154a575f80fd5b505af115801561155c573d5f803e3d5ffd5b5050505050611615565b6060880151611581906001600160a01b038616908390611906565b6020880151885160608a01516040808c01519051630c9fae0f60e31b81526001600160a01b0394851660048201529284166024840152604483019190915282166064820152908216906364fd7078906084015f604051808303815f87803b1580156115ea575f80fd5b505af11580156115fc573d5f803e3d5ffd5b50611615925050506001600160a01b038516825f611906565b60408089015190516370a0823160e01b81523060048201525f9184916001600160a01b03909116906370a0823190602401602060405180830381865afa158015611661573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116859190612842565b61168f9190612859565b6040516370a0823160e01b81523060048201529091505f906001600160a01b038616906370a0823190602401602060405180830381865afa1580156116d6573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116fa9190612842565b90505f8a604001516001600160a01b031663db006a75846040518263ffffffff1660e01b815260040161172f91815260200190565b6020604051808303815f875af115801561174b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061176f9190612842565b905080156117935760405163eeddaac560e01b81526004810182905260240161055f565b6040516370a0823160e01b815230600482015282906001600160a01b038816906370a0823190602401602060405180830381865afa1580156117d7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117fb9190612842565b6118059190612859565b985061181387878b8e61199a565b99505050505050505050915091565b6001609755565b6040516001600160a01b03831660248201526044810182905261188c90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152611c67565b505050565b606580546001600160a01b031916905561073a81611d3a565b5f54610100900460ff166118d05760405162461bcd60e51b815260040161055f9061286c565b6106c1611d8b565b5f54610100900460ff166118fe5760405162461bcd60e51b815260040161055f9061286c565b6106c1611dba565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b1790526119578482611de0565b611994576040516001600160a01b03841660248201525f604482015261198a90859063095ea7b360e01b90606401611855565b6119948482611c67565b50505050565b6040516370a0823160e01b81523060048201525f9081906001600160a01b038716906370a0823190602401602060405180830381865afa1580156119e0573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a049190612842565b60e08401519091506001600160a01b0316611a3257611a2d8584608001518560a0015187611e83565b611bb5565b6101208301516001600160a01b03161580611a635750856001600160a01b03168361012001516001600160a01b0316145b80611a845750846001600160a01b03168361012001516001600160a01b0316145b15611aa257604051631a7a563960e21b815260040160405180910390fd5b6101208301516040516370a0823160e01b81523060048201525f906001600160a01b038316906370a0823190602401602060405180830381865afa158015611aec573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b109190612842565b9050611b268786608001518760a0015189611e83565b6040516370a0823160e01b81523060048201525f9082906001600160a01b038516906370a0823190602401602060405180830381865afa158015611b6c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b909190612842565b611b9a9190612859565b9050611bb1838760e0015188610100015184611e83565b5050505b6040516370a0823160e01b815230600482015281906001600160a01b038816906370a0823190602401602060405180830381865afa158015611bf9573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c1d9190612842565b611c279190612859565b91508260c00151821015611c5e5760c083015160405163f447a23960e01b815261055f918491600401918252602082015260400190565b50949350505050565b5f611cbb826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316611f2b9092919063ffffffff16565b905080515f1480611cdb575080806020019051810190611cdb91906128b7565b61188c5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161055f565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f54610100900460ff16611db15760405162461bcd60e51b815260040161055f9061286c565b6106c133611891565b5f54610100900460ff166118225760405162461bcd60e51b815260040161055f9061286c565b5f805f846001600160a01b031684604051611dfb91906128d2565b5f604051808303815f865af19150503d805f8114611e34576040519150601f19603f3d011682016040523d82523d5f602084013e611e39565b606091505b5091509150818015611e63575080511580611e63575080806020019051810190611e6391906128b7565b8015611e7857506001600160a01b0385163b15155b925050505b92915050565b611e976001600160a01b0385168483611906565b5f836001600160a01b031683604051611eb091906128d2565b5f604051808303815f865af19150503d805f8114611ee9576040519150601f19603f3d011682016040523d82523d5f602084013e611eee565b606091505b5050905080611f105760405163081ceff360e41b815260040160405180910390fd5b611f246001600160a01b038616855f611906565b5050505050565b6060611f3984845f85611f41565b949350505050565b606082471015611fa25760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840161055f565b5f80866001600160a01b03168587604051611fbd91906128d2565b5f6040518083038185875af1925050503d805f8114611ff7576040519150601f19603f3d011682016040523d82523d5f602084013e611ffc565b606091505b509150915061200d87838387612018565b979650505050505050565b606083156120865782515f0361207f576001600160a01b0385163b61207f5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161055f565b5081611f39565b611f39838381511561209b5781518083602001fd5b8060405162461bcd60e51b815260040161055f91906128e8565b6001600160a01b038116811461073a575f80fd5b803561062b816120b5565b801515811461073a575f80fd5b5f80604083850312156120f2575f80fd5b82356120fd816120b5565b9150602083013561210d816120d4565b809150509250929050565b5f60208284031215612128575f80fd5b813567ffffffffffffffff81111561213e575f80fd5b82016101608185031215612150575f80fd5b9392505050565b5f805f60608486031215612169575f80fd5b8335612174816120b5565b92506020840135612184816120b5565b929592945050506040919091013590565b5f602082840312156121a5575f80fd5b8135612150816120b5565b5f80604083850312156121c1575f80fd5b82356121cc816120b5565b946020939093013593505050565b5f8083601f8401126121ea575f80fd5b50813567ffffffffffffffff811115612201575f80fd5b6020830191508360208260051b850101111561221b575f80fd5b9250929050565b5f8083601f840112612232575f80fd5b50813567ffffffffffffffff811115612249575f80fd5b60208301915083602082850101111561221b575f80fd5b5f805f805f805f805f8060c08b8d031215612279575f80fd5b8a3567ffffffffffffffff80821115612290575f80fd5b61229c8e838f016121da565b909c509a5060208d01359150808211156122b4575f80fd5b6122c08e838f016121da565b909a50985060408d01359150808211156122d8575f80fd5b6122e48e838f016121da565b90985096508691506122f860608e016120c9565b955061230660808e016120c9565b945060a08d013591508082111561231b575f80fd5b506123288d828e01612222565b915080935050809150509295989b9194979a5092959850565b5f815180845260208085019450602084015f5b8381101561237057815187529582019590820190600101612354565b509495945050505050565b8215158152604060208201525f611f396040830184612341565b634e487b7160e01b5f52604160045260245ffd5b604051610160810167ffffffffffffffff811182821017156123cd576123cd612395565b60405290565b5f82601f8301126123e2575f80fd5b813567ffffffffffffffff808211156123fd576123fd612395565b604051601f8301601f19908116603f0116810190828211818310171561242557612425612395565b8160405283815286602085880101111561243d575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f610160828403121561246d575f80fd5b6124756123a9565b9050612480826120c9565b815261248e602083016120c9565b602082015261249f604083016120c9565b6040820152606082013560608201526124ba608083016120c9565b608082015260a082013567ffffffffffffffff808211156124d9575f80fd5b6124e5858386016123d3565b60a084015260c084013560c084015261250060e085016120c9565b60e08401526101009150818401358181111561251a575f80fd5b612526868287016123d3565b8385015250505061012061253b8184016120c9565b818301525061014080830135818301525092915050565b5f611e7d368361245c565b634e487b7160e01b5f52603260045260245ffd5b5f808335601e19843603018112612586575f80fd5b830160208101925035905067ffffffffffffffff8111156125a5575f80fd5b80360382131561221b575f80fd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b602081526125fc602082016125ef846120c9565b6001600160a01b03169052565b5f612609602084016120c9565b6001600160a01b038116604084015250612625604084016120c9565b6001600160a01b0381166060840152506060830135608083015261264b608084016120c9565b6001600160a01b03811660a08401525061266860a0840184612571565b6101608060c0860152612680610180860183856125b3565b925060c086013560e086015261269860e087016120c9565b91506101006126b1818701846001600160a01b03169052565b6126bd81880188612571565b93509050610120601f1987860301818801526126da8585846125b3565b94506126e78189016120c9565b93505050610140612702818701846001600160a01b03169052565b9590950135939094019290925250919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f60a0820160018060a01b0380891684526020818916602086015260a0604086015282885180855260c08701915060208a0194505f5b81811015612797578551851683529483019491830191600101612779565b505085810360608701526127ab8189612341565b935050505082810360808401526127c28185612715565b98975050505050505050565b5f602082840312156127de575f80fd5b813567ffffffffffffffff8111156127f4575f80fd5b611f398482850161245c565b634e487b7160e01b5f52601160045260245ffd5b80820180821115611e7d57611e7d612800565b5f60208284031215612837575f80fd5b8151612150816120b5565b5f60208284031215612852575f80fd5b5051919050565b81810381811115611e7d57611e7d612800565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f602082840312156128c7575f80fd5b8151612150816120d4565b5f82518060208501845e5f920191825250919050565b602081525f612150602083018461271556fea2646970667358221220bd07941a77f491ec603d4823289eb0f70dbf4bd640d52af7e41ca27357b38eb264736f6c63430008190033", - "deployedBytecode": "0x608060405260043610610113575f3560e01c80638d72647e1161009d578063e30c397811610062578063e30c397814610354578063e58b51e914610371578063f2fde38b14610390578063f3d7d282146103af578063fc08f9f6146103dd575f80fd5b80638d72647e146102a75780638da5cb5b146102da5780639646f3ea146102f7578063c3c6467414610316578063c4d66de814610335575f80fd5b806360d6acc3116100e357806360d6acc3146101f557806362c06767146102225780636d70f7ae14610241578063715018a61461027f57806379ba509714610293575f80fd5b806314ba4a101461011e57806333e1567f1461016e578063558a7297146101a15780635fe3b567146101c2575f80fd5b3661011a57005b5f80fd5b348015610129575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b348015610179575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b3480156101ac575f80fd5b506101c06101bb3660046120e1565b61040a565b005b3480156101cd575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b348015610200575f80fd5b5061021461020f366004612118565b61047a565b604051908152602001610165565b34801561022d575f80fd5b506101c061023c366004612157565b610630565b34801561024c575f80fd5b5061026f61025b366004612195565b60c96020525f908152604090205460ff1681565b6040519015158152602001610165565b34801561028a575f80fd5b506101c06106b0565b34801561029e575f80fd5b506101c06106c3565b3480156102b2575f80fd5b506101517f000000000000000000000000000000000000000000000000000000000000000081565b3480156102e5575f80fd5b506033546001600160a01b0316610151565b348015610302575f80fd5b506101c06103113660046121b0565b61073d565b348015610321575f80fd5b506101c06103303660046120e1565b610806565b348015610340575f80fd5b506101c061034f366004612195565b61086e565b34801561035f575f80fd5b506065546001600160a01b0316610151565b34801561037c575f80fd5b506101c061038b366004612118565b610994565b34801561039b575f80fd5b506101c06103aa366004612195565b610c1c565b3480156103ba575f80fd5b5061026f6103c9366004612195565b60ca6020525f908152604090205460ff1681565b3480156103e8575f80fd5b506103fc6103f7366004612260565b610c8d565b60405161016592919061237b565b610412611080565b61041b826110da565b6001600160a01b0382165f81815260c96020908152604091829020805460ff191685151590811790915591519182527f1a594081ae893ab78e67d9b9e843547318164322d32c65369d78a96172d9dc8f91015b60405180910390a25050565b5f61048d6033546001600160a01b031690565b6001600160a01b0316336001600160a01b0316141580156104bd5750335f90815260c9602052604090205460ff16155b156104db57604051631f0853c160e21b815260040160405180910390fd5b6104e3611101565b61050c6104f660a0840160808501612195565b610507610100850160e08601612195565b61115a565b8160c001355f0361053057604051630a1c302560e21b815260040160405180910390fd5b816101400135421115610568576040516302a07ebf60e31b815261014083013560048201524260248201526044015b60405180910390fd5b5f61057a61057584612552565b6111f6565b909250905061058f6040840160208501612195565b6001600160a01b03166105a86060850160408601612195565b6001600160a01b03166105be6020860186612195565b6001600160a01b03167fbfd65b4425967651bb217826b6511e83ef29c5c5ad92f273bb22e7507ed16afb866060013585875f6040516106189493929190938452602084019290925260408301521515606082015260800190565b60405180910390a45061062b6001609755565b919050565b610638611080565b610641836110da565b61064a826110da565b61065e6001600160a01b0384168383611829565b816001600160a01b0316836001600160a01b03167f7b09c29f9106defeccc9ac3b823f3aad0b470d120e5df7aed033b5c43a4bf718836040516106a391815260200190565b60405180910390a3505050565b6106b8611080565b6106c15f611891565b565b60655433906001600160a01b031681146107315760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b606482015260840161055f565b61073a81611891565b50565b610745611080565b61074e826110da565b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f8114610797576040519150601f19603f3d011682016040523d82523d5f602084013e61079c565b606091505b50509050806107be57604051633d2cec6f60e21b815260040160405180910390fd5b826001600160a01b03167f1aedae30ba52b882825adc6b3ed1febc9a717c20eaca0e2c50fd510e4bdc4cd6836040516107f991815260200190565b60405180910390a2505050565b61080e611080565b610817826110da565b6001600160a01b0382165f81815260ca6020908152604091829020805460ff191685151590811790915591519182527f8443a265af33599abe6c9ed0c3cabd8e2a6c5b8ea35c1d1ed40c513242f95d45910161046e565b5f54610100900460ff161580801561088c57505f54600160ff909116105b806108a55750303b1580156108a557505f5460ff166001145b6109085760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161055f565b5f805460ff191660011790558015610929575f805461ff0019166101001790555b610932826110da565b61093a6118aa565b6109426118d8565b61094b82611891565b8015610990575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6033546001600160a01b031633148015906109be5750335f90815260c9602052604090205460ff16155b156109dc57604051631f0853c160e21b815260040160405180910390fd5b6109e4611101565b610a086109f760a0830160808401612195565b610507610100840160e08501612195565b8060c001355f03610a2c57604051630a1c302560e21b815260040160405180910390fd5b806101400135421115610a5f576040516302a07ebf60e31b8152610140820135600482015242602482015260440161055f565b6040805160018082528183019092525f91602080830190803683370190505090506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016610aba6040840160208501612195565b6001600160a01b031614610add57610ad86040830160208401612195565b610aff565b7f00000000000000000000000000000000000000000000000000000000000000005b815f81518110610b1157610b1161255d565b6001600160a01b0392909216602092830291909101909101526040805160018082528183019092525f918160200160208202803683370190505090508260600135815f81518110610b6457610b6461255d565b6020026020010181815250507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316635544ed9c3030858588604051602001610bb491906125db565b6040516020818303038152906040526040518663ffffffff1660e01b8152600401610be3959493929190612743565b5f604051808303815f87803b158015610bfa575f80fd5b505af1158015610c0c573d5f803e3d5ffd5b50505050505061073a6001609755565b610c24611080565b606580546001600160a01b0383166001600160a01b03199091168117909155610c556033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b5f6060336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610cd95760405163880ed0f360e01b815260040160405180910390fd5b6001600160a01b0386163014610d0d576040516322c7df1960e21b81526001600160a01b038716600482015260240161055f565b5f610d1a848601866127ce565b90505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031682602001516001600160a01b031614610d64578160200151610d86565b7f00000000000000000000000000000000000000000000000000000000000000005b9050806001600160a01b03168e8e5f818110610da457610da461255d565b9050602002016020810190610db99190612195565b6001600160a01b031614610de05760405163e60249cb60e01b815260040160405180910390fd5b505f80610dec836111f6565b604080516001808252818301909252929450909250602080830190803683370190505093508a8a5f818110610e2357610e2361255d565b905060200201358d8d5f818110610e3c57610e3c61255d565b90506020020135610e4d9190612814565b845f81518110610e5f57610e5f61255d565b602002602001018181525050835f81518110610e7d57610e7d61255d565b6020026020010151821015610ecb5781845f81518110610e9f57610e9f61255d565b602002602001015160405163f447a23960e01b815260040161055f929190918252602082015260400190565b610fe78f8f5f818110610ee057610ee061255d565b9050602002016020810190610ef59190612195565b855f81518110610f0757610f0761255d565b60200260200101517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031686602001516001600160a01b031614610fb55785602001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f8c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fb09190612827565b610fd7565b7f00000000000000000000000000000000000000000000000000000000000000005b6001600160a01b03169190611906565b82602001516001600160a01b031683604001516001600160a01b0316845f01516001600160a01b03167fbfd65b4425967651bb217826b6511e83ef29c5c5ad92f273bb22e7507ed16afb8660600151858760016040516110629493929190938452602084019290925260408301521515606082015260800190565b60405180910390a4600194505050509a509a98505050505050505050565b6033546001600160a01b031633146106c15760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161055f565b6001600160a01b03811661073a576040516342bcdf7f60e11b815260040160405180910390fd5b6002609754036111535760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015260640161055f565b6002609755565b6001600160a01b0382165f90815260ca602052604090205460ff1661119d5760405163c665406f60e01b81526001600160a01b038316600482015260240161055f565b6001600160a01b038116158015906111cd57506001600160a01b0381165f90815260ca602052604090205460ff16155b156109905760405163c665406f60e01b81526001600160a01b038216600482015260240161055f565b5f805f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031684602001516001600160a01b031614905080801561124c575060e08401516001600160a01b0316155b1561126a57604051631a7a563960e21b815260040160405180910390fd5b5f816112d95784602001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112b0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112d49190612827565b6112fb565b7f00000000000000000000000000000000000000000000000000000000000000005b90505f85604001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa15801561133e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113629190612827565b60408088015190516370a0823160e01b81523060048201529192505f916001600160a01b03909116906370a0823190602401602060405180830381865afa1580156113af573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113d39190612842565b90505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316639bb27d626040518163ffffffff1660e01b8152600401602060405180830381865afa158015611432573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114569190612827565b9050611461816110da565b8415611566576060880151604051632e1a7d4d60e01b815260048101919091527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690632e1a7d4d906024015f604051808303815f87803b1580156114cc575f80fd5b505af11580156114de573d5f803e3d5ffd5b505050506060880151602089015189516040808c01519051630c9fae0f60e31b81526001600160a01b03938416600482015291831660248301526044820184905282166064820152908316916364fd7078916084015f604051808303818588803b15801561154a575f80fd5b505af115801561155c573d5f803e3d5ffd5b5050505050611615565b6060880151611581906001600160a01b038616908390611906565b6020880151885160608a01516040808c01519051630c9fae0f60e31b81526001600160a01b0394851660048201529284166024840152604483019190915282166064820152908216906364fd7078906084015f604051808303815f87803b1580156115ea575f80fd5b505af11580156115fc573d5f803e3d5ffd5b50611615925050506001600160a01b038516825f611906565b60408089015190516370a0823160e01b81523060048201525f9184916001600160a01b03909116906370a0823190602401602060405180830381865afa158015611661573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116859190612842565b61168f9190612859565b6040516370a0823160e01b81523060048201529091505f906001600160a01b038616906370a0823190602401602060405180830381865afa1580156116d6573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116fa9190612842565b90505f8a604001516001600160a01b031663db006a75846040518263ffffffff1660e01b815260040161172f91815260200190565b6020604051808303815f875af115801561174b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061176f9190612842565b905080156117935760405163eeddaac560e01b81526004810182905260240161055f565b6040516370a0823160e01b815230600482015282906001600160a01b038816906370a0823190602401602060405180830381865afa1580156117d7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117fb9190612842565b6118059190612859565b985061181387878b8e61199a565b99505050505050505050915091565b6001609755565b6040516001600160a01b03831660248201526044810182905261188c90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152611c67565b505050565b606580546001600160a01b031916905561073a81611d3a565b5f54610100900460ff166118d05760405162461bcd60e51b815260040161055f9061286c565b6106c1611d8b565b5f54610100900460ff166118fe5760405162461bcd60e51b815260040161055f9061286c565b6106c1611dba565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b1790526119578482611de0565b611994576040516001600160a01b03841660248201525f604482015261198a90859063095ea7b360e01b90606401611855565b6119948482611c67565b50505050565b6040516370a0823160e01b81523060048201525f9081906001600160a01b038716906370a0823190602401602060405180830381865afa1580156119e0573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a049190612842565b60e08401519091506001600160a01b0316611a3257611a2d8584608001518560a0015187611e83565b611bb5565b6101208301516001600160a01b03161580611a635750856001600160a01b03168361012001516001600160a01b0316145b80611a845750846001600160a01b03168361012001516001600160a01b0316145b15611aa257604051631a7a563960e21b815260040160405180910390fd5b6101208301516040516370a0823160e01b81523060048201525f906001600160a01b038316906370a0823190602401602060405180830381865afa158015611aec573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b109190612842565b9050611b268786608001518760a0015189611e83565b6040516370a0823160e01b81523060048201525f9082906001600160a01b038516906370a0823190602401602060405180830381865afa158015611b6c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b909190612842565b611b9a9190612859565b9050611bb1838760e0015188610100015184611e83565b5050505b6040516370a0823160e01b815230600482015281906001600160a01b038816906370a0823190602401602060405180830381865afa158015611bf9573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c1d9190612842565b611c279190612859565b91508260c00151821015611c5e5760c083015160405163f447a23960e01b815261055f918491600401918252602082015260400190565b50949350505050565b5f611cbb826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316611f2b9092919063ffffffff16565b905080515f1480611cdb575080806020019051810190611cdb91906128b7565b61188c5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161055f565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f54610100900460ff16611db15760405162461bcd60e51b815260040161055f9061286c565b6106c133611891565b5f54610100900460ff166118225760405162461bcd60e51b815260040161055f9061286c565b5f805f846001600160a01b031684604051611dfb91906128d2565b5f604051808303815f865af19150503d805f8114611e34576040519150601f19603f3d011682016040523d82523d5f602084013e611e39565b606091505b5091509150818015611e63575080511580611e63575080806020019051810190611e6391906128b7565b8015611e7857506001600160a01b0385163b15155b925050505b92915050565b611e976001600160a01b0385168483611906565b5f836001600160a01b031683604051611eb091906128d2565b5f604051808303815f865af19150503d805f8114611ee9576040519150601f19603f3d011682016040523d82523d5f602084013e611eee565b606091505b5050905080611f105760405163081ceff360e41b815260040160405180910390fd5b611f246001600160a01b038616855f611906565b5050505050565b6060611f3984845f85611f41565b949350505050565b606082471015611fa25760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840161055f565b5f80866001600160a01b03168587604051611fbd91906128d2565b5f6040518083038185875af1925050503d805f8114611ff7576040519150601f19603f3d011682016040523d82523d5f602084013e611ffc565b606091505b509150915061200d87838387612018565b979650505050505050565b606083156120865782515f0361207f576001600160a01b0385163b61207f5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161055f565b5081611f39565b611f39838381511561209b5781518083602001fd5b8060405162461bcd60e51b815260040161055f91906128e8565b6001600160a01b038116811461073a575f80fd5b803561062b816120b5565b801515811461073a575f80fd5b5f80604083850312156120f2575f80fd5b82356120fd816120b5565b9150602083013561210d816120d4565b809150509250929050565b5f60208284031215612128575f80fd5b813567ffffffffffffffff81111561213e575f80fd5b82016101608185031215612150575f80fd5b9392505050565b5f805f60608486031215612169575f80fd5b8335612174816120b5565b92506020840135612184816120b5565b929592945050506040919091013590565b5f602082840312156121a5575f80fd5b8135612150816120b5565b5f80604083850312156121c1575f80fd5b82356121cc816120b5565b946020939093013593505050565b5f8083601f8401126121ea575f80fd5b50813567ffffffffffffffff811115612201575f80fd5b6020830191508360208260051b850101111561221b575f80fd5b9250929050565b5f8083601f840112612232575f80fd5b50813567ffffffffffffffff811115612249575f80fd5b60208301915083602082850101111561221b575f80fd5b5f805f805f805f805f8060c08b8d031215612279575f80fd5b8a3567ffffffffffffffff80821115612290575f80fd5b61229c8e838f016121da565b909c509a5060208d01359150808211156122b4575f80fd5b6122c08e838f016121da565b909a50985060408d01359150808211156122d8575f80fd5b6122e48e838f016121da565b90985096508691506122f860608e016120c9565b955061230660808e016120c9565b945060a08d013591508082111561231b575f80fd5b506123288d828e01612222565b915080935050809150509295989b9194979a5092959850565b5f815180845260208085019450602084015f5b8381101561237057815187529582019590820190600101612354565b509495945050505050565b8215158152604060208201525f611f396040830184612341565b634e487b7160e01b5f52604160045260245ffd5b604051610160810167ffffffffffffffff811182821017156123cd576123cd612395565b60405290565b5f82601f8301126123e2575f80fd5b813567ffffffffffffffff808211156123fd576123fd612395565b604051601f8301601f19908116603f0116810190828211818310171561242557612425612395565b8160405283815286602085880101111561243d575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f610160828403121561246d575f80fd5b6124756123a9565b9050612480826120c9565b815261248e602083016120c9565b602082015261249f604083016120c9565b6040820152606082013560608201526124ba608083016120c9565b608082015260a082013567ffffffffffffffff808211156124d9575f80fd5b6124e5858386016123d3565b60a084015260c084013560c084015261250060e085016120c9565b60e08401526101009150818401358181111561251a575f80fd5b612526868287016123d3565b8385015250505061012061253b8184016120c9565b818301525061014080830135818301525092915050565b5f611e7d368361245c565b634e487b7160e01b5f52603260045260245ffd5b5f808335601e19843603018112612586575f80fd5b830160208101925035905067ffffffffffffffff8111156125a5575f80fd5b80360382131561221b575f80fd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b602081526125fc602082016125ef846120c9565b6001600160a01b03169052565b5f612609602084016120c9565b6001600160a01b038116604084015250612625604084016120c9565b6001600160a01b0381166060840152506060830135608083015261264b608084016120c9565b6001600160a01b03811660a08401525061266860a0840184612571565b6101608060c0860152612680610180860183856125b3565b925060c086013560e086015261269860e087016120c9565b91506101006126b1818701846001600160a01b03169052565b6126bd81880188612571565b93509050610120601f1987860301818801526126da8585846125b3565b94506126e78189016120c9565b93505050610140612702818701846001600160a01b03169052565b9590950135939094019290925250919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f60a0820160018060a01b0380891684526020818916602086015260a0604086015282885180855260c08701915060208a0194505f5b81811015612797578551851683529483019491830191600101612779565b505085810360608701526127ab8189612341565b935050505082810360808401526127c28185612715565b98975050505050505050565b5f602082840312156127de575f80fd5b813567ffffffffffffffff8111156127f4575f80fd5b611f398482850161245c565b634e487b7160e01b5f52601160045260245ffd5b80820180821115611e7d57611e7d612800565b5f60208284031215612837575f80fd5b8151612150816120b5565b5f60208284031215612852575f80fd5b5051919050565b81810381811115611e7d57611e7d612800565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f602082840312156128c7575f80fd5b8151612150816120d4565b5f82518060208501845e5f920191825250919050565b602081525f612150602083018461271556fea2646970667358221220bd07941a77f491ec603d4823289eb0f70dbf4bd640d52af7e41ca27357b38eb264736f6c63430008190033", + "solcInputHash": "97b5bdb91b047141f90bacdfba4b885e", + "metadata": "{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"contract IComptroller\",\"name\":\"comptroller_\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"vBNB_\",\"type\":\"address\"},{\"internalType\":\"contract IVToken\",\"name\":\"vWBNB_\",\"type\":\"address\"},{\"internalType\":\"contract IWBNB\",\"name\":\"wbnb_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"}],\"name\":\"BadInitiator\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nowTs\",\"type\":\"uint256\"}],\"name\":\"DeadlineExpired\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FlashNotSupportedForVai\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"got\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minOut\",\"type\":\"uint256\"}],\"name\":\"InsufficientOut\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidIntermediate\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NativeTransferFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotOperator\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyComptroller\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"errCode\",\"type\":\"uint256\"}],\"name\":\"RedeemFailed\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"}],\"name\":\"RouterNotAllowed\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"SpenderNotContract\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SwapFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WrongFlashAsset\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddressNotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroMinOut\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"borrower\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"vBStock\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"vDebt\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"repayAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"seizedBStock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"debtOut\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"flash\",\"type\":\"bool\"}],\"name\":\"Liquidated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"OperatorSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"PartialSwapLeftover\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"RouterSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"RouterSpenderSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Swept\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"SweptNative\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"comptroller\",\"outputs\":[{\"internalType\":\"contract IComptroller\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract VToken[]\",\"name\":\"vTokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"amounts\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"premiums\",\"type\":\"uint256[]\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"param\",\"type\":\"bytes\"}],\"name\":\"executeOperation\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"uint256[]\",\"name\":\"repayAmounts\",\"type\":\"uint256[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"borrower\",\"type\":\"address\"},{\"internalType\":\"contract IVBep20\",\"name\":\"vDebt\",\"type\":\"address\"},{\"internalType\":\"contract IVBep20\",\"name\":\"vBStock\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"repayAmount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"swapCalldata\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"minOut\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"router2\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"swapCalldata2\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"intermediateToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"internalType\":\"struct IBStockLiquidator.LiquidationParams\",\"name\":\"params\",\"type\":\"tuple\"}],\"name\":\"flashLiquidate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"initialOwner\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isOperator\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isRouter\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"borrower\",\"type\":\"address\"},{\"internalType\":\"contract IVBep20\",\"name\":\"vDebt\",\"type\":\"address\"},{\"internalType\":\"contract IVBep20\",\"name\":\"vBStock\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"repayAmount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"swapCalldata\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"minOut\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"router2\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"swapCalldata2\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"intermediateToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"internalType\":\"struct IBStockLiquidator.LiquidationParams\",\"name\":\"params\",\"type\":\"tuple\"}],\"name\":\"liquidate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"debtOut\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"routerSpender\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"setOperator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"setRouter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"setRouterSpender\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"sweep\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"sweepNative\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vBNB\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vWBNB\",\"outputs\":[{\"internalType\":\"contract IVToken\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"wbnb\",\"outputs\":[{\"internalType\":\"contract IWBNB\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"author\":\"Venus\",\"events\":{\"Initialized(uint8)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"},\"Liquidated(address,address,address,uint256,uint256,uint256,bool)\":{\"params\":{\"borrower\":\"The liquidated account.\",\"debtOut\":\"Debt-asset proceeds of the swap.\",\"flash\":\"True if funded by a flash loan, false if from inventory.\",\"repayAmount\":\"Debt underlying repaid.\",\"seizedBStock\":\"Raw bStock redeemed and sold.\",\"vBStock\":\"The seized bStock collateral market.\",\"vDebt\":\"The repaid debt market.\"}},\"PartialSwapLeftover(address,uint256)\":{\"params\":{\"amount\":\"The residual amount not consumed by the swap.\",\"token\":\"The input token left over (bStock on hop 1, the intermediate on hop 2).\"}}},\"kind\":\"dev\",\"methods\":{\"acceptOwnership()\":{\"details\":\"The new owner accepts the ownership transfer.\"},\"constructor\":{\"custom:oz-upgrades-unsafe-allow\":\"constructor\",\"params\":{\"comptroller_\":\"Venus Core Comptroller (diamond) \\u2014 gates liquidation and provides flash loans.\",\"vBNB_\":\"Native BNB market; a debt equal to this address is settled in native BNB.\",\"vWBNB_\":\"WBNB market; the flash-borrow source for BNB debt (vBNB itself cannot be flash-repaid).\",\"wbnb_\":\"WBNB token; the debt-accounting asset for BNB debt, unwrapped for the native repay.\"}},\"executeOperation(address[],uint256[],uint256[],address,address,bytes)\":{\"details\":\"Implementation of this function must ensure at least the premium (fee) is repaid within the same transaction. Any unpaid balance (principal + premium - repaid amount) will be added to the onBehalf address's borrow balance.\",\"params\":{\"amounts\":\"The amounts of each underlying asset that were flash-borrowed.\",\"initiator\":\"The address that initiated the flash loan.\",\"onBehalf\":\"The address of the user whose debt position will be used for any unpaid flash loan balance.\",\"param\":\"Additional parameters encoded as bytes. These can be used to pass custom data to the receiver contract.\",\"premiums\":\"The premiums (fees) associated with each flash-borrowed asset.\",\"vTokens\":\"The vToken contracts corresponding to the flash-borrowed underlying assets.\"},\"returns\":{\"_0\":\"True if the operation succeeds (regardless of repayment amount), false if the operation fails.\",\"repayAmounts\":\"Array of uint256 representing the amounts to be repaid for each asset. The receiver contract must approve these amounts to the respective vToken contracts before this function returns.\"}},\"flashLiquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))\":{\"details\":\"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`.\",\"params\":{\"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).\"}},\"initialize(address)\":{\"params\":{\"initialOwner\":\"Address that owns the contract (admin + default operator).\"}},\"liquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))\":{\"details\":\"INVENTORY mode spends the contract's OWN debt-asset capital, so \\u2014 unlike FLASH mode, where `executeOperation` forces the swap proceeds to cover principal + premium \\u2014 there is no built-in floor tying `debtOut` to `repayAmount`. This asymmetry is intentional: a repay can legitimately out-cost its proceeds (e.g. the Venus Liquidator keeps a treasury cut of the seized collateral, so proceeds land a few % under the repay). `minOut` IS the operator's chosen loss floor for inventory mode \\u2014 set it to the lowest acceptable debt-asset return.\",\"params\":{\"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).\"},\"returns\":{\"debtOut\":\"Debt-asset proceeds realized by the swap chain.\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"pendingOwner()\":{\"details\":\"Returns the address of the pending owner.\"},\"setOperator(address,bool)\":{\"params\":{\"allowed\":\"True to allow, false to remove.\",\"operator\":\"Address to allowlist or remove.\"}},\"setRouter(address,bool)\":{\"details\":\"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.\",\"params\":{\"allowed\":\"True to allow, false to remove.\",\"router\":\"Address to allowlist or remove.\"}},\"setRouterSpender(address,address)\":{\"details\":\"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 \\u2014 it is never called; the low-level call always targets the allowlisted router.\",\"params\":{\"router\":\"The allowlisted swap target (call target).\",\"spender\":\"The contract that pulls the input token via `transferFrom` during settlement.\"}},\"sweep(address,address,uint256)\":{\"params\":{\"amount\":\"Amount to withdraw.\",\"to\":\"Recipient.\",\"token\":\"Token to withdraw.\"}},\"sweepNative(address,uint256)\":{\"details\":\"`nonReentrant` is defense-in-depth only (the function is `onlyOwner`, snapshots no state, and reads no balance after the native `.call`), added for consistency with the liquidation entrypoints.\",\"params\":{\"amount\":\"Amount of native BNB to withdraw.\",\"to\":\"Recipient.\"}},\"transferOwnership(address)\":{\"details\":\"Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. Can only be called by the current owner.\"}},\"stateVariables\":{\"__gap\":{\"details\":\"Reserved storage to allow new state variables in future upgrades without layout clashes.\"},\"comptroller\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"},\"vBNB\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"},\"vWBNB\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"},\"wbnb\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"}},\"title\":\"BStockLiquidator\",\"version\":1},\"userdoc\":{\"errors\":{\"BadInitiator(address)\":[{\"notice\":\"Thrown when the flash-loan initiator is not this contract.\"}],\"DeadlineExpired(uint256,uint256)\":[{\"notice\":\"Thrown when the call is submitted after `params.deadline`.\"}],\"FlashNotSupportedForVai()\":[{\"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 \\u2014 use `liquidate` (INVENTORY mode) with pre-funded VAI instead.\"}],\"InsufficientOut(uint256,uint256)\":[{\"notice\":\"Thrown when swap proceeds are below `minOut`.\"}],\"InvalidIntermediate()\":[{\"notice\":\"Thrown when a two-hop `intermediateToken` is zero, or equals the debt or bStock token.\"}],\"NativeTransferFailed()\":[{\"notice\":\"Thrown when a native BNB transfer (the `sweepNative` payout) fails.\"}],\"NotOperator()\":[{\"notice\":\"Thrown when the caller is neither the owner nor an allowlisted operator.\"}],\"OnlyComptroller()\":[{\"notice\":\"Thrown when `executeOperation` is called by something other than the Comptroller.\"}],\"RedeemFailed(uint256)\":[{\"notice\":\"Thrown when `vBStock.redeem` returns a non-zero error code.\"}],\"RouterNotAllowed(address)\":[{\"notice\":\"Thrown when the supplied swap router is not allowlisted.\"}],\"SpenderNotContract(address)\":[{\"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.\"}],\"SwapFailed()\":[{\"notice\":\"Thrown when the low-level call to the router reverts.\"}],\"WrongFlashAsset()\":[{\"notice\":\"Thrown when the flashed asset does not match `params.vDebt`.\"}],\"ZeroAddressNotAllowed()\":[{\"notice\":\"Thrown if the supplied address is a zero address where it is not allowed\"}],\"ZeroMinOut()\":[{\"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).\"}]},\"events\":{\"Liquidated(address,address,address,uint256,uint256,uint256,bool)\":{\"notice\":\"Emitted on a successful liquidation.\"},\"OperatorSet(address,bool)\":{\"notice\":\"Emitted when an operator is allowlisted or removed.\"},\"PartialSwapLeftover(address,uint256)\":{\"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`.\"},\"RouterSet(address,bool)\":{\"notice\":\"Emitted when a swap router is allowlisted or removed.\"},\"RouterSpenderSet(address,address)\":{\"notice\":\"Emitted when a router's token-approval target (spender) is set or cleared.\"},\"Swept(address,address,uint256)\":{\"notice\":\"Emitted when the owner withdraws a token.\"},\"SweptNative(address,uint256)\":{\"notice\":\"Emitted when the owner withdraws stuck native BNB.\"}},\"kind\":\"user\",\"methods\":{\"comptroller()\":{\"notice\":\"Core Comptroller (diamond): reads the liquidation gate and provides the flash loan via `executeFlashLoan`.\"},\"constructor\":{\"notice\":\"Constructor for the implementation contract. Sets the immutables and locks initializers.\"},\"executeOperation(address[],uint256[],uint256[],address,address,bytes)\":{\"notice\":\"Executes an operation after receiving the flash-borrowed assets.\"},\"flashLiquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))\":{\"notice\":\"Liquidate by flash-borrowing the repay amount from Venus, repaid (+ premium) in the same tx.\"},\"initialize(address)\":{\"notice\":\"Initializes the proxy: sets the owner and the reentrancy guard.\"},\"isOperator(address)\":{\"notice\":\"Addresses allowed to trigger a liquidation.\"},\"isRouter(address)\":{\"notice\":\"Routers allowed as the swap target (defends the low-level call).\"},\"liquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))\":{\"notice\":\"Liquidate using the contract's own debt-asset inventory.\"},\"renounceOwnership()\":{\"notice\":\"Disabled. This backstop custodies protocol capital (debt-asset inventory, native BNB) and every admin function (`sweep`, `sweepNative`, `setOperator`, `setRouter`) is `onlyOwner`, so renouncing ownership would permanently strand those funds and brick the contract. The override is an `onlyOwner` no-op: an accidental owner call cannot zero the owner, and a non-owner call reverts. Ownership is still transferable via the two-step `transferOwnership` flow.\"},\"routerSpender(address)\":{\"notice\":\"Optional token-approval target (spender) per router, for aggregators whose settlement contract that pulls the input token differs from the call target (e.g. Liquid Mesh, where the router is the call target but a separate spender pulls the token). When unset (address(0)), the approval defaults to the router itself \\u2014 the Native behaviour, where the call target IS the puller \\u2014 so existing routers need no spender entry.\"},\"setOperator(address,bool)\":{\"notice\":\"Allow or disallow an address to trigger liquidations.\"},\"setRouter(address,bool)\":{\"notice\":\"Allow or disallow a router as the swap target (e.g. the Native router).\"},\"setRouterSpender(address,address)\":{\"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.\"},\"sweep(address,address,uint256)\":{\"notice\":\"Withdraw any token (profit, leftover inventory, stuck dust) to `to`.\"},\"sweepNative(address,uint256)\":{\"notice\":\"Withdraw stuck native BNB (a stray transfer, or a gate refund) to `to`.\"},\"vBNB()\":{\"notice\":\"The native BNB market (vBNB). A debt equal to this address is settled with native BNB: WBNB is the debt-accounting token, and only the repay amount is unwrapped (see `_liquidate`).\"},\"vWBNB()\":{\"notice\":\"The WBNB market (vWBNB). BNB debt is flash-funded from here, NOT from vBNB: vBNB cannot be flash-repaid (its `doTransferIn` needs `msg.value`), whereas vWBNB's underlying is a plain ERC20 repaid via `transferFrom`.\"},\"wbnb()\":{\"notice\":\"WBNB token: the debt-accounting asset for BNB debt, unwrapped to native BNB for the repay.\"}},\"notice\":\"Atomic backstop liquidator for bStock (ERC-8056 tokenized stock) collateral. In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken, redeems it to the raw bStock, and sells that bStock to the debt asset in one or two hops. Hop 1 sells bStock (to USDT) through an allowlisted RFQ router using a pre-fetched, off-chain-signed `swapCalldata` \\u2014 Native firm-quote or Liquid Mesh (see `routerSpender`) or any future allowlisted source; the contract is router-agnostic and just forwards the opaque blob. For USDT debt that single hop lands the debt asset directly. For non-USDT debt an OPTIONAL hop 2 (RFQ sources quote bStock->USDT only) converts the USDT to the debt asset through a second allowlisted router (an AMM/aggregator). Because seize and sell happen in the same tx there is no price-drift window, and the realized debt-asset amount must clear `minOut` or the whole call reverts \\u2014 the protocol never ends up holding the RFQ-only asset or the intermediate. Two funding modes share the same core (`_liquidate`): - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance. - FLASH: the debt asset is flash-borrowed from Venus (`Comptroller.executeFlashLoan`) and repaid (+ premium) within the same tx; no capital is locked in the contract. Native BNB debt (vBNB): supported in both modes with WBNB as the debt-accounting token. The repay must be native BNB, so exactly the repay amount of WBNB is unwrapped and forwarded to the gate's payable path; the two-hop swap lands WBNB (bStock->USDT->WBNB) and `minOut` is measured in WBNB (1:1 with BNB). FLASH mode borrows from vWBNB, NOT vBNB: vBNB cannot be flash-repaid (its `doTransferIn` requires `msg.value`), whereas vWBNB's underlying is a plain ERC20. VAI debt (VAIController): supported in INVENTORY mode only. VAI is not a vToken \\u2014 a `vDebt` equal to `comptroller.vaiController()` is VAI, and like vBNB it has no `underlying()`, so the debt token is resolved via `getVAIAddress()`. The repay is a plain ERC20 approval to the gate, which takes its `_liquidateVAI` branch (pulls the VAI from us, then burns it via `VAIController.liquidateVAI`). Because RFQ sources quote bStock->USDT only, a VAI debt is inherently two-hop (bStock->USDT->VAI); hop 2 is expected to be the Peg Stability Module (`swapStableForVAI`, allowlisted as `router2`), which mints VAI from USDT at the oracle rate. FLASH mode is rejected (`FlashNotSupportedForVai`): `executeFlashLoan` lends a vToken's underlying, and VAI is minted/burned with no vVAI market. Ownership / scope: this is Venus's OWN backstop tool, NOT a public utility \\u2014 `liquidate` and `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus path with their own funds and their own offload. This contract is intentionally gated because it custodies funds (debt-asset inventory / flash principal) and forwards a CALLER-SUPPLIED calldata blob to an external router \\u2014 the swap's recipient (`to`) lives inside that calldata, so an open entrypoint would let anyone route the proceeds to themselves and drain the contract. The router allowlist and `minOut` bound the blast radius but cannot replace operator-gating. Security model: - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator). - BOTH swap targets (`router` and, when set, `router2`) must be allowlisted (`isRouter`) \\u2014 defends the low-level `router.call(swapCalldata)` on each hop. - the approval for each hop is the exact amount being sold, granted to the router's configured spender (the router itself when unset \\u2014 see `routerSpender`) and reset to 0 afterwards (bStock on hop 1; the measured intermediate balance delta on hop 2, so pre-existing inventory is never exposed). - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started). - the realized debt-asset amount must clear `minOut` or the tx reverts. Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`, which is always configured on the networks this contract targets. While it is set, a direct `vToken.liquidateBorrow` from an arbitrary caller reverts UNAUTHORIZED, so this contract reads the gate at call time and routes the repay through that Venus Liquidator (the permissionless entry anyone may call), reverting if the gate is ever unset. Routing through the gate needs no governance change, and no other Core market is affected. Note: setting THIS contract as `liquidatorContract` is NOT an option \\u2014 the gate is pool-wide, so every other market's liquidations would be forced through here.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/BStock/BStockLiquidator.sol\":\"BStockLiquidator\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./OwnableUpgradeable.sol\\\";\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Contract module which provides access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership} and {acceptOwnership}.\\n *\\n * This module is used through inheritance. It will make available all functions\\n * from parent (Ownable).\\n */\\nabstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {\\n address private _pendingOwner;\\n\\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\\n\\n function __Ownable2Step_init() internal onlyInitializing {\\n __Ownable_init_unchained();\\n }\\n\\n function __Ownable2Step_init_unchained() internal onlyInitializing {\\n }\\n /**\\n * @dev Returns the address of the pending owner.\\n */\\n function pendingOwner() public view virtual returns (address) {\\n return _pendingOwner;\\n }\\n\\n /**\\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual override onlyOwner {\\n _pendingOwner = newOwner;\\n emit OwnershipTransferStarted(owner(), newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual override {\\n delete _pendingOwner;\\n super._transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev The new owner accepts the ownership transfer.\\n */\\n function acceptOwnership() public virtual {\\n address sender = _msgSender();\\n require(pendingOwner() == sender, \\\"Ownable2Step: caller is not the new owner\\\");\\n _transferOwnership(sender);\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n}\\n\",\"keccak256\":\"0x9140dabc466abab21b48b72dbda26736b1183a310d0e677d3719d201df026510\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../utils/ContextUpgradeable.sol\\\";\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n function __Ownable_init() internal onlyInitializing {\\n __Ownable_init_unchained();\\n }\\n\\n function __Ownable_init_unchained() internal onlyInitializing {\\n _transferOwnership(_msgSender());\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n require(owner() == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby disabling any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n}\\n\",\"keccak256\":\"0x359a1ab89b46b9aba7bcad3fb651924baf4893d15153049b9976b0fc9be1358e\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)\\n\\npragma solidity ^0.8.2;\\n\\nimport \\\"../../utils/AddressUpgradeable.sol\\\";\\n\\n/**\\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\\n *\\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\\n * reused. This mechanism prevents re-execution of each \\\"step\\\" but allows the creation of new initialization steps in\\n * case an upgrade adds a module that needs to be initialized.\\n *\\n * For example:\\n *\\n * [.hljs-theme-light.nopadding]\\n * ```solidity\\n * contract MyToken is ERC20Upgradeable {\\n * function initialize() initializer public {\\n * __ERC20_init(\\\"MyToken\\\", \\\"MTK\\\");\\n * }\\n * }\\n *\\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\\n * function initializeV2() reinitializer(2) public {\\n * __ERC20Permit_init(\\\"MyToken\\\");\\n * }\\n * }\\n * ```\\n *\\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\\n *\\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\\n *\\n * [CAUTION]\\n * ====\\n * Avoid leaving a contract uninitialized.\\n *\\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\\n *\\n * [.hljs-theme-light.nopadding]\\n * ```\\n * /// @custom:oz-upgrades-unsafe-allow constructor\\n * constructor() {\\n * _disableInitializers();\\n * }\\n * ```\\n * ====\\n */\\nabstract contract Initializable {\\n /**\\n * @dev Indicates that the contract has been initialized.\\n * @custom:oz-retyped-from bool\\n */\\n uint8 private _initialized;\\n\\n /**\\n * @dev Indicates that the contract is in the process of being initialized.\\n */\\n bool private _initializing;\\n\\n /**\\n * @dev Triggered when the contract has been initialized or reinitialized.\\n */\\n event Initialized(uint8 version);\\n\\n /**\\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\\n * `onlyInitializing` functions can be used to initialize parent contracts.\\n *\\n * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a\\n * constructor.\\n *\\n * Emits an {Initialized} event.\\n */\\n modifier initializer() {\\n bool isTopLevelCall = !_initializing;\\n require(\\n (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),\\n \\\"Initializable: contract is already initialized\\\"\\n );\\n _initialized = 1;\\n if (isTopLevelCall) {\\n _initializing = true;\\n }\\n _;\\n if (isTopLevelCall) {\\n _initializing = false;\\n emit Initialized(1);\\n }\\n }\\n\\n /**\\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\\n * used to initialize parent contracts.\\n *\\n * A reinitializer may be used after the original initialization step. This is essential to configure modules that\\n * are added through upgrades and that require initialization.\\n *\\n * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`\\n * cannot be nested. If one is invoked in the context of another, execution will revert.\\n *\\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\\n * a contract, executing them in the right order is up to the developer or operator.\\n *\\n * WARNING: setting the version to 255 will prevent any future reinitialization.\\n *\\n * Emits an {Initialized} event.\\n */\\n modifier reinitializer(uint8 version) {\\n require(!_initializing && _initialized < version, \\\"Initializable: contract is already initialized\\\");\\n _initialized = version;\\n _initializing = true;\\n _;\\n _initializing = false;\\n emit Initialized(version);\\n }\\n\\n /**\\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\\n */\\n modifier onlyInitializing() {\\n require(_initializing, \\\"Initializable: contract is not initializing\\\");\\n _;\\n }\\n\\n /**\\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\\n * through proxies.\\n *\\n * Emits an {Initialized} event the first time it is successfully executed.\\n */\\n function _disableInitializers() internal virtual {\\n require(!_initializing, \\\"Initializable: contract is initializing\\\");\\n if (_initialized != type(uint8).max) {\\n _initialized = type(uint8).max;\\n emit Initialized(type(uint8).max);\\n }\\n }\\n\\n /**\\n * @dev Returns the highest version that has been initialized. See {reinitializer}.\\n */\\n function _getInitializedVersion() internal view returns (uint8) {\\n return _initialized;\\n }\\n\\n /**\\n * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.\\n */\\n function _isInitializing() internal view returns (bool) {\\n return _initializing;\\n }\\n}\\n\",\"keccak256\":\"0x89be10e757d242e9b18d5a32c9fbe2019f6d63052bbe46397a430a1d60d7f794\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)\\n\\npragma solidity ^0.8.0;\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Contract module that helps prevent reentrant calls to a function.\\n *\\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\\n * available, which can be applied to functions to make sure there are no nested\\n * (reentrant) calls to them.\\n *\\n * Note that because there is a single `nonReentrant` guard, functions marked as\\n * `nonReentrant` may not call one another. This can be worked around by making\\n * those functions `private`, and then adding `external` `nonReentrant` entry\\n * points to them.\\n *\\n * TIP: If you would like to learn more about reentrancy and alternative ways\\n * to protect against it, check out our blog post\\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\\n */\\nabstract contract ReentrancyGuardUpgradeable is Initializable {\\n // Booleans are more expensive than uint256 or any type that takes up a full\\n // word because each write operation emits an extra SLOAD to first read the\\n // slot's contents, replace the bits taken up by the boolean, and then write\\n // back. This is the compiler's defense against contract upgrades and\\n // pointer aliasing, and it cannot be disabled.\\n\\n // The values being non-zero value makes deployment a bit more expensive,\\n // but in exchange the refund on every call to nonReentrant will be lower in\\n // amount. Since refunds are capped to a percentage of the total\\n // transaction's gas, it is best to keep them low in cases like this one, to\\n // increase the likelihood of the full refund coming into effect.\\n uint256 private constant _NOT_ENTERED = 1;\\n uint256 private constant _ENTERED = 2;\\n\\n uint256 private _status;\\n\\n function __ReentrancyGuard_init() internal onlyInitializing {\\n __ReentrancyGuard_init_unchained();\\n }\\n\\n function __ReentrancyGuard_init_unchained() internal onlyInitializing {\\n _status = _NOT_ENTERED;\\n }\\n\\n /**\\n * @dev Prevents a contract from calling itself, directly or indirectly.\\n * Calling a `nonReentrant` function from another `nonReentrant`\\n * function is not supported. It is possible to prevent this from happening\\n * by making the `nonReentrant` function external, and making it call a\\n * `private` function that does the actual work.\\n */\\n modifier nonReentrant() {\\n _nonReentrantBefore();\\n _;\\n _nonReentrantAfter();\\n }\\n\\n function _nonReentrantBefore() private {\\n // On the first call to nonReentrant, _status will be _NOT_ENTERED\\n require(_status != _ENTERED, \\\"ReentrancyGuard: reentrant call\\\");\\n\\n // Any calls to nonReentrant after this point will fail\\n _status = _ENTERED;\\n }\\n\\n function _nonReentrantAfter() private {\\n // By storing the original value once again, a refund is triggered (see\\n // https://eips.ethereum.org/EIPS/eip-2200)\\n _status = _NOT_ENTERED;\\n }\\n\\n /**\\n * @dev Returns true if the reentrancy guard is currently set to \\\"entered\\\", which indicates there is a\\n * `nonReentrant` function in the call stack.\\n */\\n function _reentrancyGuardEntered() internal view returns (bool) {\\n return _status == _ENTERED;\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n}\\n\",\"keccak256\":\"0x2025ccf05f6f1f2fd4e078e552836f525a1864e3854ed555047cd732320ab29b\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20Upgradeable {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `from` to `to` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\\n}\\n\",\"keccak256\":\"0x0e1f0f5f62f67a881cd1a9597acbc0a5e4071f3c2c10449a183b922ae7272e3f\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\\n *\\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\\n * need to send a transaction, and thus is not required to hold Ether at all.\\n *\\n * ==== Security Considerations\\n *\\n * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature\\n * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be\\n * considered as an intention to spend the allowance in any specific way. The second is that because permits have\\n * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should\\n * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be\\n * generally recommended is:\\n *\\n * ```solidity\\n * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {\\n * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}\\n * doThing(..., value);\\n * }\\n *\\n * function doThing(..., uint256 value) public {\\n * token.safeTransferFrom(msg.sender, address(this), value);\\n * ...\\n * }\\n * ```\\n *\\n * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of\\n * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also\\n * {SafeERC20-safeTransferFrom}).\\n *\\n * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so\\n * contracts should have entry points that don't rely on permit.\\n */\\ninterface IERC20PermitUpgradeable {\\n /**\\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\\n * given ``owner``'s signed approval.\\n *\\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\\n * ordering also apply here.\\n *\\n * Emits an {Approval} event.\\n *\\n * Requirements:\\n *\\n * - `spender` cannot be the zero address.\\n * - `deadline` must be a timestamp in the future.\\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\\n * over the EIP712-formatted function arguments.\\n * - the signature must use ``owner``'s current nonce (see {nonces}).\\n *\\n * For more information on the signature format, see the\\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\\n * section].\\n *\\n * CAUTION: See Security Considerations above.\\n */\\n function permit(\\n address owner,\\n address spender,\\n uint256 value,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external;\\n\\n /**\\n * @dev Returns the current nonce for `owner`. This value must be\\n * included whenever a signature is generated for {permit}.\\n *\\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\\n * prevents a signature from being used multiple times.\\n */\\n function nonces(address owner) external view returns (uint256);\\n\\n /**\\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\\n */\\n // solhint-disable-next-line func-name-mixedcase\\n function DOMAIN_SEPARATOR() external view returns (bytes32);\\n}\\n\",\"keccak256\":\"0x07e881de3b9f6d2c07909f193f24b96c7fe4ea60013260f3f25aecd8bab3c2f8\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20Upgradeable.sol\\\";\\nimport \\\"../extensions/IERC20PermitUpgradeable.sol\\\";\\nimport \\\"../../../utils/AddressUpgradeable.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20Upgradeable {\\n using AddressUpgradeable for address;\\n\\n /**\\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n */\\n function safeTransfer(IERC20Upgradeable token, address to, uint256 value) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n /**\\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\\n */\\n function safeTransferFrom(IERC20Upgradeable token, address from, address to, uint256 value) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(IERC20Upgradeable token, address spender, uint256 value) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n /**\\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n */\\n function safeIncreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));\\n }\\n\\n /**\\n * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n */\\n function safeDecreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));\\n }\\n }\\n\\n /**\\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\\n * to be set to zero before setting it to a non-zero value, such as USDT.\\n */\\n function forceApprove(IERC20Upgradeable token, address spender, uint256 value) internal {\\n bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);\\n\\n if (!_callOptionalReturnBool(token, approvalCall)) {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));\\n _callOptionalReturn(token, approvalCall);\\n }\\n }\\n\\n /**\\n * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.\\n * Revert on invalid signature.\\n */\\n function safePermit(\\n IERC20PermitUpgradeable token,\\n address owner,\\n address spender,\\n uint256 value,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) internal {\\n uint256 nonceBefore = token.nonces(owner);\\n token.permit(owner, spender, value, deadline, v, r, s);\\n uint256 nonceAfter = token.nonces(owner);\\n require(nonceAfter == nonceBefore + 1, \\\"SafeERC20: permit did not succeed\\\");\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20Upgradeable token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n require(returndata.length == 0 || abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n *\\n * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.\\n */\\n function _callOptionalReturnBool(IERC20Upgradeable token, bytes memory data) private returns (bool) {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false\\n // and not revert is the subcall reverts.\\n\\n (bool success, bytes memory returndata) = address(token).call(data);\\n return\\n success && (returndata.length == 0 || abi.decode(returndata, (bool))) && AddressUpgradeable.isContract(address(token));\\n }\\n}\\n\",\"keccak256\":\"0x23b997be73d3dd46885262704f0f8cfc7273fdadfe303d37969a9561373972b5\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary AddressUpgradeable {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n *\\n * Furthermore, `isContract` will also return true if the target contract within\\n * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,\\n * which only has an effect at the end of a transaction.\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\\n *\\n * _Available since v4.8._\\n */\\n function verifyCallResultFromTarget(\\n address target,\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n if (success) {\\n if (returndata.length == 0) {\\n // only check isContract if the call was successful and the return data is empty\\n // otherwise we already know that it was a contract\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n }\\n return returndata;\\n } else {\\n _revert(returndata, errorMessage);\\n }\\n }\\n\\n /**\\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason or using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n _revert(returndata, errorMessage);\\n }\\n }\\n\\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n /// @solidity memory-safe-assembly\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n}\\n\",\"keccak256\":\"0x9c80f545915582e63fe206c6ce27cbe85a86fc10b9cd2a0e8c9488fb7c2ee422\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract ContextUpgradeable is Initializable {\\n function __Context_init() internal onlyInitializing {\\n }\\n\\n function __Context_init_unchained() internal onlyInitializing {\\n }\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[50] private __gap;\\n}\\n\",\"keccak256\":\"0x75097e35253e7fb282ee4d7f27a80eaacfa759923185bf17302a89cbc059c5ef\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/IAccessControl.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev External interface of AccessControl declared to support ERC165 detection.\\n */\\ninterface IAccessControl {\\n /**\\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\\n *\\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\\n * {RoleAdminChanged} not being emitted signaling this.\\n *\\n * _Available since v3.1._\\n */\\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\\n\\n /**\\n * @dev Emitted when `account` is granted `role`.\\n *\\n * `sender` is the account that originated the contract call, an admin role\\n * bearer except when using {AccessControl-_setupRole}.\\n */\\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Emitted when `account` is revoked `role`.\\n *\\n * `sender` is the account that originated the contract call:\\n * - if using `revokeRole`, it is the admin role bearer\\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\\n */\\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Returns `true` if `account` has been granted `role`.\\n */\\n function hasRole(bytes32 role, address account) external view returns (bool);\\n\\n /**\\n * @dev Returns the admin role that controls `role`. See {grantRole} and\\n * {revokeRole}.\\n *\\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\\n */\\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function grantRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function revokeRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from the calling account.\\n *\\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\\n * purpose is to provide a mechanism for accounts to lose their privileges\\n * if they are compromised (such as when a trusted device is misplaced).\\n *\\n * If the calling account had been granted `role`, emits a {RoleRevoked}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must be `account`.\\n */\\n function renounceRole(bytes32 role, address account) external;\\n}\\n\",\"keccak256\":\"0x59ce320a585d7e1f163cd70390a0ef2ff9cec832e2aa544293a00692465a7a57\",\"license\":\"MIT\"},\"@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\nimport \\\"@openzeppelin/contracts/access/IAccessControl.sol\\\";\\n\\n/**\\n * @title IAccessControlManagerV8\\n * @author Venus\\n * @notice Interface implemented by the `AccessControlManagerV8` contract.\\n */\\ninterface IAccessControlManagerV8 is IAccessControl {\\n function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external;\\n\\n function revokeCallPermission(\\n address contractAddress,\\n string calldata functionSig,\\n address accountToRevoke\\n ) external;\\n\\n function isAllowedToCall(address account, string calldata functionSig) external view returns (bool);\\n\\n function hasPermission(\\n address account,\\n address contractAddress,\\n string calldata functionSig\\n ) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xaa29b098440d0b3a131c5ecdf25ce548790c1b5ac7bf9b5c0264b6af6f7a1e0b\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\ninterface IDeviationBoundedOracle {\\n // --- Enums ---\\n\\n /// @notice Identifies whether a price bound is a minimum or maximum\\n enum PriceBoundType {\\n MIN,\\n MAX\\n }\\n\\n /// @notice Identifies which keeper action a single syncPriceBoundsAndProtections item performs\\n enum KeeperAction {\\n SetMinPrice,\\n SetMaxPrice,\\n ExitProtectionMode\\n }\\n\\n // --- Structs ---\\n\\n /// @notice Per-asset protection state tracking the min/max price window\\n struct MarketProtectionState {\\n /// @notice Lowest price observed in the current window (packed with maxPrice in one slot)\\n uint128 minPrice;\\n /// @notice Highest price observed in the current window\\n uint128 maxPrice;\\n /// @notice Whether protected price is currently being used\\n bool currentlyUsingProtectedPrice;\\n /// @notice Whether this market is whitelisted for bounded pricing\\n bool isBoundedPricingEnabled;\\n /// @notice Timestamp of the last protection trigger \\u2014 reset on every trigger\\n uint64 lastProtectionTriggeredAt;\\n /// @notice Minimum time protection stays active after last trigger\\n uint64 cooldownPeriod;\\n /// @notice The underlying asset address, used to verify initialization\\n address asset;\\n /// @notice Entry deviation threshold (mantissa, e.g. 0.1667e18 = 16.67%); packed with resetThreshold\\n uint128 triggerThreshold;\\n /// @notice Exit threshold (mantissa); window must converge below this for protection to be disabled\\n uint128 resetThreshold;\\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\\n bool cachingEnabled;\\n }\\n\\n /// @notice One item in an syncPriceBoundsAndProtections payload\\n /// @dev `value` is interpreted per-action: the new bound price for SetMinPrice / SetMaxPrice, ignored for ExitProtectionMode\\n struct KeeperActionItem {\\n address asset;\\n KeeperAction action;\\n uint256 value;\\n }\\n\\n /// @notice One item in a setTokenConfigs payload\\n struct TokenConfigInput {\\n /// @notice The underlying asset address\\n address asset;\\n /// @notice Minimum time protection stays active after the last trigger (seconds)\\n uint64 cooldownPeriod;\\n /// @notice Entry deviation threshold (mantissa). Must be between 5% and 50%.\\n uint256 triggerThreshold;\\n /// @notice Exit deviation threshold (mantissa). Must be non-zero and below triggerThreshold.\\n uint256 resetThreshold;\\n /// @notice Whether to enable bounded pricing immediately upon initialization\\n bool enableBoundedPricing;\\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\\n bool enableCaching;\\n }\\n\\n // --- Events ---\\n\\n /// @notice Emitted when protection is initialized for an asset\\n event ProtectionInitialized(\\n address indexed asset,\\n uint128 minPrice,\\n uint128 maxPrice,\\n uint64 cooldownPeriod,\\n uint256 triggerThreshold\\n );\\n\\n /// @notice Emitted when protection mode is triggered for an asset\\n event ProtectionTriggered(address indexed asset, uint256 spotPrice, uint128 minPrice, uint128 maxPrice);\\n\\n /// @notice Emitted when protection mode is disabled for an asset\\n event ProtectionModeExited(address indexed asset);\\n\\n /// @notice Emitted when the keeper updates the minimum price for an asset\\n event MinPriceUpdated(address indexed asset, uint128 oldMin, uint128 newMin);\\n\\n /// @notice Emitted when the keeper updates the maximum price for an asset\\n event MaxPriceUpdated(address indexed asset, uint128 oldMax, uint128 newMax);\\n\\n /// @notice Emitted when the entry threshold is updated for an asset\\n event TriggerThresholdSet(address indexed asset, uint256 oldThreshold, uint256 newThreshold);\\n\\n /// @notice Emitted when the exit threshold is updated for an asset\\n event ResetThresholdSet(address indexed asset, uint256 oldExitThreshold, uint256 newExitThreshold);\\n\\n /// @notice Emitted when the cooldown period is updated for an asset\\n event CooldownPeriodSet(address indexed asset, uint64 oldCooldown, uint64 newCooldown);\\n\\n /// @notice Emitted when an asset's whitelist status changes\\n event BoundedPricingWhitelistUpdated(address indexed asset, bool whitelisted);\\n\\n /// @notice Emitted when the per-asset transient caching flag is toggled\\n event CachingEnabledUpdated(address indexed asset, bool oldEnabled, bool newEnabled);\\n\\n // --- Errors ---\\n\\n /// @notice Thrown when trying to use or update protection for an asset that has not been initialized\\n error MarketNotInitialized(address asset);\\n\\n /// @notice Thrown when trying to initialize an already initialized market\\n error MarketAlreadyInitialized(address asset);\\n\\n /// @notice Thrown when trying to disable protection that is not active\\n error ProtectedPriceInactive(address asset);\\n\\n /// @notice Thrown when trying to disable protection before cooldown has elapsed\\n error CooldownNotElapsed(address asset, uint64 lastProtectionTriggeredAt, uint64 cooldownPeriod);\\n\\n /// @notice Thrown when trying to disable protection before price range has converged\\n error PriceRangeNotConverged(address asset, uint256 currentRangeRatio, uint256 resetThreshold);\\n\\n /// @notice Thrown when keeper tries to set minPrice above current spot\\n error InvalidMinPrice(address asset, uint128 newMin, uint256 currentSpot);\\n\\n /// @notice Thrown when keeper tries to set maxPrice below current spot\\n error InvalidMaxPrice(address asset, uint128 newMax, uint256 currentSpot);\\n\\n /// @notice Thrown when threshold is set below the minimum allowed value\\n error ThresholdBelowMinimum(uint256 threshold, uint256 minimum);\\n\\n /// @notice Thrown when threshold is set above the maximum allowed value\\n error ThresholdAboveMaximum(uint256 threshold, uint256 maximum);\\n\\n /// @notice Thrown when a price exceeds uint128 max\\n error PriceExceedsUint128(uint256 price);\\n\\n /// @notice Thrown when a zero price is provided where a non-zero price is required\\n error ZeroPriceNotAllowed();\\n\\n /// @notice Thrown when trying to initialize protection for VAI\\n error VAINotAllowed();\\n\\n /// @notice Thrown when trying to disable bounded pricing for an asset while protection is active\\n error ProtectedPriceActive(address asset);\\n\\n /// @notice Thrown when the lengths of the arrays are not equal\\n error InvalidArrayLength();\\n\\n /// @notice Thrown when the exit threshold is set at or above the trigger threshold\\n error InvalidResetThreshold(uint256 resetThreshold);\\n\\n /// @notice Thrown when an syncPriceBoundsAndProtections item carries an unsupported action enum value\\n error InvalidKeeperAction(uint8 action);\\n\\n // --- Non-view price functions (update window + trigger protection) ---\\n\\n /**\\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice);\\n\\n /**\\n * @notice Gets the bounded debt price for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice);\\n\\n /**\\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice);\\n\\n // --- State update (call before view price reads to populate transient cache) ---\\n\\n /**\\n * @notice Updates the protection state for a given vToken, caching the resolved collateral and debt prices\\n * @dev Called by PolicyFacet before liquidity calculations so subsequent view price\\n * reads in the same transaction are served from transient storage. The transient\\n * cache is only populated when the asset's `cachingEnabled` flag is `true`.\\n * @param vToken vToken address\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function updateProtectionState(address vToken) external;\\n\\n // --- View price functions (read stored/cached state only) ---\\n\\n /**\\n * @notice Gets the bounded collateral price for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return price The bounded collateral price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 price);\\n\\n /**\\n * @notice Gets the bounded debt price for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return price The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 price);\\n\\n /**\\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice);\\n\\n // --- Keeper functions ---\\n\\n /**\\n * @notice Updates the minimum price in the rolling window for a given asset\\n * @param asset The underlying asset address\\n * @param newMin The new minimum price; must be at or below the current spot and below maxPrice\\n * @custom:access Only authorized keeper addresses\\n * @custom:error ZeroPriceNotAllowed if newMin is zero\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error InvalidMinPrice if newMin exceeds the current spot or is at or above maxPrice\\n * @custom:event MinPriceUpdated\\n */\\n function updateMinPrice(address asset, uint128 newMin) external;\\n\\n /**\\n * @notice Updates the maximum price in the rolling window for a given asset\\n * @param asset The underlying asset address\\n * @param newMax The new maximum price; must be at or above the current spot and above minPrice\\n * @custom:access Only authorized keeper addresses\\n * @custom:error ZeroPriceNotAllowed if newMax is zero\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error InvalidMaxPrice if newMax is below the current spot or is at or below minPrice\\n * @custom:event MaxPriceUpdated\\n */\\n function updateMaxPrice(address asset, uint128 newMax) external;\\n\\n /**\\n * @notice Exits protection mode for a given asset once conditions are met\\n * @param asset The underlying asset address\\n * @custom:access Only authorized monitor/keeper addresses\\n * @custom:error ProtectedPriceInactive if protection is not currently active\\n * @custom:error CooldownNotElapsed if the cooldown period has not elapsed since the last trigger\\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\\n * @custom:event ProtectionModeExited\\n */\\n function exitProtectionMode(address asset) external;\\n\\n /**\\n * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\\n * @dev Each item is processed in array order; any item revert rolls back the whole batch.\\n * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode.\\n * Empty `actions` is a no-op success.\\n * @param actions The list of keeper actions to apply\\n * @custom:access Only authorized keeper addresses\\n * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value\\n * @custom:error PriceExceedsUint128 if a SetMin/SetMax item value overflows uint128\\n * @custom:error ZeroPriceNotAllowed if a SetMin/SetMax item value is zero\\n * @custom:error MarketNotInitialized if any referenced asset has not been initialized\\n * @custom:error InvalidMinPrice if a SetMinPrice item violates the spot/maxPrice constraints\\n * @custom:error InvalidMaxPrice if a SetMaxPrice item violates the spot/minPrice constraints\\n * @custom:error ProtectedPriceInactive if an ExitProtectionMode item targets an asset whose protection is not active\\n * @custom:error CooldownNotElapsed if an ExitProtectionMode item is submitted before cooldown elapsed\\n * @custom:error PriceRangeNotConverged if an ExitProtectionMode item is submitted before window convergence\\n * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\\n */\\n function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external;\\n\\n // --- Admin functions (governance-gated) ---\\n\\n /**\\n * @notice Initializes protection parameters for a new asset\\n * @dev Seeds the initial min/max window from the current ResilientOracle spot price,\\n * confirming the oracle is live for this asset before it is listed.\\n * @param tokenConfig_ Token config input for the asset\\n * @custom:access Only Governance\\n * @custom:error ZeroAddressNotAllowed if asset is the zero address\\n * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero\\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\\n * @custom:error VAINotAllowed if asset is the VAI token\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event ProtectionInitialized\\n * @custom:event BoundedPricingWhitelistUpdated\\n */\\n function setTokenConfig(TokenConfigInput calldata tokenConfig_) external;\\n\\n /**\\n * @notice Batch-initializes protection parameters for multiple assets in a single transaction\\n * @param tokenConfigs_ Array of token config inputs, one per asset\\n * @custom:access Only Governance\\n * @custom:error InvalidArrayLength if the input array is empty\\n * @custom:error ZeroAddressNotAllowed if any asset is the zero address\\n * @custom:error ZeroValueNotAllowed if any cooldownPeriod, triggerThreshold, or resetThreshold is zero\\n * @custom:error MarketAlreadyInitialized if any asset has already been initialized\\n * @custom:error ThresholdBelowMinimum if any triggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if any triggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if any resetThreshold is at or above its triggerThreshold\\n * @custom:error VAINotAllowed if any asset is the VAI token\\n * @custom:error PriceExceedsUint128 if the spot price for any asset overflows uint128\\n * @custom:event ProtectionInitialized for each asset\\n * @custom:event BoundedPricingWhitelistUpdated for each asset\\n */\\n function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external;\\n\\n /**\\n * @notice Sets the cooldown period for an asset\\n * @param asset The underlying asset address\\n * @param newCooldown The new cooldown period in seconds; must be non-zero\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:event CooldownPeriodSet\\n */\\n function setCooldownPeriod(address asset, uint64 newCooldown) external;\\n\\n /**\\n * @notice Sets the trigger and reset thresholds for an asset\\n * @param asset The underlying asset address\\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\\n * @custom:event TriggerThresholdSet if the trigger threshold changed\\n * @custom:event ResetThresholdSet if the reset threshold changed\\n */\\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external;\\n\\n /**\\n * @notice Sets whether bounded pricing is enabled for an asset\\n * @param asset The underlying asset address\\n * @param enabled Whether bounded pricing should be enabled for the asset\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\\n * @custom:event BoundedPricingWhitelistUpdated\\n */\\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external;\\n\\n /**\\n * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset\\n * @dev When disabled, each view/non-view price call recomputes bounded prices from the\\n * live spot instead of reading or writing the transient slots. The initial value is\\n * set via the `enableCaching` argument of `setTokenConfig`.\\n * @param asset The underlying asset address\\n * @param enabled Whether transient caching is enabled for this asset\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:event CachingEnabledUpdated\\n */\\n function setCachingEnabled(address asset, bool enabled) external;\\n\\n // --- View helpers ---\\n\\n /**\\n * @notice Returns the full protection state for an asset\\n * @param asset The underlying asset address\\n * @return minPrice Lowest price observed in the current window\\n * @return maxPrice Highest price observed in the current window\\n * @return currentlyUsingProtectedPrice Whether protected price is currently active\\n * @return isBoundedPricingEnabled Whether the asset is whitelisted for bounded pricing\\n * @return lastProtectionTriggeredAt Timestamp of the last protection trigger\\n * @return cooldownPeriod Minimum time protection stays active after last trigger\\n * @return assetAddr The underlying asset address stored in the struct\\n * @return triggerThreshold Entry deviation threshold (mantissa) that activates protection\\n * @return resetThreshold Exit deviation threshold (mantissa) below which protection can be disabled\\n * @return cachingEnabled Whether transient caching of the bounded pair is enabled for the asset\\n */\\n function assetProtectionConfig(\\n address asset\\n )\\n external\\n view\\n returns (\\n uint128 minPrice,\\n uint128 maxPrice,\\n bool currentlyUsingProtectedPrice,\\n bool isBoundedPricingEnabled,\\n uint64 lastProtectionTriggeredAt,\\n uint64 cooldownPeriod,\\n address assetAddr,\\n uint128 triggerThreshold,\\n uint128 resetThreshold,\\n bool cachingEnabled\\n );\\n\\n /**\\n * @notice Checks if an asset is whitelisted for bounded pricing\\n * @param asset The underlying asset address\\n * @return True if the asset is whitelisted\\n */\\n function isBoundedPricingEnabled(address asset) external view returns (bool);\\n\\n /**\\n * @notice Checks if the asset is currently using the protected (bounded) price\\n * @param asset The underlying asset address\\n * @return True if the asset is currently using the protected price instead of spot\\n */\\n function currentlyUsingProtectedPrice(address asset) external view returns (bool);\\n\\n /**\\n * @notice Checks if protection can be exited for a given asset\\n * @param asset The underlying asset address\\n * @return True if both the cooldown has elapsed and the price range has converged below the exit threshold\\n */\\n function canExitProtection(address asset) external view returns (bool);\\n\\n /**\\n * @notice Returns the initialized asset at the given index (auto-generated array getter)\\n * @param index Array index\\n * @return The asset address at the given index\\n */\\n function allAssets(uint256 index) external view returns (address);\\n\\n /**\\n * @notice Returns all currently whitelisted asset addresses\\n * @return result Array of whitelisted asset addresses\\n */\\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory result);\\n\\n /**\\n * @notice Returns all asset addresses that have ever been initialized\\n * @return Array of all initialized asset addresses\\n */\\n function getInitializedAssets() external view returns (address[] memory);\\n\\n /**\\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the keeper deadband\\n * @param assets Array of asset addresses to check\\n * @param proposedMins Keeper's proposed window minimum prices\\n * @param proposedMaxs Keeper's proposed window maximum prices\\n * @return needsMinUpdate Whether minPrice drift exceeds the deadband for each asset\\n * @return needsMaxUpdate Whether maxPrice drift exceeds the deadband for each asset\\n * @custom:error InvalidArrayLength if the input array lengths do not match\\n */\\n function checkAndGetWindowDrift(\\n address[] calldata assets,\\n uint128[] calldata proposedMins,\\n uint128[] calldata proposedMaxs\\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate);\\n}\\n\",\"keccak256\":\"0xe223d88c17c0d752fdb33fa223b63ce124a798512f3d69f3800e9508e7dff931\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\ninterface OracleInterface {\\n function getPrice(address asset) external view returns (uint256);\\n}\\n\\ninterface ResilientOracleInterface is OracleInterface {\\n function updatePrice(address vToken) external;\\n\\n function updateAssetPrice(address asset) external;\\n\\n function getUnderlyingPrice(address vToken) external view returns (uint256);\\n}\\n\\ninterface BoundValidatorInterface {\\n function validatePriceWithAnchorPrice(\\n address asset,\\n uint256 reporterPrice,\\n uint256 anchorPrice\\n ) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xd3bbb7c9eef19e8f467342df6034ef95399a00964646fb8c82b438968ae3a8c0\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/solidity-utilities/contracts/constants.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\n/// @dev Base unit for computations, usually used in scaling (multiplications, divisions)\\nuint256 constant EXP_SCALE = 1e18;\\n\\n/// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions\\nuint256 constant MANTISSA_ONE = EXP_SCALE;\\n\\n/// @dev The approximate number of seconds per year\\nuint256 constant SECONDS_PER_YEAR = 31_536_000;\\n\",\"keccak256\":\"0x14de93ead464da249af31bea0e3bcfb62ec693bea3475fb4d90f055ac81dc5eb\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/solidity-utilities/contracts/validators.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\n/// @notice Thrown if the supplied address is a zero address where it is not allowed\\nerror ZeroAddressNotAllowed();\\n\\n/// @notice Thrown if the supplied value is 0 where it is not allowed\\nerror ZeroValueNotAllowed();\\n\\n/// @notice Checks if the provided address is nonzero, reverts otherwise\\n/// @param address_ Address to check\\n/// @custom:error ZeroAddressNotAllowed is thrown if the provided address is a zero address\\nfunction ensureNonzeroAddress(address address_) pure {\\n if (address_ == address(0)) {\\n revert ZeroAddressNotAllowed();\\n }\\n}\\n\\n/// @notice Checks if the provided value is nonzero, reverts otherwise\\n/// @param value_ Value to check\\n/// @custom:error ZeroValueNotAllowed is thrown if the provided value is 0\\nfunction ensureNonzeroValue(uint256 value_) pure {\\n if (value_ == 0) {\\n revert ZeroValueNotAllowed();\\n }\\n}\\n\",\"keccak256\":\"0xdb88e14d50dd21889ca3329d755673d022c47e8da005b6a545c7f69c2c4b7b86\",\"license\":\"BSD-3-Clause\"},\"contracts/BStock/BStockLiquidator.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { IERC20Upgradeable } from \\\"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\\\";\\nimport { SafeERC20Upgradeable } from \\\"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\\\";\\nimport { Ownable2StepUpgradeable } from \\\"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\\\";\\nimport { ReentrancyGuardUpgradeable } from \\\"@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol\\\";\\nimport { ensureNonzeroAddress } from \\\"@venusprotocol/solidity-utilities/contracts/validators.sol\\\";\\n\\nimport { VToken } from \\\"../Tokens/VTokens/VToken.sol\\\";\\nimport { IFlashLoanReceiver } from \\\"../FlashLoan/interfaces/IFlashLoanReceiver.sol\\\";\\nimport { IWBNB } from \\\"../external/IWBNB.sol\\\";\\n\\n// Shared Venus interfaces: IComptroller (Core diamond \\u2014 liquidator gate + flash loan), IVToken\\n// (flash-loan asset array element), and ILiquidator (the pool-wide Venus Liquidator gate that pulls\\n// the repay and returns our share of the seized collateral).\\nimport { IComptroller, IVToken, ILiquidator, IVAIController } from \\\"../InterfacesV8.sol\\\";\\nimport { IBStockLiquidator } from \\\"./IBStockLiquidator.sol\\\";\\n\\n/**\\n * @title BStockLiquidator\\n * @author Venus\\n * @notice Atomic backstop liquidator for bStock (ERC-8056 tokenized stock) collateral.\\n *\\n * In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken,\\n * redeems it to the raw bStock, and sells that bStock to the debt asset in one or two hops. Hop 1\\n * sells bStock (to USDT) through an allowlisted RFQ router using a pre-fetched, off-chain-signed\\n * `swapCalldata` \\u2014 Native firm-quote or Liquid Mesh (see `routerSpender`) or any future allowlisted\\n * source; the contract is router-agnostic and just forwards the opaque blob. For USDT debt that single\\n * hop lands the debt asset directly. For non-USDT debt an OPTIONAL hop 2 (RFQ sources quote bStock->USDT\\n * only) converts the USDT to the debt asset through a second allowlisted router (an AMM/aggregator).\\n * Because seize and sell happen in the same tx there is no price-drift window, and the realized\\n * debt-asset amount must clear `minOut` or the whole call reverts \\u2014 the protocol never ends up holding\\n * the RFQ-only asset or the intermediate.\\n *\\n * Two funding modes share the same core (`_liquidate`):\\n * - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance.\\n * - FLASH: the debt asset is flash-borrowed from Venus (`Comptroller.executeFlashLoan`) and repaid\\n * (+ premium) within the same tx; no capital is locked in the contract.\\n *\\n * Native BNB debt (vBNB): supported in both modes with WBNB as the debt-accounting token. The repay\\n * must be native BNB, so exactly the repay amount of WBNB is unwrapped and forwarded to the gate's\\n * payable path; the two-hop swap lands WBNB (bStock->USDT->WBNB) and `minOut` is measured in WBNB\\n * (1:1 with BNB). FLASH mode borrows from vWBNB, NOT vBNB: vBNB cannot be flash-repaid (its\\n * `doTransferIn` requires `msg.value`), whereas vWBNB's underlying is a plain ERC20.\\n *\\n * VAI debt (VAIController): supported in INVENTORY mode only. VAI is not a vToken \\u2014 a `vDebt` equal to\\n * `comptroller.vaiController()` is VAI, and like vBNB it has no `underlying()`, so the debt token is\\n * resolved via `getVAIAddress()`. The repay is a plain ERC20 approval to the gate, which takes its\\n * `_liquidateVAI` branch (pulls the VAI from us, then burns it via `VAIController.liquidateVAI`).\\n * Because RFQ sources quote bStock->USDT only, a VAI debt is inherently two-hop (bStock->USDT->VAI);\\n * hop 2 is expected to be the Peg Stability Module (`swapStableForVAI`, allowlisted as `router2`),\\n * which mints VAI from USDT at the oracle rate. FLASH mode is rejected (`FlashNotSupportedForVai`):\\n * `executeFlashLoan` lends a vToken's underlying, and VAI is minted/burned with no vVAI market.\\n *\\n * Ownership / scope: this is Venus's OWN backstop tool, NOT a public utility \\u2014 `liquidate` and\\n * `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock\\n * liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus\\n * path with their own funds and their own offload. This contract is intentionally gated because it\\n * custodies funds (debt-asset inventory / flash principal) and forwards a CALLER-SUPPLIED calldata blob to\\n * an external router \\u2014 the swap's recipient (`to`) lives inside that calldata, so an open entrypoint\\n * would let anyone route the proceeds to themselves and drain the contract. The router allowlist and\\n * `minOut` bound the blast radius but cannot replace operator-gating.\\n *\\n * Security model:\\n * - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator).\\n * - BOTH swap targets (`router` and, when set, `router2`) must be allowlisted (`isRouter`) \\u2014 defends\\n * the low-level `router.call(swapCalldata)` on each hop.\\n * - the approval for each hop is the exact amount being sold, granted to the router's configured spender\\n * (the router itself when unset \\u2014 see `routerSpender`) and reset to 0 afterwards (bStock on hop 1; the\\n * measured intermediate balance delta on hop 2, so pre-existing inventory is never exposed).\\n * - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started).\\n * - the realized debt-asset amount must clear `minOut` or the tx reverts.\\n *\\n * Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`,\\n * which is always configured on the networks this contract targets. While it is set, a direct\\n * `vToken.liquidateBorrow` from an arbitrary caller reverts UNAUTHORIZED, so this contract reads the gate\\n * at call time and routes the repay through that Venus Liquidator (the permissionless entry anyone may\\n * call), reverting if the gate is ever unset. Routing through the gate needs no governance change, and no\\n * other Core market is affected. Note: setting THIS contract as `liquidatorContract` is NOT an option \\u2014\\n * the gate is pool-wide, so every other market's liquidations would be forced through here.\\n */\\ncontract BStockLiquidator is\\n Ownable2StepUpgradeable,\\n ReentrancyGuardUpgradeable,\\n IBStockLiquidator,\\n IFlashLoanReceiver\\n{\\n using SafeERC20Upgradeable for IERC20Upgradeable;\\n\\n /// @notice Core Comptroller (diamond): reads the liquidation gate and provides the flash loan\\n /// via `executeFlashLoan`.\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n IComptroller public immutable comptroller;\\n\\n /// @notice The native BNB market (vBNB). A debt equal to this address is settled with native BNB:\\n /// WBNB is the debt-accounting token, and only the repay amount is unwrapped (see `_liquidate`).\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n address public immutable vBNB;\\n\\n /// @notice The WBNB market (vWBNB). BNB debt is flash-funded from here, NOT from vBNB: vBNB cannot be\\n /// flash-repaid (its `doTransferIn` needs `msg.value`), whereas vWBNB's underlying is a plain\\n /// ERC20 repaid via `transferFrom`.\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n IVToken public immutable vWBNB;\\n\\n /// @notice WBNB token: the debt-accounting asset for BNB debt, unwrapped to native BNB for the repay.\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n IWBNB public immutable wbnb;\\n\\n /// @notice Addresses allowed to trigger a liquidation.\\n mapping(address => bool) public isOperator;\\n\\n /// @notice Routers allowed as the swap target (defends the low-level call).\\n mapping(address => bool) public isRouter;\\n\\n /// @notice Optional token-approval target (spender) per router, for aggregators whose settlement\\n /// contract that pulls the input token differs from the call target (e.g. Liquid Mesh, where\\n /// the router is the call target but a separate spender pulls the token). When unset\\n /// (address(0)), the approval defaults to the router itself \\u2014 the Native behaviour, where the\\n /// call target IS the puller \\u2014 so existing routers need no spender entry.\\n mapping(address => address) public routerSpender;\\n\\n /// @dev Reserved storage to allow new state variables in future upgrades without layout clashes.\\n uint256[49] private __gap;\\n\\n modifier onlyOperator() {\\n if (msg.sender != owner() && !isOperator[msg.sender]) revert NotOperator();\\n _;\\n }\\n\\n /// @notice Constructor for the implementation contract. Sets the immutables and locks initializers.\\n /// @param comptroller_ Venus Core Comptroller (diamond) \\u2014 gates liquidation and provides flash loans.\\n /// @param vBNB_ Native BNB market; a debt equal to this address is settled in native BNB.\\n /// @param vWBNB_ WBNB market; the flash-borrow source for BNB debt (vBNB itself cannot be flash-repaid).\\n /// @param wbnb_ WBNB token; the debt-accounting asset for BNB debt, unwrapped for the native repay.\\n /// @custom:oz-upgrades-unsafe-allow constructor\\n constructor(IComptroller comptroller_, address vBNB_, IVToken vWBNB_, IWBNB wbnb_) {\\n ensureNonzeroAddress(address(comptroller_));\\n ensureNonzeroAddress(vBNB_);\\n ensureNonzeroAddress(address(vWBNB_));\\n ensureNonzeroAddress(address(wbnb_));\\n comptroller = comptroller_;\\n vBNB = vBNB_;\\n vWBNB = vWBNB_;\\n wbnb = wbnb_;\\n _disableInitializers();\\n }\\n\\n /// @notice Initializes the proxy: sets the owner and the reentrancy guard.\\n /// @param initialOwner Address that owns the contract (admin + default operator).\\n function initialize(address initialOwner) external initializer {\\n ensureNonzeroAddress(initialOwner);\\n __Ownable2Step_init();\\n __ReentrancyGuard_init();\\n _transferOwnership(initialOwner);\\n }\\n\\n /// @notice Accept native BNB. The expected inflow is `wbnb.withdraw` during a BNB liquidation (the\\n /// unwrapped repay is forwarded to the gate in the same call, so no BNB is retained on the\\n /// happy path). Left permissive (not restricted to `wbnb`) both to keep the receive body\\n /// minimal for WBNB's 2300-gas `.transfer` stipend and to tolerate a stray transfer or a\\n /// future gate refund \\u2014 any such balance is recoverable via `sweepNative`.\\n receive() external payable {}\\n\\n // --------------------------------------------------------------------- //\\n // Admin //\\n // --------------------------------------------------------------------- //\\n\\n /// @inheritdoc IBStockLiquidator\\n function setOperator(address operator, bool allowed) external override onlyOwner {\\n ensureNonzeroAddress(operator);\\n isOperator[operator] = allowed;\\n emit OperatorSet(operator, allowed);\\n }\\n\\n /// @inheritdoc IBStockLiquidator\\n function setRouter(address router, bool allowed) external override onlyOwner {\\n ensureNonzeroAddress(router);\\n isRouter[router] = allowed;\\n // De-allowlisting also clears any configured spender: a stale entry must not silently\\n // reactivate (with a possibly rotated-away spender) if the router is ever re-allowlisted.\\n if (!allowed && routerSpender[router] != address(0)) {\\n delete routerSpender[router];\\n emit RouterSpenderSet(router, address(0));\\n }\\n emit RouterSet(router, allowed);\\n }\\n\\n /// @inheritdoc IBStockLiquidator\\n function setRouterSpender(address router, address spender) external override onlyOwner {\\n // Couple the spender lifecycle to the allowlist: a spender only ever matters for an\\n // allowlisted router (`_swap` approves it right before the router call), so requiring\\n // `isRouter` here catches a fat-fingered router address instead of storing it silently.\\n if (!isRouter[router]) revert RouterNotAllowed(router);\\n // `spender == address(0)` is allowed and clears the entry, reverting the router to\\n // approve-the-call-target (Native) behaviour. A non-zero spender receives a live (exact-amount,\\n // same-tx) approval during `_swap`, so require it to be a deployed contract \\u2014 an EOA spender is\\n // always a misconfiguration.\\n if (spender != address(0) && spender.code.length == 0) revert SpenderNotContract(spender);\\n routerSpender[router] = spender;\\n emit RouterSpenderSet(router, spender);\\n }\\n\\n /// @inheritdoc IBStockLiquidator\\n function sweep(address token, address to, uint256 amount) external override onlyOwner {\\n ensureNonzeroAddress(token);\\n ensureNonzeroAddress(to);\\n IERC20Upgradeable(token).safeTransfer(to, amount);\\n emit Swept(token, to, amount);\\n }\\n\\n /// @inheritdoc IBStockLiquidator\\n /// @dev `nonReentrant` is defense-in-depth only (the function is `onlyOwner`, snapshots no state, and\\n /// reads no balance after the native `.call`), added for consistency with the liquidation entrypoints.\\n function sweepNative(address to, uint256 amount) external override onlyOwner nonReentrant {\\n ensureNonzeroAddress(to);\\n (bool ok, ) = to.call{ value: amount }(\\\"\\\");\\n if (!ok) revert NativeTransferFailed();\\n emit SweptNative(to, amount);\\n }\\n\\n /// @notice Disabled. This backstop custodies protocol capital (debt-asset inventory, native BNB) and\\n /// every admin function (`sweep`, `sweepNative`, `setOperator`, `setRouter`) is `onlyOwner`,\\n /// so renouncing ownership would permanently strand those funds and brick the contract. The\\n /// override is an `onlyOwner` no-op: an accidental owner call cannot zero the owner, and a\\n /// non-owner call reverts. Ownership is still transferable via the two-step `transferOwnership` flow.\\n function renounceOwnership() public override onlyOwner {}\\n\\n // --------------------------------------------------------------------- //\\n // INVENTORY mode //\\n // --------------------------------------------------------------------- //\\n\\n /// @inheritdoc IBStockLiquidator\\n /// @dev INVENTORY mode spends the contract's OWN debt-asset capital, so \\u2014 unlike FLASH mode, where\\n /// `executeOperation` forces the swap proceeds to cover principal + premium \\u2014 there is no\\n /// built-in floor tying `debtOut` to `repayAmount`. This asymmetry is intentional: a repay can\\n /// legitimately out-cost its proceeds (e.g. the Venus Liquidator keeps a treasury cut of the\\n /// seized collateral, so proceeds land a few % under the repay). `minOut` IS the operator's\\n /// chosen loss floor for inventory mode \\u2014 set it to the lowest acceptable debt-asset return.\\n function liquidate(\\n LiquidationParams calldata params\\n ) external override onlyOperator nonReentrant returns (uint256 debtOut) {\\n _validateRouters(params.router, params.router2);\\n\\n if (params.minOut == 0) revert ZeroMinOut();\\n if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp);\\n uint256 seizedBStock;\\n (debtOut, seizedBStock) = _liquidate(params);\\n emit Liquidated(\\n params.borrower,\\n address(params.vBStock),\\n address(params.vDebt),\\n params.repayAmount,\\n seizedBStock,\\n debtOut,\\n false\\n );\\n }\\n\\n // --------------------------------------------------------------------- //\\n // FLASH mode //\\n // --------------------------------------------------------------------- //\\n\\n /// @inheritdoc IBStockLiquidator\\n function flashLiquidate(LiquidationParams calldata params) external override onlyOperator nonReentrant {\\n _validateRouters(params.router, params.router2);\\n\\n if (params.minOut == 0) revert ZeroMinOut();\\n if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp);\\n\\n // VAI has no market to flash from: `executeFlashLoan` lends a vToken's underlying, whereas VAI is\\n // MINTED/BURNED by the VAIController (`repayVAIFresh` burns it) and has no vVAI. Reject up front\\n // instead of passing the VAIController into `executeFlashLoan` and failing opaquely. Use\\n // `liquidate` (INVENTORY) with pre-funded VAI for a VAI debt.\\n if (address(params.vDebt) == address(comptroller.vaiController())) revert FlashNotSupportedForVai();\\n\\n // BNB debt is flash-funded from vWBNB (an ERC20 market), not vBNB: vBNB cannot be flash-repaid.\\n // The flashed WBNB is unwrapped to native BNB for the repay inside `_liquidate` (see `executeOperation`).\\n IVToken[] memory vTokens = new IVToken[](1);\\n vTokens[0] = (address(params.vDebt) == vBNB) ? vWBNB : IVToken(address(params.vDebt));\\n uint256[] memory amounts = new uint256[](1);\\n amounts[0] = params.repayAmount;\\n\\n comptroller.executeFlashLoan(\\n payable(address(this)),\\n payable(address(this)),\\n vTokens,\\n amounts,\\n abi.encode(params)\\n );\\n }\\n\\n /// @inheritdoc IFlashLoanReceiver\\n function executeOperation(\\n VToken[] calldata vTokens,\\n uint256[] calldata amounts,\\n uint256[] calldata premiums,\\n address initiator,\\n address /* onBehalf */,\\n bytes calldata param\\n ) external override returns (bool, uint256[] memory repayAmounts) {\\n if (msg.sender != address(comptroller)) revert OnlyComptroller();\\n // initiator == this proves the flash was started by our own flashLiquidate: the FlashLoanFacet\\n // passes msg.sender (the executeFlashLoan caller) as `initiator`, and only flashLiquidate calls it.\\n if (initiator != address(this)) revert BadInitiator(initiator);\\n\\n LiquidationParams memory params = abi.decode(param, (LiquidationParams));\\n // For BNB debt the flash is drawn from vWBNB, not vBNB (see `flashLiquidate`). Scoped so the\\n // temporary doesn't count against the stack depth of the rest of the function.\\n {\\n address expectedFlash = address(params.vDebt) == vBNB ? address(vWBNB) : address(params.vDebt);\\n if (address(vTokens[0]) != expectedFlash) revert WrongFlashAsset();\\n }\\n\\n // Repay was just funded by the flash loan; run the liquidation + swap.\\n (uint256 debtOut, uint256 seizedBStock) = _liquidate(params);\\n\\n // The swap proceeds alone MUST cover principal + premium. Without this, any debt-asset inventory\\n // held by the contract would silently backfill an underwater swap (a real loss), since the\\n // flash repayment is pulled from the total balance, not just the swap output.\\n repayAmounts = new uint256[](1);\\n repayAmounts[0] = amounts[0] + premiums[0];\\n if (debtOut < repayAmounts[0]) revert InsufficientOut(debtOut, repayAmounts[0]);\\n\\n // Approve the flashed vToken to pull back principal + premium. For BNB debt the flashed asset is\\n // WBNB (from vWBNB); the ternary short-circuits so `underlying()` is never called on vBNB (it has none).\\n IERC20Upgradeable(address(params.vDebt) == vBNB ? address(wbnb) : params.vDebt.underlying()).forceApprove(\\n address(vTokens[0]),\\n repayAmounts[0]\\n );\\n\\n emit Liquidated(\\n params.borrower,\\n address(params.vBStock),\\n address(params.vDebt),\\n params.repayAmount,\\n seizedBStock,\\n debtOut,\\n true\\n );\\n return (true, repayAmounts);\\n }\\n\\n // --------------------------------------------------------------------- //\\n // Core //\\n // --------------------------------------------------------------------- //\\n\\n /// @dev Pre-flight: every swap router must be allowlisted. `router2` is optional (single-hop when\\n /// zero) so it is only checked when set. Liquidatability itself is not pre-checked here \\u2014 Core's\\n /// `liquidateBorrowAllowed` already enforces it, and pre-checking shortfall would wrongly block\\n /// forced liquidations (which liquidate healthy accounts).\\n function _validateRouters(address router, address router2) private view {\\n if (!isRouter[router]) revert RouterNotAllowed(router);\\n if (router2 != address(0) && !isRouter[router2]) revert RouterNotAllowed(router2);\\n }\\n\\n /// @dev One swap hop: approve the exact `amount` to the router's configured spender, forward the\\n /// opaque calldata via a low-level call to the allowlisted `router`, then reset the approval to\\n /// 0. The spender defaults to the router itself when unset (Native, where the call target is the\\n /// puller); aggregators with a separate settlement/pull contract (e.g. Liquid Mesh) set it via\\n /// `setRouterSpender`. The approval caps what the spender can pull; if the calldata sells less\\n /// (e.g. a partially-filled RFQ quote), the unconsumed remainder stays in the contract and is\\n /// surfaced via `PartialSwapLeftover` so operations can recover it with `sweep` \\u2014 `minOut` still\\n /// bounds the realized debt-asset proceeds regardless.\\n function _swap(IERC20Upgradeable token, address router, bytes memory data, uint256 amount) private {\\n uint256 balBefore = token.balanceOf(address(this));\\n // Approve the PULLER, which is not always the call target: Liquid Mesh and similar split-settlement\\n // aggregators pull through a separate spender. Defaults to the router when unset, so Native (whose\\n // call target is the puller) is unaffected.\\n address spender = routerSpender[router];\\n if (spender == address(0)) spender = router;\\n token.forceApprove(spender, amount);\\n (bool ok, bytes memory returndata) = router.call(data);\\n if (!ok) {\\n // Bubble up the router's own revert reason for easier debugging; fall back to SwapFailed()\\n // only when the call reverted without returndata.\\n if (returndata.length != 0) {\\n assembly {\\n revert(add(returndata, 0x20), mload(returndata))\\n }\\n }\\n revert SwapFailed();\\n }\\n token.forceApprove(spender, 0); // never leave a standing approval\\n // `token` is the hop's INPUT: the spender can pull at most `amount` (the approval, just reset),\\n // and any refund is a subset of what it pulled, so the balance can only fall (balAfter <=\\n // balBefore) \\u2014 the subtraction cannot underflow. A shortfall (spent < amount) means the router\\n // filled less than approved; emit the residual so it can be swept.\\n uint256 spent = balBefore - token.balanceOf(address(this));\\n if (spent < amount) emit PartialSwapLeftover(address(token), amount - spent);\\n }\\n\\n /**\\n * @dev The atomic sequence shared by both funding modes:\\n * approve debt -> liquidateBorrow (seize vBStock) -> redeem (raw bStock)\\n * -> swap bStock to the debt asset (one hop, or two via an intermediate) -> assert minOut.\\n * A `calldata` struct from `liquidate` is copied to memory on entry; `executeOperation`\\n * already holds it in memory (decoded from the flash callback).\\n * @param params Liquidation parameters.\\n * @return debtOut Debt-asset proceeds realized by the swap chain (reverts if below `minOut`).\\n * @return seizedBStock Raw bStock redeemed and sold (balance delta).\\n */\\n function _liquidate(LiquidationParams memory params) private returns (uint256 debtOut, uint256 seizedBStock) {\\n // A debt equal to `vBNB` is native BNB. vBNB has no `underlying()`, so the `isBnb` check MUST come\\n // first: the ternary short-circuits and `underlying()` is never evaluated for vBNB. WBNB is the\\n // debt-accounting token throughout (1:1 with BNB), so the whole swap/minOut path below is reused.\\n // KEEP THIS ORDER \\u2014 hoisting `underlying()` above the check reverts every BNB liquidation.\\n bool isBnb = address(params.vDebt) == vBNB;\\n IERC20Upgradeable debt;\\n // Scoped so `vaiCtrl`/`isVai` don't hold stack slots for the rest of the frame.\\n {\\n // A debt equal to the VAIController is VAI: it is not a vToken and has no `underlying()`\\n // either, so \\u2014 like vBNB \\u2014 the check MUST come before any `underlying()` evaluation. The gate\\n // takes its VAI branch (`Liquidator._liquidateVAI`), which pulls VAI from us and burns it via\\n // `VAIController.liquidateVAI`; the repay is a plain ERC20 approval, so the non-BNB path below\\n // is reused as-is.\\n IVAIController vaiCtrl = comptroller.vaiController();\\n bool isVai = address(params.vDebt) == address(vaiCtrl);\\n // RFQ sources only quote bStock->USDT, so BNB and VAI debts are inherently two-hop\\n // (...->WBNB / ...->VAI). Reject a single-hop config up front instead of failing opaquely\\n // later on a zero debt-asset delta.\\n if ((isBnb || isVai) && params.router2 == address(0)) revert InvalidIntermediate();\\n debt = isBnb\\n ? IERC20Upgradeable(address(wbnb))\\n : isVai\\n ? IERC20Upgradeable(vaiCtrl.getVAIAddress())\\n : IERC20Upgradeable(params.vDebt.underlying());\\n }\\n IERC20Upgradeable bStock = IERC20Upgradeable(params.vBStock.underlying());\\n\\n // 1. Repay the borrow, seizing the bStock vToken to this contract.\\n // Core has a POOL-WIDE liquidator gate (`liquidatorContract`), which is always configured on\\n // the networks this contract targets. While it is set, a direct `vToken.liquidateBorrow`\\n // reverts UNAUTHORIZED, so we route the repay through that Venus Liquidator (it pulls our\\n // repay and sends us our share of the seized collateral; treasury keeps a cut). The seized\\n // amount is read as a BALANCE DELTA, so the Liquidator's cut is handled correctly. We guard\\n // against an unset gate so a misconfig fails loudly instead of silently no-op'ing a call to\\n // address(0) (a low-level call to a codeless address returns success).\\n uint256 vBefore = params.vBStock.balanceOf(address(this));\\n address gate = comptroller.liquidatorContract();\\n ensureNonzeroAddress(gate);\\n if (isBnb) {\\n // Unwrap EXACTLY the repay (WBNB held as inventory or drawn from the vWBNB flash) and forward\\n // native BNB to the gate's vBNB branch (`{value:}`). Only the repay portion is unwrapped, so\\n // pre-existing WBNB inventory is untouched; the swap proceeds below stay as WBNB. No approval\\n // is granted (value is forwarded), so there is no standing allowance to reset.\\n wbnb.withdraw(params.repayAmount);\\n ILiquidator(gate).liquidateBorrow{ value: params.repayAmount }(\\n address(params.vDebt),\\n params.borrower,\\n params.repayAmount,\\n params.vBStock\\n );\\n } else {\\n debt.forceApprove(gate, params.repayAmount);\\n ILiquidator(gate).liquidateBorrow(\\n address(params.vDebt),\\n params.borrower,\\n params.repayAmount,\\n params.vBStock\\n );\\n // Reset the gate approval: if the Liquidator pulled less than `repayAmount` (e.g. a close-factor\\n // cap), the remainder would otherwise linger as a standing allowance. Same invariant as `_swap`.\\n debt.forceApprove(gate, 0);\\n }\\n uint256 seizedV = params.vBStock.balanceOf(address(this)) - vBefore;\\n\\n // 2. Redeem the seized vBStock for raw bStock. Measure by DELTA so any pre-existing bStock\\n // (dust or a stray transfer) is excluded \\u2014 we only sell what this redeem actually returned.\\n uint256 rawBefore = bStock.balanceOf(address(this));\\n uint256 redeemErr = params.vBStock.redeem(seizedV);\\n if (redeemErr != 0) revert RedeemFailed(redeemErr);\\n seizedBStock = bStock.balanceOf(address(this)) - rawBefore;\\n\\n // 3. Sell the bStock to the debt asset (one hop, or two via an intermediate) and assert minOut.\\n // Extracted into `_sellToDebt` to keep this frame within the EVM stack limit.\\n debtOut = _sellToDebt(debt, bStock, seizedBStock, params);\\n }\\n\\n /**\\n * @dev Sell `seizedBStock` to the debt asset and enforce `minOut`. Single hop by default\\n * (bStock -> debt via the Native router). When `params.router2` is set, two hops\\n * (bStock -> intermediate -> debt): hop 1 sells bStock to the intermediate (USDT) via the\\n * Native router, hop 2 converts that intermediate to the debt asset via a second allowlisted\\n * router (AMM/aggregator). `minOut` is measured in the debt asset across the whole chain.\\n * @return debtOut Debt-asset proceeds (balance delta), reverting if below `minOut`.\\n */\\n function _sellToDebt(\\n IERC20Upgradeable debt,\\n IERC20Upgradeable bStock,\\n uint256 seizedBStock,\\n LiquidationParams memory params\\n ) private returns (uint256 debtOut) {\\n uint256 debtBefore = debt.balanceOf(address(this));\\n if (params.router2 == address(0)) {\\n _swap(bStock, params.router, params.swapCalldata, seizedBStock);\\n } else {\\n // The intermediate must be a real token distinct from both endpoints: if it equals `debt`,\\n // hop 1 would inflate the balance `debtBefore` snapshots against (breaking the proceeds\\n // delta); if it equals `bStock`, the hop-1 sell shrinks the balance and the midDelta\\n // subtraction underflows.\\n if (\\n params.intermediateToken == address(0) ||\\n params.intermediateToken == address(debt) ||\\n params.intermediateToken == address(bStock)\\n ) revert InvalidIntermediate();\\n\\n IERC20Upgradeable mid = IERC20Upgradeable(params.intermediateToken);\\n uint256 midBefore = mid.balanceOf(address(this));\\n _swap(bStock, params.router, params.swapCalldata, seizedBStock); // hop 1: bStock -> intermediate\\n // Only the hop-1 proceeds are sold onward; any pre-existing intermediate inventory is excluded.\\n uint256 midDelta = mid.balanceOf(address(this)) - midBefore;\\n _swap(mid, params.router2, params.swapCalldata2, midDelta); // hop 2: intermediate -> debt\\n }\\n\\n debtOut = debt.balanceOf(address(this)) - debtBefore;\\n if (debtOut < params.minOut) revert InsufficientOut(debtOut, params.minOut);\\n }\\n}\\n\",\"keccak256\":\"0xa3b32d763613855f7657b7577821ebd40dbf373bc67425eac4f7f649393242d6\",\"license\":\"BSD-3-Clause\"},\"contracts/BStock/IBStockLiquidator.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { IVBep20 } from \\\"../InterfacesV8.sol\\\";\\n\\n/// @title IBStockLiquidator\\n/// @author Venus\\n/// @notice External API, events, errors and parameter struct for {BStockLiquidator}.\\n/// @dev The flash-loan callback `executeOperation` is intentionally NOT part of this interface \\u2014 it is\\n/// declared by {IFlashLoanReceiver} (owned by the Core flash-loan subsystem) and implemented directly\\n/// by {BStockLiquidator}, keeping this interface free of the concrete `VToken` dependency.\\ninterface IBStockLiquidator {\\n /// @notice Parameters for a single liquidation.\\n /// @dev The swap can be one or two hops. When `router2 == address(0)` it is a single hop\\n /// (bStock -> debt) and `swapCalldata2` / `intermediateToken` are ignored \\u2014 behavior is\\n /// identical to the single-hop version. When `router2` is set it is two hops\\n /// (bStock -> `intermediateToken` -> debt): hop 1 sells bStock via `router`, hop 2 converts\\n /// the intermediate to the debt asset via `router2`. `minOut` is always the FINAL debt-asset\\n /// floor across the whole chain. `deadline` is a unix-timestamp expiry: the call reverts once\\n /// `block.timestamp` passes it, so a stale tx cannot settle against an expired quote.\\n struct LiquidationParams {\\n address borrower; // account to liquidate\\n IVBep20 vDebt; // borrowed market to repay (e.g. vUSDT)\\n IVBep20 vBStock; // bStock collateral market to seize (e.g. vTSLAB)\\n uint256 repayAmount; // debt underlying to repay (its own decimals)\\n address router; // hop-1 RFQ router (Native firm-quote target, Liquid Mesh router, \\u2026) \\u2014 must be allowlisted\\n bytes swapCalldata; // hop-1 calldata (off-chain-signed RFQ order): bStock -> intermediate (or -> debt if single-hop)\\n uint256 minOut; // minimum FINAL debt-asset amount the swap chain must yield, else revert\\n address router2; // hop-2 router (AMM/aggregator): intermediate -> debt; address(0) = single-hop\\n bytes swapCalldata2; // hop-2 calldata; the swap recipient inside it MUST be this contract\\n address intermediateToken; // token hop 1 outputs and hop 2 consumes (e.g. USDT); required when router2 set\\n uint256 deadline; // unix timestamp after which the call reverts; guards a stale tx sitting in the mempool\\n }\\n\\n /// @notice Emitted when an operator is allowlisted or removed.\\n event OperatorSet(address indexed operator, bool allowed);\\n\\n /// @notice Emitted when a swap router is allowlisted or removed.\\n event RouterSet(address indexed router, bool allowed);\\n\\n /// @notice Emitted when a router's token-approval target (spender) is set or cleared.\\n event RouterSpenderSet(address indexed router, address indexed spender);\\n\\n /// @notice Emitted on a successful liquidation.\\n /// @param borrower The liquidated account.\\n /// @param vBStock The seized bStock collateral market.\\n /// @param vDebt The repaid debt market.\\n /// @param repayAmount Debt underlying repaid.\\n /// @param seizedBStock Raw bStock redeemed and sold.\\n /// @param debtOut Debt-asset proceeds of the swap.\\n /// @param flash True if funded by a flash loan, false if from inventory.\\n event Liquidated(\\n address indexed borrower,\\n address indexed vBStock,\\n address indexed vDebt,\\n uint256 repayAmount,\\n uint256 seizedBStock,\\n uint256 debtOut,\\n bool flash\\n );\\n\\n /// @notice Emitted when the owner withdraws a token.\\n event Swept(address indexed token, address indexed to, uint256 amount);\\n\\n /// @notice Emitted when the owner withdraws stuck native BNB.\\n event SweptNative(address indexed to, uint256 amount);\\n\\n /// @notice Emitted when a swap hop pulls less than the amount approved to the router, leaving a\\n /// residual of the input token in the contract (e.g. a partially-filled RFQ quote). The\\n /// residual is recoverable via `sweep`.\\n /// @param token The input token left over (bStock on hop 1, the intermediate on hop 2).\\n /// @param amount The residual amount not consumed by the swap.\\n event PartialSwapLeftover(address indexed token, uint256 amount);\\n\\n /// @notice Thrown when the caller is neither the owner nor an allowlisted operator.\\n error NotOperator();\\n\\n /// @notice Thrown when the supplied swap router is not allowlisted.\\n error RouterNotAllowed(address router);\\n\\n /// @notice Thrown when a router spender being set is not a deployed contract. The spender receives\\n /// a live token approval during the swap, so an EOA spender is always a misconfiguration.\\n error SpenderNotContract(address spender);\\n\\n /// @notice Thrown when `vBStock.redeem` returns a non-zero error code.\\n error RedeemFailed(uint256 errCode);\\n\\n /// @notice Thrown when the low-level call to the router reverts.\\n error SwapFailed();\\n\\n /// @notice Thrown when swap proceeds are below `minOut`.\\n error InsufficientOut(uint256 got, uint256 minOut);\\n\\n /// @notice Thrown when `minOut` is zero: a liquidation must set a non-zero debt-asset floor,\\n /// else it would silently accept any proceeds (including zero).\\n error ZeroMinOut();\\n\\n /// @notice Thrown when a two-hop `intermediateToken` is zero, or equals the debt or bStock token.\\n error InvalidIntermediate();\\n\\n /// @notice Thrown when `executeOperation` is called by something other than the Comptroller.\\n error OnlyComptroller();\\n\\n /// @notice Thrown when the flash-loan initiator is not this contract.\\n error BadInitiator(address initiator);\\n\\n /// @notice Thrown when the flashed asset does not match `params.vDebt`.\\n error WrongFlashAsset();\\n\\n /// @notice Thrown when `flashLiquidate` is called with a VAI debt. VAI is minted/burned by the\\n /// VAIController and has no vToken market to flash from \\u2014 use `liquidate` (INVENTORY mode)\\n /// with pre-funded VAI instead.\\n error FlashNotSupportedForVai();\\n\\n /// @notice Thrown when the call is submitted after `params.deadline`.\\n error DeadlineExpired(uint256 deadline, uint256 nowTs);\\n\\n /// @notice Thrown when a native BNB transfer (the `sweepNative` payout) fails.\\n error NativeTransferFailed();\\n\\n /// @notice Allow or disallow an address to trigger liquidations.\\n /// @param operator Address to allowlist or remove.\\n /// @param allowed True to allow, false to remove.\\n function setOperator(address operator, bool allowed) external;\\n\\n /// @notice Allow or disallow a router as the swap target (e.g. the Native router).\\n /// @dev Removing a router also clears its `routerSpender` entry (emitting {RouterSpenderSet} with\\n /// `address(0)`), so a stale spender cannot silently reactivate on a later re-allowlist.\\n /// @param router Address to allowlist or remove.\\n /// @param allowed True to allow, false to remove.\\n function setRouter(address router, bool allowed) external;\\n\\n /// @notice Set the token-approval target (spender) for a router whose settlement contract that pulls\\n /// the input token differs from the call target (e.g. Liquid Mesh). When unset, the approval\\n /// defaults to the router itself (Native behaviour). Setting `spender = address(0)` clears it.\\n /// @dev Reverts with {RouterNotAllowed} unless `router` is currently allowlisted, and with\\n /// {SpenderNotContract} when a non-zero `spender` has no code. The spender is an approval\\n /// target only \\u2014 it is never called; the low-level call always targets the allowlisted router.\\n /// @param router The allowlisted swap target (call target).\\n /// @param spender The contract that pulls the input token via `transferFrom` during settlement.\\n function setRouterSpender(address router, address spender) external;\\n\\n /// @notice Withdraw any token (profit, leftover inventory, stuck dust) to `to`.\\n /// @param token Token to withdraw.\\n /// @param to Recipient.\\n /// @param amount Amount to withdraw.\\n function sweep(address token, address to, uint256 amount) external;\\n\\n /// @notice Withdraw stuck native BNB (a stray transfer, or a gate refund) to `to`.\\n /// @param to Recipient.\\n /// @param amount Amount of native BNB to withdraw.\\n function sweepNative(address to, uint256 amount) external;\\n\\n /// @notice Liquidate using the contract's own debt-asset inventory.\\n /// @dev The contract must already hold >= `repayAmount` of `vDebt.underlying()`.\\n /// Profit (proceeds - repay) stays in the contract; withdraw it with `sweep`.\\n /// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final\\n /// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt).\\n /// @return debtOut Debt-asset proceeds realized by the swap chain.\\n function liquidate(LiquidationParams calldata params) external returns (uint256 debtOut);\\n\\n /// @notice Liquidate by flash-borrowing the repay amount from Venus, repaid (+ premium) in the same tx.\\n /// @dev Requires this contract to be `authorizedFlashLoan` in the Comptroller and `vDebt` flash-enabled.\\n /// Profit (proceeds - repay - premium) stays in the contract; withdraw it with `sweep`.\\n /// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final\\n /// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt).\\n function flashLiquidate(LiquidationParams calldata params) external;\\n}\\n\",\"keccak256\":\"0x4a9ecb9f8681bf77b3592252a4db0f47e42b0e0bf3b08c179bf6d102241f572a\",\"license\":\"BSD-3-Clause\"},\"contracts/Comptroller/ComptrollerInterface.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { ResilientOracleInterface } from \\\"@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol\\\";\\nimport { IDeviationBoundedOracle } from \\\"@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol\\\";\\n\\nimport { VToken } from \\\"../Tokens/VTokens/VToken.sol\\\";\\nimport { VAIControllerInterface } from \\\"../Tokens/VAI/VAIControllerInterface.sol\\\";\\nimport { WeightFunction } from \\\"./Diamond/interfaces/IFacetBase.sol\\\";\\n\\nenum Action {\\n MINT,\\n REDEEM,\\n BORROW,\\n REPAY,\\n SEIZE,\\n LIQUIDATE,\\n TRANSFER,\\n ENTER_MARKET,\\n EXIT_MARKET\\n}\\n\\ninterface ComptrollerInterface {\\n /// @notice Indicator that this is a Comptroller contract (for inspection)\\n function isComptroller() external pure returns (bool);\\n\\n /*** Assets You Are In ***/\\n\\n function enterMarkets(address[] calldata vTokens) external returns (uint[] memory);\\n\\n function exitMarket(address vToken) external returns (uint);\\n\\n /*** Policy Hooks ***/\\n\\n function mintAllowed(address vToken, address minter, uint mintAmount) external returns (uint);\\n\\n function mintVerify(address vToken, address minter, uint mintAmount, uint mintTokens) external;\\n\\n function redeemAllowed(address vToken, address redeemer, uint redeemTokens) external returns (uint);\\n\\n function redeemVerify(address vToken, address redeemer, uint redeemAmount, uint redeemTokens) external;\\n\\n function borrowAllowed(address vToken, address borrower, uint borrowAmount) external returns (uint);\\n\\n function borrowVerify(address vToken, address borrower, uint borrowAmount) external;\\n\\n function executeFlashLoan(\\n address payable onBehalf,\\n address payable receiver,\\n VToken[] calldata vTokens,\\n uint256[] calldata underlyingAmounts,\\n bytes calldata param\\n ) external;\\n\\n function repayBorrowAllowed(\\n address vToken,\\n address payer,\\n address borrower,\\n uint repayAmount\\n ) external returns (uint);\\n\\n function repayBorrowVerify(\\n address vToken,\\n address payer,\\n address borrower,\\n uint repayAmount,\\n uint borrowerIndex\\n ) external;\\n\\n function liquidateBorrowAllowed(\\n address vTokenBorrowed,\\n address vTokenCollateral,\\n address liquidator,\\n address borrower,\\n uint repayAmount\\n ) external returns (uint);\\n\\n function liquidateBorrowVerify(\\n address vTokenBorrowed,\\n address vTokenCollateral,\\n address liquidator,\\n address borrower,\\n uint repayAmount,\\n uint seizeTokens\\n ) external;\\n\\n function seizeAllowed(\\n address vTokenCollateral,\\n address vTokenBorrowed,\\n address liquidator,\\n address borrower,\\n uint seizeTokens\\n ) external returns (uint);\\n\\n function seizeVerify(\\n address vTokenCollateral,\\n address vTokenBorrowed,\\n address liquidator,\\n address borrower,\\n uint seizeTokens\\n ) external;\\n\\n function transferAllowed(address vToken, address src, address dst, uint transferTokens) external returns (uint);\\n\\n function transferVerify(address vToken, address src, address dst, uint transferTokens) external;\\n\\n /*** Liquidity/Liquidation Calculations ***/\\n\\n function liquidateCalculateSeizeTokens(\\n address vTokenBorrowed,\\n address vTokenCollateral,\\n uint repayAmount\\n ) external view returns (uint, uint);\\n\\n function liquidateCalculateSeizeTokens(\\n address borrower,\\n address vTokenBorrowed,\\n address vTokenCollateral,\\n uint repayAmount\\n ) external view returns (uint, uint);\\n\\n function setMintedVAIOf(address owner, uint amount) external returns (uint);\\n\\n function liquidateVAICalculateSeizeTokens(\\n address vTokenCollateral,\\n uint repayAmount\\n ) external view returns (uint, uint);\\n\\n function getXVSAddress() external view returns (address);\\n\\n function markets(address) external view returns (bool, uint, bool, uint, uint, uint96, bool);\\n\\n function oracle() external view returns (ResilientOracleInterface);\\n\\n function deviationBoundedOracle() external view returns (IDeviationBoundedOracle);\\n\\n function getAccountLiquidity(address) external view returns (uint, uint, uint);\\n\\n function getAssetsIn(address) external view returns (VToken[] memory);\\n\\n function claimVenus(address) external;\\n\\n function venusAccrued(address) external view returns (uint);\\n\\n function venusSupplySpeeds(address) external view returns (uint);\\n\\n function venusBorrowSpeeds(address) external view returns (uint);\\n\\n function getAllMarkets() external view returns (VToken[] memory);\\n\\n function venusSupplierIndex(address, address) external view returns (uint);\\n\\n function venusInitialIndex() external view returns (uint224);\\n\\n function venusBorrowerIndex(address, address) external view returns (uint);\\n\\n function venusBorrowState(address) external view returns (uint224, uint32);\\n\\n function venusSupplyState(address) external view returns (uint224, uint32);\\n\\n function approvedDelegates(address borrower, address delegate) external view returns (bool);\\n\\n function vaiController() external view returns (VAIControllerInterface);\\n\\n function protocolPaused() external view returns (bool);\\n\\n function actionPaused(address market, Action action) external view returns (bool);\\n\\n function mintedVAIs(address user) external view returns (uint);\\n\\n function vaiMintRate() external view returns (uint);\\n\\n function authorizedFlashLoan(address account) external view returns (bool);\\n\\n function userPoolId(address account) external view returns (uint96);\\n\\n function getLiquidationIncentive(address vToken) external view returns (uint256);\\n\\n function getEffectiveLiquidationIncentive(address account, address vToken) external view returns (uint256);\\n\\n function getEffectiveLtvFactor(\\n address account,\\n address vToken,\\n WeightFunction weightingStrategy\\n ) external view returns (uint256);\\n\\n function lastPoolId() external view returns (uint96);\\n\\n function corePoolId() external pure returns (uint96);\\n\\n function pools(\\n uint96 poolId\\n ) external view returns (string memory label, bool isActive, bool allowCorePoolFallback);\\n\\n function getPoolVTokens(uint96 poolId) external view returns (address[] memory);\\n\\n function poolMarkets(\\n uint96 poolId,\\n address vToken\\n )\\n external\\n view\\n returns (\\n bool isListed,\\n uint256 collateralFactorMantissa,\\n bool isVenus,\\n uint256 liquidationThresholdMantissa,\\n uint256 liquidationIncentiveMantissa,\\n uint96 marketPoolId,\\n bool isBorrowAllowed\\n );\\n\\n function isFlashLoanPaused() external view returns (bool);\\n}\\n\\ninterface IVAIVault {\\n function updatePendingRewards() external;\\n}\\n\\ninterface IComptroller {\\n /*** Treasury Data ***/\\n function treasuryAddress() external view returns (address);\\n\\n function treasuryPercent() external view returns (uint);\\n}\\n\",\"keccak256\":\"0x79fb234214cebf97f6e85c2bbe013977a4cd38ff6146a7c405a2fa9521e0cd4a\",\"license\":\"BSD-3-Clause\"},\"contracts/Comptroller/Diamond/interfaces/IFacetBase.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { Action } from \\\"../../../Comptroller/ComptrollerInterface.sol\\\";\\nimport { PoolMarketId } from \\\"../../../Comptroller/Types/PoolMarketId.sol\\\";\\n\\nenum WeightFunction {\\n /// @notice Use the collateral factor of the asset for weighting\\n USE_COLLATERAL_FACTOR,\\n /// @notice Use the liquidation threshold of the asset for weighting\\n USE_LIQUIDATION_THRESHOLD\\n}\\n\\ninterface IFacetBase {\\n /**\\n * @notice The initial XVS rewards index for a market\\n */\\n function venusInitialIndex() external pure returns (uint224);\\n\\n /**\\n * @notice Checks if a certain action is paused on a market\\n * @param action Action id\\n * @param market vToken address\\n */\\n function actionPaused(address market, Action action) external view returns (bool);\\n\\n /**\\n * @notice Returns the XVS address\\n * @return The address of XVS token\\n */\\n function getXVSAddress() external view returns (address);\\n\\n function getPoolMarketIndex(uint96 poolId, address vToken) external pure returns (PoolMarketId);\\n\\n function corePoolId() external pure returns (uint96);\\n}\\n\",\"keccak256\":\"0x454a2e213a5f54fbe7991193b78ae23406234f465c9806f75ee7bf2fe6d1531c\",\"license\":\"BSD-3-Clause\"},\"contracts/Comptroller/Types/PoolMarketId.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\n/// @notice Strongly-typed identifier for pool markets mapping keys\\n/// @dev Underlying storage is bytes32: first 12 bytes (96 bits) = poolId, last 20 bytes = vToken address\\ntype PoolMarketId is bytes32;\\n\\n \",\"keccak256\":\"0xf68bde30ddd6f8bf08194b493991c2a2ebd3814972f93a804beb9b366004cbe3\",\"license\":\"BSD-3-Clause\"},\"contracts/FlashLoan/interfaces/IFlashLoanReceiver.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { VToken } from \\\"../../Tokens/VTokens/VToken.sol\\\";\\n\\n/// @title IFlashLoanReceiver\\n/// @notice Interface for flashLoan receiver contract, which executes custom logic with flash-borrowed assets.\\n/// @dev This interface defines the method that must be implemented by any contract wishing to interact with the flashLoan system.\\n/// Contracts must ensure they have the means to repay at least the premium (fee), with any unpaid balance becoming debt.\\ninterface IFlashLoanReceiver {\\n /**\\n * @notice Executes an operation after receiving the flash-borrowed assets.\\n * @dev Implementation of this function must ensure at least the premium (fee) is repaid within the same transaction.\\n * Any unpaid balance (principal + premium - repaid amount) will be added to the onBehalf address's borrow balance.\\n * @param vTokens The vToken contracts corresponding to the flash-borrowed underlying assets.\\n * @param amounts The amounts of each underlying asset that were flash-borrowed.\\n * @param premiums The premiums (fees) associated with each flash-borrowed asset.\\n * @param initiator The address that initiated the flash loan.\\n * @param onBehalf The address of the user whose debt position will be used for any unpaid flash loan balance.\\n * @param param Additional parameters encoded as bytes. These can be used to pass custom data to the receiver contract.\\n * @return success True if the operation succeeds (regardless of repayment amount), false if the operation fails.\\n * @return repayAmounts Array of uint256 representing the amounts to be repaid for each asset. The receiver contract\\n * must approve these amounts to the respective vToken contracts before this function returns.\\n */\\n function executeOperation(\\n VToken[] calldata vTokens,\\n uint256[] calldata amounts,\\n uint256[] calldata premiums,\\n address initiator,\\n address onBehalf,\\n bytes calldata param\\n ) external returns (bool success, uint256[] memory repayAmounts);\\n}\\n\",\"keccak256\":\"0xe8eb540e98551178726f9d2d24e7ae1ab12ab96349c22f7232bacd377a0b33dd\",\"license\":\"BSD-3-Clause\"},\"contracts/InterestRateModels/InterestRateModelV8.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\n/**\\n * @title Venus's InterestRateModelV8 Interface\\n * @author Venus\\n */\\nabstract contract InterestRateModelV8 {\\n /// @notice Indicator that this is an InterestRateModel contract (for inspection)\\n bool public constant isInterestRateModel = true;\\n\\n /**\\n * @notice Calculates the current borrow interest rate per block\\n * @param cash The total amount of cash the market has\\n * @param borrows The total amount of borrows the market has outstanding\\n * @param reserves The total amnount of reserves the market has\\n * @return The borrow rate per block (as a percentage, and scaled by 1e18)\\n */\\n function getBorrowRate(uint256 cash, uint256 borrows, uint256 reserves) external view virtual returns (uint256);\\n\\n /**\\n * @notice Calculates the current supply interest rate per block\\n * @param cash The total amount of cash the market has\\n * @param borrows The total amount of borrows the market has outstanding\\n * @param reserves The total amnount of reserves the market has\\n * @param reserveFactorMantissa The current reserve factor the market has\\n * @return The supply rate per block (as a percentage, and scaled by 1e18)\\n */\\n function getSupplyRate(\\n uint256 cash,\\n uint256 borrows,\\n uint256 reserves,\\n uint256 reserveFactorMantissa\\n ) external view virtual returns (uint256);\\n}\\n\",\"keccak256\":\"0x4d5595e761d50a1431c34b39e72dde6c09b0ebccdbe8c5c4e12c8a2ac7b796e1\",\"license\":\"BSD-3-Clause\"},\"contracts/InterfacesV8.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\nimport { IERC20Upgradeable } from \\\"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\\\";\\nimport { ResilientOracleInterface } from \\\"@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol\\\";\\n\\ninterface IVToken is IERC20Upgradeable {\\n function accrueInterest() external returns (uint256);\\n\\n function redeem(uint256 redeemTokens) external returns (uint256);\\n\\n function redeemUnderlying(uint256 redeemAmount) external returns (uint256);\\n\\n function borrowBalanceCurrent(address borrower) external returns (uint256);\\n\\n function balanceOfUnderlying(address owner) external returns (uint256);\\n\\n function comptroller() external view returns (IComptroller);\\n\\n function borrowBalanceStored(address account) external view returns (uint256);\\n}\\n\\ninterface IVBep20 is IVToken {\\n function borrowBehalf(address borrower, uint256 borrowAmount) external returns (uint256);\\n\\n function repayBorrowBehalf(address borrower, uint256 repayAmount) external returns (uint256);\\n\\n function liquidateBorrow(\\n address borrower,\\n uint256 repayAmount,\\n IVToken vTokenCollateral\\n ) external returns (uint256);\\n\\n function underlying() external view returns (address);\\n}\\n\\ninterface IVBNB is IVToken {\\n function repayBorrowBehalf(address borrower) external payable;\\n\\n function liquidateBorrow(address borrower, IVToken vTokenCollateral) external payable;\\n}\\n\\ninterface IVAIController {\\n function accrueVAIInterest() external;\\n\\n function liquidateVAI(\\n address borrower,\\n uint256 repayAmount,\\n IVToken vTokenCollateral\\n ) external returns (uint256, uint256);\\n\\n function repayVAIBehalf(address borrower, uint256 amount) external returns (uint256, uint256);\\n\\n function getVAIAddress() external view returns (address);\\n\\n function getVAIRepayAmount(address borrower) external view returns (uint256);\\n}\\n\\ninterface IComptroller {\\n enum Action {\\n MINT,\\n REDEEM,\\n BORROW,\\n REPAY,\\n SEIZE,\\n LIQUIDATE,\\n TRANSFER,\\n ENTER_MARKET,\\n EXIT_MARKET\\n }\\n\\n function _setActionsPaused(address[] calldata markets_, Action[] calldata actions_, bool paused_) external;\\n\\n function executeFlashLoan(\\n address payable onBehalf,\\n address payable receiver,\\n IVToken[] calldata vTokens,\\n uint256[] calldata underlyingAmounts,\\n bytes calldata param\\n ) external;\\n\\n function vaiController() external view returns (IVAIController);\\n\\n function liquidatorContract() external view returns (address);\\n\\n function getAccountLiquidity(address account) external view returns (uint256, uint256, uint256);\\n\\n function oracle() external view returns (ResilientOracleInterface);\\n\\n function actionPaused(address market, Action action) external view returns (bool);\\n\\n function markets(address) external view returns (bool, uint256, bool);\\n\\n function isForcedLiquidationEnabled(address) external view returns (bool);\\n\\n function getEffectiveLiquidationIncentive(address account, address vToken) external view returns (uint256);\\n}\\n\\ninterface ILiquidator {\\n function restrictLiquidation(address borrower) external;\\n\\n function unrestrictLiquidation(address borrower) external;\\n\\n function addToAllowlist(address borrower, address liquidator) external;\\n\\n function removeFromAllowlist(address borrower, address liquidator) external;\\n\\n function liquidateBorrow(\\n address vToken,\\n address borrower,\\n uint256 repayAmount,\\n IVToken vTokenCollateral\\n ) external payable;\\n\\n function setTreasuryPercent(uint256 newTreasuryPercentMantissa) external;\\n\\n function treasuryPercentMantissa() external view returns (uint256);\\n}\\n\",\"keccak256\":\"0x02c315f8a158e79d96a4befd4b90473eebb8af75580a27c1eb3b001b421389a0\",\"license\":\"BSD-3-Clause\"},\"contracts/Tokens/VAI/VAIControllerInterface.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { VTokenInterface } from \\\"../VTokens/VTokenInterfaces.sol\\\";\\n\\ninterface VAIControllerInterface {\\n function mintVAI(uint256 mintVAIAmount) external returns (uint256);\\n\\n function repayVAI(uint256 amount) external returns (uint256, uint256);\\n\\n function repayVAIBehalf(address borrower, uint256 amount) external returns (uint256, uint256);\\n\\n function liquidateVAI(\\n address borrower,\\n uint256 repayAmount,\\n VTokenInterface vTokenCollateral\\n ) external returns (uint256, uint256);\\n\\n function getMintableVAI(address minter) external view returns (uint256, uint256);\\n\\n function getVAIAddress() external view returns (address);\\n\\n function getVAIRepayAmount(address account) external view returns (uint256);\\n}\\n\",\"keccak256\":\"0x69d2f9e13b7fbf0a29885048503642372d9ba3d37f2427d4b9cffb87eddd925b\",\"license\":\"BSD-3-Clause\"},\"contracts/Tokens/VTokens/VToken.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { IAccessControlManagerV8 } from \\\"@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol\\\";\\nimport { IProtocolShareReserve } from \\\"../../external/IProtocolShareReserve.sol\\\";\\nimport { ComptrollerInterface, IComptroller } from \\\"../../Comptroller/ComptrollerInterface.sol\\\";\\nimport { TokenErrorReporter } from \\\"../../Utils/ErrorReporter.sol\\\";\\nimport { Exponential } from \\\"../../Utils/Exponential.sol\\\";\\nimport { InterestRateModelV8 } from \\\"../../InterestRateModels/InterestRateModelV8.sol\\\";\\nimport { VTokenInterface } from \\\"./VTokenInterfaces.sol\\\";\\nimport { MANTISSA_ONE } from \\\"@venusprotocol/solidity-utilities/contracts/constants.sol\\\";\\n\\n/**\\n * @title Venus's vToken Contract\\n * @notice Abstract base for vTokens\\n * @author Venus\\n */\\nabstract contract VToken is VTokenInterface, Exponential, TokenErrorReporter {\\n struct MintLocalVars {\\n MathError mathErr;\\n uint exchangeRateMantissa;\\n uint mintTokens;\\n uint totalSupplyNew;\\n uint accountTokensNew;\\n uint actualMintAmount;\\n }\\n\\n struct RedeemLocalVars {\\n MathError mathErr;\\n uint exchangeRateMantissa;\\n uint redeemTokens;\\n uint redeemAmount;\\n uint totalSupplyNew;\\n uint accountTokensNew;\\n }\\n\\n struct BorrowLocalVars {\\n MathError mathErr;\\n uint accountBorrows;\\n uint accountBorrowsNew;\\n uint totalBorrowsNew;\\n }\\n\\n struct RepayBorrowLocalVars {\\n Error err;\\n MathError mathErr;\\n uint repayAmount;\\n uint borrowerIndex;\\n uint accountBorrows;\\n uint accountBorrowsNew;\\n uint totalBorrowsNew;\\n uint actualRepayAmount;\\n }\\n\\n /*** Reentrancy Guard ***/\\n\\n /**\\n * @dev Prevents a contract from calling itself, directly or indirectly.\\n */\\n modifier nonReentrant() {\\n require(_notEntered, \\\"re-entered\\\");\\n _notEntered = false;\\n _;\\n _notEntered = true; // get a gas-refund post-Istanbul\\n }\\n\\n /**\\n * @notice Transfer `amount` tokens from `msg.sender` to `dst`\\n * @param dst The address of the destination account\\n * @param amount The number of tokens to transfer\\n * @return Whether or not the transfer succeeded\\n */\\n // @custom:event Emits Transfer event\\n function transfer(address dst, uint256 amount) external override nonReentrant returns (bool) {\\n return transferTokens(msg.sender, msg.sender, dst, amount) == uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Transfer `amount` tokens from `src` to `dst`\\n * @param src The address of the source account\\n * @param dst The address of the destination account\\n * @param amount The number of tokens to transfer\\n * @return Whether or not the transfer succeeded\\n */\\n // @custom:event Emits Transfer event\\n function transferFrom(address src, address dst, uint256 amount) external override nonReentrant returns (bool) {\\n return transferTokens(msg.sender, src, dst, amount) == uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Approve `spender` to transfer up to `amount` from `src`\\n * @dev This will overwrite the approval amount for `spender`\\n * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)\\n * @param spender The address of the account which may transfer tokens\\n * @param amount The number of tokens that are approved (type(uint256).max means infinite)\\n * @return Whether or not the approval succeeded\\n */\\n // @custom:event Emits Approval event on successful approve\\n function approve(address spender, uint256 amount) external override returns (bool) {\\n transferAllowances[msg.sender][spender] = amount;\\n emit Approval(msg.sender, spender, amount);\\n return true;\\n }\\n\\n /**\\n * @notice Get the underlying balance of the `owner`\\n * @dev This also accrues interest in a transaction\\n * @param owner The address of the account to query\\n * @return The amount of underlying owned by `owner`\\n */\\n function balanceOfUnderlying(address owner) external override returns (uint) {\\n Exp memory exchangeRate = Exp({ mantissa: exchangeRateCurrent() });\\n (MathError mErr, uint balance) = mulScalarTruncate(exchangeRate, accountTokens[owner]);\\n ensureNoMathError(mErr);\\n return balance;\\n }\\n\\n /**\\n * @notice Returns the current total borrows plus accrued interest\\n * @return The total borrows with interest\\n */\\n function totalBorrowsCurrent() external override nonReentrant returns (uint) {\\n require(accrueInterest() == uint(Error.NO_ERROR), \\\"accrue interest failed\\\");\\n return totalBorrows;\\n }\\n\\n /**\\n * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex\\n * @param account The address whose balance should be calculated after updating borrowIndex\\n * @return The calculated balance\\n */\\n function borrowBalanceCurrent(address account) external override nonReentrant returns (uint) {\\n require(accrueInterest() == uint(Error.NO_ERROR), \\\"accrue interest failed\\\");\\n return borrowBalanceStored(account);\\n }\\n\\n /**\\n * @notice Transfers collateral tokens (this market) to the liquidator.\\n * @dev Will fail unless called by another vToken during the process of liquidation.\\n * Its absolutely critical to use msg.sender as the borrowed vToken and not a parameter.\\n * @param liquidator The account receiving seized collateral\\n * @param borrower The account having collateral seized\\n * @param seizeTokens The number of vTokens to seize\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits Transfer event\\n function seize(\\n address liquidator,\\n address borrower,\\n uint seizeTokens\\n ) external override nonReentrant returns (uint) {\\n return seizeInternal(msg.sender, liquidator, borrower, seizeTokens);\\n }\\n\\n /**\\n * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.\\n * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.\\n * @param newPendingAdmin New pending admin.\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits NewPendingAdmin event with old and new admin addresses\\n function _setPendingAdmin(address payable newPendingAdmin) external override returns (uint) {\\n // Check caller = admin\\n ensureAdmin(msg.sender);\\n\\n // Save current value, if any, for inclusion in log\\n address oldPendingAdmin = pendingAdmin;\\n\\n // Store pendingAdmin with value newPendingAdmin\\n pendingAdmin = newPendingAdmin;\\n\\n // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin)\\n emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin\\n * @dev Admin function for pending admin to accept role and update admin\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits NewAdmin event on successful acceptance\\n // @custom:event Emits NewPendingAdmin event with null new pending admin\\n function _acceptAdmin() external override returns (uint) {\\n // Check caller is pendingAdmin\\n if (msg.sender != pendingAdmin) {\\n return fail(Error.UNAUTHORIZED, FailureInfo.ACCEPT_ADMIN_PENDING_ADMIN_CHECK);\\n }\\n\\n // Save current values for inclusion in log\\n address oldAdmin = admin;\\n address oldPendingAdmin = pendingAdmin;\\n\\n // Store admin with value pendingAdmin\\n admin = pendingAdmin;\\n\\n // Clear the pending value\\n pendingAdmin = payable(address(0));\\n\\n emit NewAdmin(oldAdmin, admin);\\n emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice accrues interest and sets a new reserve factor for the protocol using `_setReserveFactorFresh`\\n * @dev Governor function to accrue interest and set a new reserve factor\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits NewReserveFactor event\\n function _setReserveFactor(uint newReserveFactorMantissa_) external override nonReentrant returns (uint) {\\n ensureAllowed(\\\"_setReserveFactor(uint256)\\\");\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reserve factor change failed.\\n return fail(Error(error), FailureInfo.SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // _setReserveFactorFresh emits reserve-factor-specific logs on errors, so we don't need to.\\n return _setReserveFactorFresh(newReserveFactorMantissa_);\\n }\\n\\n /**\\n * @notice Sets the address of the access control manager of this contract\\n * @dev Admin function to set the access control address\\n * @param newAccessControlManagerAddress New address for the access control\\n * @return uint 0=success, otherwise will revert\\n */\\n function setAccessControlManager(address newAccessControlManagerAddress) external returns (uint) {\\n // Check caller is admin\\n ensureAdmin(msg.sender);\\n\\n ensureNonZeroAddress(newAccessControlManagerAddress);\\n\\n emit NewAccessControlManager(accessControlManager, newAccessControlManagerAddress);\\n accessControlManager = newAccessControlManagerAddress;\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Accrues interest and reduces reserves by transferring to protocol share reserve\\n * @param reduceAmount_ Amount of reduction to reserves\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits ReservesReduced event\\n function _reduceReserves(uint reduceAmount_) external virtual override nonReentrant returns (uint) {\\n ensureAllowed(\\\"_reduceReserves(uint256)\\\");\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed.\\n return fail(Error(error), FailureInfo.REDUCE_RESERVES_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // If reserves were reduced in accrueInterest\\n if (reduceReservesBlockNumber == block.number) return (uint(Error.NO_ERROR));\\n // _reduceReservesFresh emits reserve-reduction-specific logs on errors, so we don't need to.\\n return _reduceReservesFresh(reduceAmount_);\\n }\\n\\n /**\\n * @notice Get the current allowance from `owner` for `spender`\\n * @param owner The address of the account which owns the tokens to be spent\\n * @param spender The address of the account which may transfer tokens\\n * @return The number of tokens allowed to be spent (type(uint256).max means infinite)\\n */\\n function allowance(address owner, address spender) external view override returns (uint256) {\\n return transferAllowances[owner][spender];\\n }\\n\\n /**\\n * @notice Get the token balance of the `owner`\\n * @param owner The address of the account to query\\n * @return The number of tokens owned by `owner`\\n */\\n function balanceOf(address owner) external view override returns (uint256) {\\n return accountTokens[owner];\\n }\\n\\n /**\\n * @notice Get a snapshot of the account's balances, and the cached exchange rate\\n * @dev This is used by comptroller to more efficiently perform liquidity checks.\\n * @param account Address of the account to snapshot\\n * @return (possible error, token balance, borrow balance, exchange rate mantissa)\\n */\\n function getAccountSnapshot(address account) external view override returns (uint, uint, uint, uint) {\\n uint borrowBalance;\\n uint exchangeRateMantissa;\\n\\n MathError mErr;\\n\\n (mErr, borrowBalance) = borrowBalanceStoredInternal(account);\\n if (mErr != MathError.NO_ERROR) {\\n return (uint(Error.MATH_ERROR), 0, 0, 0);\\n }\\n\\n (mErr, exchangeRateMantissa) = exchangeRateStoredInternal();\\n if (mErr != MathError.NO_ERROR) {\\n return (uint(Error.MATH_ERROR), 0, 0, 0);\\n }\\n\\n return (uint(Error.NO_ERROR), accountTokens[account], borrowBalance, exchangeRateMantissa);\\n }\\n\\n /**\\n * @notice Returns the current per-block supply interest rate for this vToken\\n * @dev The calculation includes `flashLoanAmount` in the cash balance to account for active flash loans.\\n * @return The supply interest rate per block, scaled by 1e18\\n */\\n function supplyRatePerBlock() external view override returns (uint) {\\n return\\n interestRateModel.getSupplyRate(\\n _getCashPriorWithFlashLoan(),\\n totalBorrows,\\n totalReserves,\\n reserveFactorMantissa\\n );\\n }\\n\\n /**\\n * @notice Returns the current per-block borrow interest rate for this vToken\\n * @dev The calculation includes `flashLoanAmount` in the cash balance to account for active flash loans.\\n * @return The borrow interest rate per block, scaled by 1e18\\n */\\n function borrowRatePerBlock() external view override returns (uint) {\\n return interestRateModel.getBorrowRate(_getCashPriorWithFlashLoan(), totalBorrows, totalReserves);\\n }\\n\\n /**\\n * @notice Get cash balance of this vToken in the underlying asset\\n * @return The quantity of underlying asset owned by this contract\\n */\\n function getCash() external view override returns (uint) {\\n return getCashPrior();\\n }\\n\\n /**\\n * @notice Governance function to set new threshold of block difference after which funds will be sent to the protocol share reserve\\n * @param newReduceReservesBlockDelta_ block difference value\\n */\\n function setReduceReservesBlockDelta(uint256 newReduceReservesBlockDelta_) external {\\n require(newReduceReservesBlockDelta_ > 0, \\\"Invalid Input\\\");\\n ensureAllowed(\\\"setReduceReservesBlockDelta(uint256)\\\");\\n emit NewReduceReservesBlockDelta(reduceReservesBlockDelta, newReduceReservesBlockDelta_);\\n reduceReservesBlockDelta = newReduceReservesBlockDelta_;\\n }\\n\\n /**\\n * @notice Sets protocol share reserve contract address\\n * @param protcolShareReserve_ The address of protocol share reserve contract\\n */\\n function setProtocolShareReserve(address payable protcolShareReserve_) external {\\n // Check caller is admin\\n ensureAdmin(msg.sender);\\n ensureNonZeroAddress(protcolShareReserve_);\\n emit NewProtocolShareReserve(protocolShareReserve, protcolShareReserve_);\\n protocolShareReserve = protcolShareReserve_;\\n }\\n\\n /**\\n * @notice Transfers the underlying asset to the specified address for flash loan purposes.\\n * @dev Can only be called by the Comptroller contract. This function performs the actual transfer of the underlying\\n * asset by calling the `doTransferOut` internal function.\\n * - The caller must be the Comptroller contract.\\n * - Sets the flashLoanAmount to track the borrowed amount during the flash loan process.\\n * @param to The address to which the underlying asset is to be transferred.\\n * @param amount The amount of the underlying asset to transfer.\\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\\n * @custom:error FlashLoanAlreadyActive is thrown if there is already an active flash loan.\\n * @custom:error InsufficientCash is thrown when the vToken does not have enough cash to lend\\n * @custom:event Emits TransferOutUnderlyingFlashLoan event on successful transfer of amount to receiver\\n */\\n function transferOutUnderlyingFlashLoan(address payable to, uint256 amount) external nonReentrant {\\n if (msg.sender != address(comptroller)) {\\n revert InvalidComptroller();\\n }\\n\\n if (flashLoanAmount > 0) {\\n revert FlashLoanAlreadyActive();\\n }\\n\\n if (getCashPrior() < amount) {\\n revert InsufficientCash();\\n }\\n flashLoanAmount = amount;\\n doTransferOut(to, amount);\\n emit TransferOutUnderlyingFlashLoan(underlying, to, amount);\\n }\\n\\n /**\\n * @notice Transfers the underlying asset from the specified address during flash loan repayment.\\n * @dev Can only be called by the Comptroller contract. This function performs the actual transfer of the underlying\\n * asset by calling the `doTransferIn` internal function and handles protocol fee distribution.\\n * - The caller must be the Comptroller contract.\\n * - Transfers the protocol fee to the protocol share reserve.\\n * - Resets the flashLoanAmount to 0 to complete the flash loan cycle.\\n * @param from The address from which the underlying asset is to be transferred.\\n * @param repaymentAmount The amount of the underlying asset being repaid by the receiver.\\n * @param totalFee The total fee amount for the flash loan.\\n * @param protocolFee The protocol fee amount to be transferred to the protocol share reserve.\\n * @return actualAmountTransferred The actual amount transferred in from the receiver.\\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\\n * @custom:error InsufficientRepayment is thrown when the repayment amount is insufficient to cover the total fee\\n * @custom:event Emits TransferInUnderlyingFlashLoan event on successful transfer of amount from the receiver to the vToken\\n */\\n function transferInUnderlyingFlashLoan(\\n address payable from,\\n uint256 repaymentAmount,\\n uint256 totalFee,\\n uint256 protocolFee\\n ) external nonReentrant returns (uint256) {\\n if (msg.sender != address(comptroller)) {\\n revert InvalidComptroller();\\n }\\n\\n uint256 actualAmountTransferred = doTransferIn(from, repaymentAmount);\\n\\n if (actualAmountTransferred < totalFee) {\\n revert InsufficientRepayment(actualAmountTransferred, totalFee);\\n }\\n\\n // Transfer protocol fee to protocol share reserve\\n doTransferOut(protocolShareReserve, protocolFee);\\n\\n IProtocolShareReserve(protocolShareReserve).updateAssetsState(\\n address(comptroller),\\n underlying,\\n IProtocolShareReserve.IncomeType.FLASHLOAN\\n );\\n\\n // Reset flashLoanAmount to complete the flash loan cycle\\n flashLoanAmount = 0;\\n\\n emit TransferInUnderlyingFlashLoan(underlying, from, actualAmountTransferred, totalFee, protocolFee);\\n return actualAmountTransferred;\\n }\\n\\n /**\\n * @notice Sets flash loan status for the market\\n * @param enabled True to enable flash loans, false to disable\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n * @custom:access Only Governance\\n * @custom:event Emits FlashLoanStatusChanged event on success\\n */\\n function setFlashLoanEnabled(bool enabled) external returns (uint256) {\\n ensureAllowed(\\\"setFlashLoanEnabled(bool)\\\");\\n\\n if (isFlashLoanEnabled != enabled) {\\n emit FlashLoanStatusChanged(isFlashLoanEnabled, enabled);\\n isFlashLoanEnabled = enabled;\\n }\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Update flashLoan fee mantissa\\n * @param flashLoanFeeMantissa_ FlashLoan fee, scaled by 1e18\\n * @param flashLoanProtocolShare_ FlashLoan protocol fee share, transferred to protocol share reserve\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n * @custom:access Only Governance\\n * @custom:error FlashLoanFeeTooHigh is thrown when flash loan fee exceeds maximum allowed\\n * @custom:error FlashLoanProtocolShareTooHigh is thrown when flash loan fee protocol share exceeds maximum allowed\\n * @custom:event Emits FlashLoanFeeUpdated event on success\\n */\\n function setFlashLoanFeeMantissa(\\n uint256 flashLoanFeeMantissa_,\\n uint256 flashLoanProtocolShare_\\n ) external returns (uint256) {\\n ensureAllowed(\\\"setFlashLoanFeeMantissa(uint256,uint256)\\\");\\n\\n if (flashLoanFeeMantissa_ > MANTISSA_ONE) {\\n revert FlashLoanFeeTooHigh(flashLoanFeeMantissa_, MANTISSA_ONE);\\n }\\n\\n if (flashLoanProtocolShare_ > MANTISSA_ONE) {\\n revert FlashLoanProtocolShareTooHigh(flashLoanProtocolShare_, MANTISSA_ONE);\\n }\\n\\n // Only proceed if values are changing\\n if (\\n flashLoanFeeMantissa != flashLoanFeeMantissa_ || flashLoanProtocolShareMantissa != flashLoanProtocolShare_\\n ) {\\n emit FlashLoanFeeUpdated(\\n flashLoanFeeMantissa,\\n flashLoanFeeMantissa_,\\n flashLoanProtocolShareMantissa,\\n flashLoanProtocolShare_\\n );\\n\\n flashLoanFeeMantissa = flashLoanFeeMantissa_;\\n flashLoanProtocolShareMantissa = flashLoanProtocolShare_;\\n }\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Initialize the money market\\n * @param comptroller_ The address of the Comptroller\\n * @param interestRateModel_ The address of the interest rate model\\n * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18\\n * @param name_ EIP-20 name of this token\\n * @param symbol_ EIP-20 symbol of this token\\n * @param decimals_ EIP-20 decimal precision of this token\\n */\\n function initialize(\\n ComptrollerInterface comptroller_,\\n InterestRateModelV8 interestRateModel_,\\n uint initialExchangeRateMantissa_,\\n string memory name_,\\n string memory symbol_,\\n uint8 decimals_\\n ) public {\\n ensureAdmin(msg.sender);\\n require(accrualBlockNumber == 0 && borrowIndex == 0, \\\"market may only be initialized once\\\");\\n\\n // Set initial exchange rate\\n initialExchangeRateMantissa = initialExchangeRateMantissa_;\\n require(initialExchangeRateMantissa > 0, \\\"initial exchange rate must be greater than zero.\\\");\\n\\n // Set the comptroller\\n uint err = _setComptroller(comptroller_);\\n require(err == uint(Error.NO_ERROR), \\\"setting comptroller failed\\\");\\n\\n // Initialize block number and borrow index (block number mocks depend on comptroller being set)\\n accrualBlockNumber = block.number;\\n borrowIndex = mantissaOne;\\n\\n // Set the interest rate model (depends on block number / borrow index)\\n err = _setInterestRateModelFresh(interestRateModel_);\\n require(err == uint(Error.NO_ERROR), \\\"setting interest rate model failed\\\");\\n\\n name = name_;\\n symbol = symbol_;\\n decimals = decimals_;\\n\\n // The counter starts true to prevent changing it from zero to non-zero (i.e. smaller cost/refund)\\n _notEntered = true;\\n }\\n\\n /**\\n * @notice Accrue interest then return the up-to-date exchange rate\\n * @return Calculated exchange rate scaled by 1e18\\n */\\n function exchangeRateCurrent() public override nonReentrant returns (uint) {\\n require(accrueInterest() == uint(Error.NO_ERROR), \\\"accrue interest failed\\\");\\n return exchangeRateStored();\\n }\\n\\n /**\\n * @notice Applies accrued interest to total borrows and reserves\\n * @dev This calculates interest accrued from the last checkpointed block\\n * up to the current block and writes new checkpoint to storage and\\n * reduce spread reserves to protocol share reserve\\n * if currentBlock - reduceReservesBlockNumber >= blockDelta\\n */\\n // @custom:event Emits AccrueInterest event\\n function accrueInterest() public virtual override returns (uint) {\\n /* Remember the initial block number */\\n uint currentBlockNumber = block.number;\\n uint accrualBlockNumberPrior = accrualBlockNumber;\\n\\n /* Short-circuit accumulating 0 interest */\\n if (accrualBlockNumberPrior == currentBlockNumber) {\\n return uint(Error.NO_ERROR);\\n }\\n\\n /* Read the previous values out of storage */\\n uint cashPrior = _getCashPriorWithFlashLoan();\\n uint borrowsPrior = totalBorrows;\\n uint reservesPrior = totalReserves;\\n uint borrowIndexPrior = borrowIndex;\\n\\n /* Calculate the current borrow interest rate */\\n uint borrowRateMantissa = interestRateModel.getBorrowRate(cashPrior, borrowsPrior, reservesPrior);\\n require(borrowRateMantissa <= borrowRateMaxMantissa, \\\"borrow rate is absurdly high\\\");\\n\\n /* Calculate the number of blocks elapsed since the last accrual */\\n (MathError mathErr, uint blockDelta) = subUInt(currentBlockNumber, accrualBlockNumberPrior);\\n ensureNoMathError(mathErr);\\n\\n /*\\n * Calculate the interest accumulated into borrows and reserves and the new index:\\n * simpleInterestFactor = borrowRate * blockDelta\\n * interestAccumulated = simpleInterestFactor * totalBorrows\\n * totalBorrowsNew = interestAccumulated + totalBorrows\\n * totalReservesNew = interestAccumulated * reserveFactor + totalReserves\\n * borrowIndexNew = simpleInterestFactor * borrowIndex + borrowIndex\\n */\\n\\n Exp memory simpleInterestFactor;\\n uint interestAccumulated;\\n uint totalBorrowsNew;\\n uint totalReservesNew;\\n uint borrowIndexNew;\\n\\n (mathErr, simpleInterestFactor) = mulScalar(Exp({ mantissa: borrowRateMantissa }), blockDelta);\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n (mathErr, interestAccumulated) = mulScalarTruncate(simpleInterestFactor, borrowsPrior);\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n (mathErr, totalBorrowsNew) = addUInt(interestAccumulated, borrowsPrior);\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n (mathErr, totalReservesNew) = mulScalarTruncateAddUInt(\\n Exp({ mantissa: reserveFactorMantissa }),\\n interestAccumulated,\\n reservesPrior\\n );\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n (mathErr, borrowIndexNew) = mulScalarTruncateAddUInt(simpleInterestFactor, borrowIndexPrior, borrowIndexPrior);\\n if (mathErr != MathError.NO_ERROR) {\\n return\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED,\\n uint(mathErr)\\n );\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We write the previously calculated values into storage */\\n accrualBlockNumber = currentBlockNumber;\\n borrowIndex = borrowIndexNew;\\n totalBorrows = totalBorrowsNew;\\n totalReserves = totalReservesNew;\\n\\n (mathErr, blockDelta) = subUInt(currentBlockNumber, reduceReservesBlockNumber);\\n ensureNoMathError(mathErr);\\n if (blockDelta >= reduceReservesBlockDelta) {\\n reduceReservesBlockNumber = currentBlockNumber;\\n uint actualCash = getCashPrior();\\n if (actualCash < totalReservesNew) {\\n _reduceReservesFresh(actualCash);\\n } else {\\n _reduceReservesFresh(totalReservesNew);\\n }\\n }\\n\\n /* We emit an AccrueInterest event */\\n emit AccrueInterest(cashPrior, interestAccumulated, borrowIndexNew, totalBorrowsNew);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Sets a new comptroller for the market\\n * @dev Admin function to set a new comptroller\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // @custom:event Emits NewComptroller event\\n function _setComptroller(ComptrollerInterface newComptroller) public override returns (uint) {\\n // Check caller is admin\\n ensureAdmin(msg.sender);\\n\\n // Ensure invoke comptroller.isComptroller() returns true\\n require(newComptroller.isComptroller(), \\\"marker method returned false\\\");\\n\\n // Emit NewComptroller(oldComptroller, newComptroller)\\n emit NewComptroller(comptroller, newComptroller);\\n\\n // Set market's comptroller to newComptroller\\n comptroller = newComptroller;\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Accrues interest and updates the interest rate model using _setInterestRateModelFresh\\n * @dev Governance function to accrue interest and update the interest rate model\\n * @param newInterestRateModel_ The new interest rate model to use\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _setInterestRateModel(InterestRateModelV8 newInterestRateModel_) public override returns (uint) {\\n ensureAllowed(\\\"_setInterestRateModel(address)\\\");\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted change of interest rate model failed\\n return fail(Error(error), FailureInfo.SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // _setInterestRateModelFresh emits interest-rate-model-update-specific logs on errors, so we don't need to.\\n return _setInterestRateModelFresh(newInterestRateModel_);\\n }\\n\\n /**\\n * @notice Calculates the exchange rate from the underlying to the VToken\\n * @dev This function does not accrue interest before calculating the exchange rate\\n * @return Calculated exchange rate scaled by 1e18\\n */\\n function exchangeRateStored() public view override returns (uint) {\\n (MathError err, uint result) = exchangeRateStoredInternal();\\n ensureNoMathError(err);\\n return result;\\n }\\n\\n /**\\n * @notice Return the borrow balance of account based on stored data\\n * @param account The address whose balance should be calculated\\n * @return The calculated balance\\n */\\n function borrowBalanceStored(address account) public view override returns (uint) {\\n (MathError err, uint result) = borrowBalanceStoredInternal(account);\\n ensureNoMathError(err);\\n return result;\\n }\\n\\n /**\\n * @notice Opens a debt position for the borrower as part of flash loan repayment\\n * @dev This function is specifically called during flash loan operations when the repayment\\n * is insufficient to cover the full borrowed amount plus fees. It creates a debt position\\n * for the unpaid balance. The function checks if the borrow is allowed, accrues interest,\\n * and updates the borrower's balance. It also emits a Borrow event and calls the\\n * comptroller's borrowVerify function. It reverts if the borrow is not allowed or\\n * if the market's block number is not current.\\n * @param borrower The address of the borrower who will have the debt position created\\n * @param borrowAmount The amount of underlying asset that becomes debt (unpaid flash loan balance)\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\\n */\\n function flashLoanDebtPosition(address borrower, uint borrowAmount) external nonReentrant returns (uint256) {\\n // Reverts if the caller is not the comptroller\\n if (msg.sender != address(comptroller)) {\\n revert InvalidComptroller();\\n }\\n\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors.\\n return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // borrowFresh emits borrow-specific logs on errors, so we don't need to\\n return borrowFresh(borrower, payable(address(0)), borrowAmount, false);\\n }\\n\\n /**\\n * @notice Calculates the total fee and protocol fee for a flash loan..\\n * @param amount The amount of the flash loan.\\n * @return totalFee The total fee for the flash loan.\\n * @return protocolFee The portion of the total fee allocated to the protocol.\\n */\\n function calculateFlashLoanFee(uint256 amount) public view returns (uint256, uint256) {\\n MathError mErr;\\n uint256 totalFee;\\n uint256 protocolFee;\\n\\n (mErr, totalFee) = mulScalarTruncate(Exp({ mantissa: amount }), flashLoanFeeMantissa);\\n ensureNoMathError(mErr);\\n\\n (mErr, protocolFee) = mulScalarTruncate(Exp({ mantissa: totalFee }), flashLoanProtocolShareMantissa);\\n ensureNoMathError(mErr);\\n\\n return (totalFee, protocolFee);\\n }\\n\\n /**\\n * @notice Transfers `tokens` tokens from `src` to `dst` by `spender`\\n * @dev Called by both `transfer` and `transferFrom` internally\\n * @param spender The address of the account performing the transfer\\n * @param src The address of the source account\\n * @param dst The address of the destination account\\n * @param tokens The number of tokens to transfer\\n * @return Whether or not the transfer succeeded\\n */\\n function transferTokens(address spender, address src, address dst, uint tokens) internal returns (uint) {\\n /* Fail if transfer not allowed */\\n uint allowed = comptroller.transferAllowed(address(this), src, dst, tokens);\\n if (allowed != 0) {\\n return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.TRANSFER_COMPTROLLER_REJECTION, allowed);\\n }\\n\\n /* Do not allow self-transfers */\\n if (src == dst) {\\n return fail(Error.BAD_INPUT, FailureInfo.TRANSFER_NOT_ALLOWED);\\n }\\n\\n /* Get the allowance, infinite for the account owner */\\n uint startingAllowance = 0;\\n if (spender == src) {\\n startingAllowance = type(uint256).max;\\n } else {\\n startingAllowance = transferAllowances[src][spender];\\n }\\n\\n /* Do the calculations, checking for {under,over}flow */\\n MathError mathErr;\\n uint allowanceNew;\\n uint srvTokensNew;\\n uint dstTokensNew;\\n\\n (mathErr, allowanceNew) = subUInt(startingAllowance, tokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ALLOWED);\\n }\\n\\n (mathErr, srvTokensNew) = subUInt(accountTokens[src], tokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ENOUGH);\\n }\\n\\n (mathErr, dstTokensNew) = addUInt(accountTokens[dst], tokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_TOO_MUCH);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n accountTokens[src] = srvTokensNew;\\n accountTokens[dst] = dstTokensNew;\\n\\n /* Eat some of the allowance (if necessary) */\\n if (startingAllowance != type(uint256).max) {\\n transferAllowances[src][spender] = allowanceNew;\\n }\\n\\n /* We emit a Transfer event */\\n emit Transfer(src, dst, tokens);\\n\\n comptroller.transferVerify(address(this), src, dst, tokens);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Sender supplies assets into the market and receives vTokens in exchange\\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\\n * @param mintAmount The amount of the underlying asset to supply\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\\n */\\n function mintInternal(uint mintAmount) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted mint failed\\n return (fail(Error(error), FailureInfo.MINT_ACCRUE_INTEREST_FAILED), 0);\\n }\\n\\n // mintFresh emits the actual Mint event if successful and logs on errors, so we don't need to\\n return mintFresh(msg.sender, mintAmount);\\n }\\n\\n /**\\n * @notice User supplies assets into the market and receives vTokens in exchange\\n * @dev Assumes interest has already been accrued up to the current block\\n * @param minter The address of the account which is supplying the assets\\n * @param mintAmount The amount of the underlying asset to supply\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\\n */\\n function mintFresh(address minter, uint mintAmount) internal returns (uint, uint) {\\n /* Fail if mint not allowed */\\n uint allowed = comptroller.mintAllowed(address(this), minter, mintAmount);\\n if (allowed != 0) {\\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed), 0);\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.MINT_FRESHNESS_CHECK), 0);\\n }\\n\\n MintLocalVars memory vars;\\n\\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\\n if (vars.mathErr != MathError.NO_ERROR) {\\n return (failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)), 0);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /*\\n * We call `doTransferIn` for the minter and the mintAmount.\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * `doTransferIn` reverts if anything goes wrong, since we can't be sure if\\n * side-effects occurred. The function returns the amount actually transferred,\\n * in case of a fee. On success, the vToken holds an additional `actualMintAmount`\\n * of cash.\\n */\\n vars.actualMintAmount = doTransferIn(minter, mintAmount);\\n\\n /*\\n * We get the current exchange rate and calculate the number of vTokens to be minted:\\n * mintTokens = actualMintAmount / exchangeRate\\n */\\n\\n (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(\\n vars.actualMintAmount,\\n Exp({ mantissa: vars.exchangeRateMantissa })\\n );\\n ensureNoMathError(vars.mathErr);\\n\\n /*\\n * We calculate the new total supply of vTokens and minter token balance, checking for overflow:\\n * totalSupplyNew = totalSupply + mintTokens\\n * accountTokensNew = accountTokens[minter] + mintTokens\\n */\\n (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens);\\n ensureNoMathError(vars.mathErr);\\n (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[minter], vars.mintTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n /* We write previously calculated values into storage */\\n totalSupply = vars.totalSupplyNew;\\n accountTokens[minter] = vars.accountTokensNew;\\n\\n /* We emit a Mint event, and a Transfer event */\\n emit Mint(minter, vars.actualMintAmount, vars.mintTokens, vars.accountTokensNew);\\n emit Transfer(address(this), minter, vars.mintTokens);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.mintVerify(address(this), minter, vars.actualMintAmount, vars.mintTokens);\\n\\n return (uint(Error.NO_ERROR), vars.actualMintAmount);\\n }\\n\\n /**\\n * @notice Sender supplies assets into the market and receiver receives vTokens in exchange\\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\\n * @param receiver The address of the account which is receiving the vTokens\\n * @param mintAmount The amount of the underlying asset to supply\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\\n */\\n function mintBehalfInternal(address receiver, uint mintAmount) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted mintBehalf failed\\n return (fail(Error(error), FailureInfo.MINT_ACCRUE_INTEREST_FAILED), 0);\\n }\\n\\n // mintBehalfFresh emits the actual Mint event if successful and logs on errors, so we don't need to\\n return mintBehalfFresh(msg.sender, receiver, mintAmount);\\n }\\n\\n /**\\n * @notice Payer supplies assets into the market and receiver receives vTokens in exchange\\n * @dev Assumes interest has already been accrued up to the current block\\n * @param payer The address of the account which is paying the underlying token\\n * @param receiver The address of the account which is receiving vToken\\n * @param mintAmount The amount of the underlying asset to supply\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\\n */\\n function mintBehalfFresh(address payer, address receiver, uint mintAmount) internal returns (uint, uint) {\\n ensureNonZeroAddress(receiver);\\n /* Fail if mint not allowed */\\n uint allowed = comptroller.mintAllowed(address(this), receiver, mintAmount);\\n if (allowed != 0) {\\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed), 0);\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.MINT_FRESHNESS_CHECK), 0);\\n }\\n\\n MintLocalVars memory vars;\\n\\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\\n if (vars.mathErr != MathError.NO_ERROR) {\\n return (failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)), 0);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /*\\n * We call `doTransferIn` for the payer and the mintAmount.\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * `doTransferIn` reverts if anything goes wrong, since we can't be sure if\\n * side-effects occurred. The function returns the amount actually transferred,\\n * in case of a fee. On success, the vToken holds an additional `actualMintAmount`\\n * of cash.\\n */\\n vars.actualMintAmount = doTransferIn(payer, mintAmount);\\n\\n /*\\n * We get the current exchange rate and calculate the number of vTokens to be minted:\\n * mintTokens = actualMintAmount / exchangeRate\\n */\\n\\n (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(\\n vars.actualMintAmount,\\n Exp({ mantissa: vars.exchangeRateMantissa })\\n );\\n ensureNoMathError(vars.mathErr);\\n\\n /*\\n * We calculate the new total supply of vTokens and receiver token balance, checking for overflow:\\n * totalSupplyNew = totalSupply + mintTokens\\n * accountTokensNew = accountTokens[receiver] + mintTokens\\n */\\n (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[receiver], vars.mintTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n /* We write previously calculated values into storage */\\n totalSupply = vars.totalSupplyNew;\\n accountTokens[receiver] = vars.accountTokensNew;\\n\\n /* We emit a MintBehalf event, and a Transfer event */\\n emit MintBehalf(payer, receiver, vars.actualMintAmount, vars.mintTokens, vars.accountTokensNew);\\n emit Transfer(address(this), receiver, vars.mintTokens);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.mintVerify(address(this), receiver, vars.actualMintAmount, vars.mintTokens);\\n\\n return (uint(Error.NO_ERROR), vars.actualMintAmount);\\n }\\n\\n /**\\n * @notice Redeemer redeems vTokens in exchange for the underlying assets, transferred to the receiver. Redeemer and receiver can be the same\\n * address, or different addresses if the receiver was previously approved by the redeemer as a valid delegate (see MarketFacet.updateDelegate)\\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\\n * @param redeemer The address of the account which is redeeming the tokens\\n * @param receiver The receiver of the tokens\\n * @param redeemTokens The number of vTokens to redeem into underlying\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function redeemInternal(\\n address redeemer,\\n address payable receiver,\\n uint redeemTokens\\n ) internal nonReentrant returns (uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed\\n return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // redeemFresh emits redeem-specific logs on errors, so we don't need to\\n return redeemFresh(redeemer, receiver, redeemTokens, 0);\\n }\\n\\n /**\\n * @notice Sender redeems underlying assets on behalf of some other address. This function is only available\\n * for senders, explicitly marked as delegates of the supplier using `comptroller.updateDelegate`\\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\\n * @param redeemer The address of the account which is redeeming the tokens\\n * @param receiver The receiver of the tokens, if called by a delegate\\n * @param redeemAmount The amount of underlying to receive from redeeming vTokens\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function redeemUnderlyingInternal(\\n address redeemer,\\n address payable receiver,\\n uint redeemAmount\\n ) internal nonReentrant returns (uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed\\n return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // redeemFresh emits redeem-specific logs on errors, so we don't need to\\n return redeemFresh(redeemer, receiver, 0, redeemAmount);\\n }\\n\\n /**\\n * @notice Redeemer redeems vTokens in exchange for the underlying assets, transferred to the receiver. Redeemer and receiver can be the same\\n * address, or different addresses if the receiver was previously approved by the redeemer as a valid delegate (see MarketFacet.updateDelegate)\\n * @dev Assumes interest has already been accrued up to the current block\\n * @param redeemer The address of the account which is redeeming the tokens\\n * @param receiver The receiver of the tokens\\n * @param redeemTokensIn The number of vTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be non-zero)\\n * @param redeemAmountIn The number of underlying tokens to receive from redeeming vTokens (only one of redeemTokensIn or redeemAmountIn may be non-zero)\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n // solhint-disable-next-line code-complexity\\n function redeemFresh(\\n address redeemer,\\n address payable receiver,\\n uint redeemTokensIn,\\n uint redeemAmountIn\\n ) internal returns (uint) {\\n require(redeemTokensIn == 0 || redeemAmountIn == 0, \\\"one of redeemTokensIn or redeemAmountIn must be zero\\\");\\n\\n RedeemLocalVars memory vars;\\n\\n /* exchangeRate = invoke Exchange Rate Stored() */\\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\\n ensureNoMathError(vars.mathErr);\\n\\n /* If redeemTokensIn > 0: */\\n if (redeemTokensIn > 0) {\\n /*\\n * We calculate the exchange rate and the amount of underlying to be redeemed:\\n * redeemTokens = redeemTokensIn\\n * redeemAmount = redeemTokensIn x exchangeRateCurrent\\n */\\n vars.redeemTokens = redeemTokensIn;\\n\\n (vars.mathErr, vars.redeemAmount) = mulScalarTruncate(\\n Exp({ mantissa: vars.exchangeRateMantissa }),\\n redeemTokensIn\\n );\\n ensureNoMathError(vars.mathErr);\\n } else {\\n /*\\n * We get the current exchange rate and calculate the amount to be redeemed:\\n * redeemTokens = redeemAmountIn / exchangeRate\\n * redeemAmount = redeemAmountIn\\n */\\n\\n (vars.mathErr, vars.redeemTokens) = divScalarByExpTruncate(\\n redeemAmountIn,\\n Exp({ mantissa: vars.exchangeRateMantissa })\\n );\\n ensureNoMathError(vars.mathErr);\\n\\n vars.redeemAmount = redeemAmountIn;\\n }\\n\\n /* Fail if redeem not allowed */\\n uint allowed = comptroller.redeemAllowed(address(this), redeemer, vars.redeemTokens);\\n if (allowed != 0) {\\n revert(\\\"math error\\\");\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n revert(\\\"math error\\\");\\n }\\n\\n /*\\n * We calculate the new total supply and redeemer balance, checking for underflow:\\n * totalSupplyNew = totalSupply - redeemTokens\\n * accountTokensNew = accountTokens[redeemer] - redeemTokens\\n */\\n (vars.mathErr, vars.totalSupplyNew) = subUInt(totalSupply, vars.redeemTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.accountTokensNew) = subUInt(accountTokens[redeemer], vars.redeemTokens);\\n ensureNoMathError(vars.mathErr);\\n\\n /* Fail gracefully if protocol has insufficient cash */\\n if (getCashPrior() < vars.redeemAmount) {\\n revert(\\\"math error\\\");\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We write previously calculated values into storage */\\n totalSupply = vars.totalSupplyNew;\\n accountTokens[redeemer] = vars.accountTokensNew;\\n\\n /*\\n * We invoke doTransferOut for the receiver and the redeemAmount.\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * On success, the vToken has redeemAmount less of cash.\\n * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n */\\n\\n uint feeAmount;\\n uint remainedAmount;\\n if (IComptroller(address(comptroller)).treasuryPercent() != 0) {\\n (vars.mathErr, feeAmount) = mulUInt(\\n vars.redeemAmount,\\n IComptroller(address(comptroller)).treasuryPercent()\\n );\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, feeAmount) = divUInt(feeAmount, MANTISSA_ONE);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, remainedAmount) = subUInt(vars.redeemAmount, feeAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n address payable treasuryAddress = payable(IComptroller(address(comptroller)).treasuryAddress());\\n doTransferOut(treasuryAddress, feeAmount);\\n\\n emit RedeemFee(redeemer, feeAmount, vars.redeemTokens);\\n } else {\\n remainedAmount = vars.redeemAmount;\\n }\\n\\n doTransferOut(receiver, remainedAmount);\\n\\n /* We emit a Transfer event, and a Redeem event */\\n emit Transfer(redeemer, address(this), vars.redeemTokens);\\n emit Redeem(redeemer, remainedAmount, vars.redeemTokens, vars.accountTokensNew);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.redeemVerify(address(this), redeemer, vars.redeemAmount, vars.redeemTokens);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Receiver gets the borrow on behalf of the borrower address\\n * @param borrower The borrower, on behalf of whom to borrow\\n * @param receiver The account that would receive the funds (can be the same as the borrower)\\n * @param borrowAmount The amount of the underlying asset to borrow\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function borrowInternal(\\n address borrower,\\n address payable receiver,\\n uint borrowAmount\\n ) internal nonReentrant returns (uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\\n return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // borrowFresh emits borrow-specific logs on errors, so we don't need to\\n return borrowFresh(borrower, receiver, borrowAmount, true);\\n }\\n\\n /**\\n * @notice Receiver gets the borrow on behalf of the borrower address, controls whether to do the transfer\\n * @dev Before calling this function, ensure that the interest has been accrued\\n * @param borrower The borrower, on behalf of whom to borrow\\n * @param receiver The account that would receive the funds (can be the same as the borrower)\\n * @param borrowAmount The amount of the underlying asset to borrow\\n * @param shouldTransfer Whether to call doTransferOut for the receiver\\n * @return uint Returns 0 on success, otherwise revert (see ErrorReporter.sol for details).\\n */\\n function borrowFresh(\\n address borrower,\\n address payable receiver,\\n uint borrowAmount,\\n bool shouldTransfer\\n ) internal returns (uint) {\\n /* Revert if borrow not allowed */\\n uint allowed = comptroller.borrowAllowed(address(this), borrower, borrowAmount);\\n\\n if (allowed != 0) {\\n revert(\\\"math error\\\");\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n revert(\\\"math error\\\");\\n }\\n\\n /* Revert if protocol has insufficient underlying cash */\\n if (shouldTransfer && getCashPrior() < borrowAmount) {\\n revert(\\\"math error\\\");\\n }\\n\\n BorrowLocalVars memory vars;\\n\\n /*\\n * We calculate the new borrower and total borrow balances, failing on overflow:\\n * accountBorrowsNew = accountBorrows + borrowAmount\\n * totalBorrowsNew = totalBorrows + borrowAmount\\n */\\n (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.accountBorrowsNew) = addUInt(vars.accountBorrows, borrowAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.totalBorrowsNew) = addUInt(totalBorrows, borrowAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We write the previously calculated values into storage */\\n accountBorrows[borrower].principal = vars.accountBorrowsNew;\\n accountBorrows[borrower].interestIndex = borrowIndex;\\n totalBorrows = vars.totalBorrowsNew;\\n\\n if (shouldTransfer) {\\n /*\\n * We invoke doTransferOut for the receiver and the borrowAmount.\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * On success, the vToken borrowAmount less of cash.\\n * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n */\\n doTransferOut(receiver, borrowAmount);\\n }\\n\\n /* We emit a Borrow event */\\n emit Borrow(borrower, borrowAmount, vars.accountBorrowsNew, vars.totalBorrowsNew);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.borrowVerify(address(this), borrower, borrowAmount);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Sender repays their own borrow\\n * @param repayAmount The amount to repay\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n function repayBorrowInternal(uint repayAmount) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\\n return (fail(Error(error), FailureInfo.REPAY_BORROW_ACCRUE_INTEREST_FAILED), 0);\\n }\\n\\n // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to\\n return repayBorrowFresh(msg.sender, msg.sender, repayAmount);\\n }\\n\\n /**\\n * @notice Sender repays a borrow belonging to another borrowing account\\n * @param borrower The account with the debt being payed off\\n * @param repayAmount The amount to repay\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n function repayBorrowBehalfInternal(address borrower, uint repayAmount) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\\n return (fail(Error(error), FailureInfo.REPAY_BEHALF_ACCRUE_INTEREST_FAILED), 0);\\n }\\n\\n // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to\\n return repayBorrowFresh(msg.sender, borrower, repayAmount);\\n }\\n\\n /**\\n * @notice Borrows are repaid by another user (possibly the borrower).\\n * @param payer The account paying off the borrow\\n * @param borrower The account with the debt being payed off\\n * @param repayAmount The amount of undelrying tokens being returned\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n function repayBorrowFresh(address payer, address borrower, uint repayAmount) internal returns (uint, uint) {\\n /* Fail if repayBorrow not allowed */\\n uint allowed = comptroller.repayBorrowAllowed(address(this), payer, borrower, repayAmount);\\n if (allowed != 0) {\\n return (\\n failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REPAY_BORROW_COMPTROLLER_REJECTION, allowed),\\n 0\\n );\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.REPAY_BORROW_FRESHNESS_CHECK), 0);\\n }\\n\\n RepayBorrowLocalVars memory vars;\\n\\n /* We remember the original borrowerIndex for verification purposes */\\n vars.borrowerIndex = accountBorrows[borrower].interestIndex;\\n\\n /* We fetch the amount the borrower owes, with accumulated interest */\\n (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower);\\n if (vars.mathErr != MathError.NO_ERROR) {\\n return (\\n failOpaque(\\n Error.MATH_ERROR,\\n FailureInfo.REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\\n uint(vars.mathErr)\\n ),\\n 0\\n );\\n }\\n\\n // caps the repayAmount to the actual owed amount\\n vars.repayAmount = repayAmount >= vars.accountBorrows ? vars.accountBorrows : repayAmount;\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /*\\n * We call doTransferIn for the payer and the repayAmount\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * On success, the vToken holds an additional repayAmount of cash.\\n * doTransferIn reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n * it returns the amount actually transferred, in case of a fee.\\n */\\n vars.actualRepayAmount = doTransferIn(payer, vars.repayAmount);\\n\\n /*\\n * We calculate the new borrower and total borrow balances, failing on underflow:\\n * accountBorrowsNew = accountBorrows - actualRepayAmount\\n * totalBorrowsNew = totalBorrows - actualRepayAmount\\n */\\n (vars.mathErr, vars.accountBorrowsNew) = subUInt(vars.accountBorrows, vars.actualRepayAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n (vars.mathErr, vars.totalBorrowsNew) = subUInt(totalBorrows, vars.actualRepayAmount);\\n ensureNoMathError(vars.mathErr);\\n\\n /* We write the previously calculated values into storage */\\n accountBorrows[borrower].principal = vars.accountBorrowsNew;\\n accountBorrows[borrower].interestIndex = borrowIndex;\\n totalBorrows = vars.totalBorrowsNew;\\n\\n /* We emit a RepayBorrow event */\\n emit RepayBorrow(payer, borrower, vars.actualRepayAmount, vars.accountBorrowsNew, vars.totalBorrowsNew);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.repayBorrowVerify(address(this), payer, borrower, vars.actualRepayAmount, vars.borrowerIndex);\\n\\n return (uint(Error.NO_ERROR), vars.actualRepayAmount);\\n }\\n\\n /**\\n * @notice The sender liquidates the borrowers collateral.\\n * The collateral seized is transferred to the liquidator.\\n * @param borrower The borrower of this vToken to be liquidated\\n * @param vTokenCollateral The market in which to seize collateral from the borrower\\n * @param repayAmount The amount of the underlying borrowed asset to repay\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n function liquidateBorrowInternal(\\n address borrower,\\n uint repayAmount,\\n VTokenInterface vTokenCollateral\\n ) internal nonReentrant returns (uint, uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed\\n return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED), 0);\\n }\\n\\n error = vTokenCollateral.accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed\\n return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED), 0);\\n }\\n\\n // liquidateBorrowFresh emits borrow-specific logs on errors, so we don't need to\\n return liquidateBorrowFresh(msg.sender, borrower, repayAmount, vTokenCollateral);\\n }\\n\\n /**\\n * @notice The liquidator liquidates the borrowers collateral.\\n * The collateral seized is transferred to the liquidator.\\n * @param borrower The borrower of this vToken to be liquidated\\n * @param liquidator The address repaying the borrow and seizing collateral\\n * @param vTokenCollateral The market in which to seize collateral from the borrower\\n * @param repayAmount The amount of the underlying borrowed asset to repay\\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\\n */\\n // solhint-disable-next-line code-complexity\\n function liquidateBorrowFresh(\\n address liquidator,\\n address borrower,\\n uint repayAmount,\\n VTokenInterface vTokenCollateral\\n ) internal returns (uint, uint) {\\n /* Fail if liquidate not allowed */\\n uint allowed = comptroller.liquidateBorrowAllowed(\\n address(this),\\n address(vTokenCollateral),\\n liquidator,\\n borrower,\\n repayAmount\\n );\\n if (allowed != 0) {\\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_COMPTROLLER_REJECTION, allowed), 0);\\n }\\n\\n /* Verify market's block number equals current block number */\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_FRESHNESS_CHECK), 0);\\n }\\n\\n /* Verify vTokenCollateral market's block number equals current block number */\\n if (vTokenCollateral.accrualBlockNumber() != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_COLLATERAL_FRESHNESS_CHECK), 0);\\n }\\n\\n /* Fail if borrower = liquidator */\\n if (borrower == liquidator) {\\n return (fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_LIQUIDATOR_IS_BORROWER), 0);\\n }\\n\\n /* Fail if repayAmount = 0 */\\n if (repayAmount == 0) {\\n return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_ZERO), 0);\\n }\\n\\n /* Fail if repayAmount = type(uint256).max */\\n if (repayAmount == type(uint256).max) {\\n return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX), 0);\\n }\\n\\n /* Fail if repayBorrow fails */\\n (uint repayBorrowError, uint actualRepayAmount) = repayBorrowFresh(liquidator, borrower, repayAmount);\\n if (repayBorrowError != uint(Error.NO_ERROR)) {\\n return (fail(Error(repayBorrowError), FailureInfo.LIQUIDATE_REPAY_BORROW_FRESH_FAILED), 0);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We calculate the number of collateral tokens that will be seized */\\n (uint amountSeizeError, uint seizeTokens) = comptroller.liquidateCalculateSeizeTokens(\\n borrower,\\n address(this),\\n address(vTokenCollateral),\\n actualRepayAmount\\n );\\n\\n require(amountSeizeError == uint(Error.NO_ERROR), \\\"LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED\\\");\\n\\n /* Revert if borrower collateral token balance < seizeTokens */\\n require(vTokenCollateral.balanceOf(borrower) >= seizeTokens, \\\"LIQUIDATE_SEIZE_TOO_MUCH\\\");\\n\\n // If this is also the collateral, run seizeInternal to avoid re-entrancy, otherwise make an external call\\n uint seizeError;\\n if (address(vTokenCollateral) == address(this)) {\\n seizeError = seizeInternal(address(this), liquidator, borrower, seizeTokens);\\n } else {\\n seizeError = vTokenCollateral.seize(liquidator, borrower, seizeTokens);\\n }\\n\\n /* Revert if seize tokens fails (since we cannot be sure of side effects) */\\n require(seizeError == uint(Error.NO_ERROR), \\\"token seizure failed\\\");\\n\\n /* We emit a LiquidateBorrow event */\\n emit LiquidateBorrow(liquidator, borrower, actualRepayAmount, address(vTokenCollateral), seizeTokens);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.liquidateBorrowVerify(\\n address(this),\\n address(vTokenCollateral),\\n liquidator,\\n borrower,\\n actualRepayAmount,\\n seizeTokens\\n );\\n\\n return (uint(Error.NO_ERROR), actualRepayAmount);\\n }\\n\\n /**\\n * @notice Transfers collateral tokens (this market) to the liquidator.\\n * @dev Called only during an in-kind liquidation, or by liquidateBorrow during the liquidation of another vToken.\\n * Its absolutely critical to use msg.sender as the seizer vToken and not a parameter.\\n * @param seizerToken The contract seizing the collateral (i.e. borrowed vToken)\\n * @param liquidator The account receiving seized collateral\\n * @param borrower The account having collateral seized\\n * @param seizeTokens The number of vTokens to seize\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function seizeInternal(\\n address seizerToken,\\n address liquidator,\\n address borrower,\\n uint seizeTokens\\n ) internal returns (uint) {\\n /* Fail if seize not allowed */\\n uint allowed = comptroller.seizeAllowed(address(this), seizerToken, liquidator, borrower, seizeTokens);\\n if (allowed != 0) {\\n return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_SEIZE_COMPTROLLER_REJECTION, allowed);\\n }\\n\\n /* Fail if borrower = liquidator */\\n if (borrower == liquidator) {\\n return fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER);\\n }\\n\\n MathError mathErr;\\n uint borrowerTokensNew;\\n uint liquidatorTokensNew;\\n\\n /*\\n * We calculate the new borrower and liquidator token balances, failing on underflow/overflow:\\n * borrowerTokensNew = accountTokens[borrower] - seizeTokens\\n * liquidatorTokensNew = accountTokens[liquidator] + seizeTokens\\n */\\n (mathErr, borrowerTokensNew) = subUInt(accountTokens[borrower], seizeTokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED, uint(mathErr));\\n }\\n\\n (mathErr, liquidatorTokensNew) = addUInt(accountTokens[liquidator], seizeTokens);\\n if (mathErr != MathError.NO_ERROR) {\\n return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED, uint(mathErr));\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /* We write the previously calculated values into storage */\\n accountTokens[borrower] = borrowerTokensNew;\\n accountTokens[liquidator] = liquidatorTokensNew;\\n\\n /* Emit a Transfer event */\\n emit Transfer(borrower, liquidator, seizeTokens);\\n\\n /* We call the defense and prime accrue interest hook */\\n comptroller.seizeVerify(address(this), seizerToken, liquidator, borrower, seizeTokens);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Sets a new reserve factor for the protocol (requires fresh interest accrual)\\n * @dev Governance function to set a new reserve factor\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _setReserveFactorFresh(uint newReserveFactorMantissa) internal returns (uint) {\\n // Verify market's block number equals current block number\\n if (accrualBlockNumber != block.number) {\\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_RESERVE_FACTOR_FRESH_CHECK);\\n }\\n\\n // Check newReserveFactor \\u2264 maxReserveFactor\\n if (newReserveFactorMantissa > reserveFactorMaxMantissa) {\\n return fail(Error.BAD_INPUT, FailureInfo.SET_RESERVE_FACTOR_BOUNDS_CHECK);\\n }\\n\\n uint oldReserveFactorMantissa = reserveFactorMantissa;\\n reserveFactorMantissa = newReserveFactorMantissa;\\n\\n emit NewReserveFactor(oldReserveFactorMantissa, newReserveFactorMantissa);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice Accrues interest and adds reserves by transferring from `msg.sender`\\n * @param addAmount Amount of addition to reserves\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _addReservesInternal(uint addAmount) internal nonReentrant returns (uint) {\\n uint error = accrueInterest();\\n if (error != uint(Error.NO_ERROR)) {\\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed.\\n return fail(Error(error), FailureInfo.ADD_RESERVES_ACCRUE_INTEREST_FAILED);\\n }\\n\\n // _addReservesFresh emits reserve-addition-specific logs on errors, so we don't need to.\\n (error, ) = _addReservesFresh(addAmount);\\n return error;\\n }\\n\\n /**\\n * @notice Add reserves by transferring from caller\\n * @dev Requires fresh interest accrual\\n * @param addAmount Amount of addition to reserves\\n * @return (uint, uint) An error code (0=success, otherwise a failure (see ErrorReporter.sol for details)) and the actual amount added, net token fees\\n */\\n function _addReservesFresh(uint addAmount) internal returns (uint, uint) {\\n // totalReserves + actualAddAmount\\n uint totalReservesNew;\\n uint actualAddAmount;\\n\\n // We fail gracefully unless market's block number equals current block number\\n if (accrualBlockNumber != block.number) {\\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.ADD_RESERVES_FRESH_CHECK), actualAddAmount);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n /*\\n * We call doTransferIn for the caller and the addAmount\\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\\n * On success, the vToken holds an additional addAmount of cash.\\n * doTransferIn reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n * it returns the amount actually transferred, in case of a fee.\\n */\\n\\n actualAddAmount = doTransferIn(msg.sender, addAmount);\\n\\n totalReservesNew = totalReserves + actualAddAmount;\\n\\n /* Revert on overflow */\\n require(totalReservesNew >= totalReserves, \\\"add reserves unexpected overflow\\\");\\n\\n // Store reserves[n+1] = reserves[n] + actualAddAmount\\n totalReserves = totalReservesNew;\\n\\n /* Emit NewReserves(admin, actualAddAmount, reserves[n+1]) */\\n emit ReservesAdded(msg.sender, actualAddAmount, totalReservesNew);\\n\\n /* Return (NO_ERROR, actualAddAmount) */\\n return (uint(Error.NO_ERROR), actualAddAmount);\\n }\\n\\n /**\\n * @notice Reduces reserves by transferring to protocol share reserve contract\\n * @dev Requires fresh interest accrual\\n * @param reduceAmount Amount of reduction to reserves\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _reduceReservesFresh(uint reduceAmount) internal virtual returns (uint) {\\n if (reduceAmount == 0) {\\n return uint(Error.NO_ERROR);\\n }\\n\\n // We fail gracefully unless market's block number equals current block number\\n if (accrualBlockNumber != block.number) {\\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDUCE_RESERVES_FRESH_CHECK);\\n }\\n\\n // Fail gracefully if protocol has insufficient underlying cash\\n if (getCashPrior() < reduceAmount) {\\n return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDUCE_RESERVES_CASH_NOT_AVAILABLE);\\n }\\n\\n // Check reduceAmount \\u2264 reserves[n] (totalReserves)\\n if (reduceAmount > totalReserves) {\\n return fail(Error.BAD_INPUT, FailureInfo.REDUCE_RESERVES_VALIDATION);\\n }\\n\\n /////////////////////////\\n // EFFECTS & INTERACTIONS\\n // (No safe failures beyond this point)\\n\\n // Store reserves[n+1] = reserves[n] - reduceAmount\\n totalReserves = totalReserves - reduceAmount;\\n\\n // doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\\n doTransferOut(protocolShareReserve, reduceAmount);\\n\\n IProtocolShareReserve(protocolShareReserve).updateAssetsState(\\n address(comptroller),\\n underlying,\\n IProtocolShareReserve.IncomeType.SPREAD\\n );\\n\\n emit ReservesReduced(protocolShareReserve, reduceAmount, totalReserves);\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /**\\n * @notice updates the interest rate model (requires fresh interest accrual)\\n * @dev Governance function to update the interest rate model\\n * @param newInterestRateModel the new interest rate model to use\\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\\n */\\n function _setInterestRateModelFresh(InterestRateModelV8 newInterestRateModel) internal returns (uint) {\\n // We fail gracefully unless market's block number equals current block number\\n if (accrualBlockNumber != block.number) {\\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_INTEREST_RATE_MODEL_FRESH_CHECK);\\n }\\n\\n // Ensure invoke newInterestRateModel.isInterestRateModel() returns true\\n require(newInterestRateModel.isInterestRateModel(), \\\"marker method returned false\\\");\\n\\n // Emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel)\\n emit NewMarketInterestRateModel(interestRateModel, newInterestRateModel);\\n\\n // Set the interest rate model to newInterestRateModel\\n interestRateModel = newInterestRateModel;\\n\\n return uint(Error.NO_ERROR);\\n }\\n\\n /*** Safe Token ***/\\n\\n /**\\n * @dev Performs a transfer in, reverting upon failure. Returns the amount actually transferred to the protocol, in case of a fee.\\n * This may revert due to insufficient balance or insufficient allowance.\\n */\\n function doTransferIn(address from, uint amount) internal virtual returns (uint);\\n\\n /**\\n * @dev Performs a transfer out, ideally returning an explanatory error code upon failure rather than reverting.\\n * If caller has not called checked protocol's balance, may revert due to insufficient cash held in the contract.\\n * If caller has checked protocol's balance, and verified it is >= amount, this should not revert in normal conditions.\\n */\\n function doTransferOut(address payable to, uint amount) internal virtual;\\n\\n /**\\n * @notice Return the borrow balance of account based on stored data\\n * @param account The address whose balance should be calculated\\n * @return Tuple of error code and the calculated balance or 0 if error code is non-zero\\n */\\n function borrowBalanceStoredInternal(address account) internal view returns (MathError, uint) {\\n /* Note: we do not assert that the market is up to date */\\n MathError mathErr;\\n uint principalTimesIndex;\\n uint result;\\n\\n /* Get borrowBalance and borrowIndex */\\n BorrowSnapshot storage borrowSnapshot = accountBorrows[account];\\n\\n /* If borrowBalance = 0 then borrowIndex is likely also 0.\\n * Rather than failing the calculation with a division by 0, we immediately return 0 in this case.\\n */\\n if (borrowSnapshot.principal == 0) {\\n return (MathError.NO_ERROR, 0);\\n }\\n\\n /* Calculate new borrow balance using the interest index:\\n * recentBorrowBalance = borrower.borrowBalance * market.borrowIndex / borrower.borrowIndex\\n */\\n (mathErr, principalTimesIndex) = mulUInt(borrowSnapshot.principal, borrowIndex);\\n if (mathErr != MathError.NO_ERROR) {\\n return (mathErr, 0);\\n }\\n\\n (mathErr, result) = divUInt(principalTimesIndex, borrowSnapshot.interestIndex);\\n if (mathErr != MathError.NO_ERROR) {\\n return (mathErr, 0);\\n }\\n\\n return (MathError.NO_ERROR, result);\\n }\\n\\n /**\\n * @notice Calculates the exchange rate from the underlying to the vToken\\n * @dev This function does not accrue interest before calculating the exchange rate\\n * @return Tuple of error code and calculated exchange rate scaled by 1e18\\n */\\n function exchangeRateStoredInternal() internal view virtual returns (MathError, uint) {\\n uint _totalSupply = totalSupply;\\n if (_totalSupply == 0) {\\n /*\\n * If there are no tokens minted:\\n * exchangeRate = initialExchangeRate\\n */\\n return (MathError.NO_ERROR, initialExchangeRateMantissa);\\n } else {\\n /*\\n * Otherwise:\\n * exchangeRate = (totalCash + totalBorrows + flashLoanAmount - totalReserves) / totalSupply\\n */\\n uint totalCash = _getCashPriorWithFlashLoan();\\n uint cashPlusBorrowsMinusReserves;\\n Exp memory exchangeRate;\\n MathError mathErr;\\n\\n (mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(totalCash, totalBorrows, totalReserves);\\n if (mathErr != MathError.NO_ERROR) {\\n return (mathErr, 0);\\n }\\n\\n (mathErr, exchangeRate) = getExp(cashPlusBorrowsMinusReserves, _totalSupply);\\n if (mathErr != MathError.NO_ERROR) {\\n return (mathErr, 0);\\n }\\n\\n return (MathError.NO_ERROR, exchangeRate.mantissa);\\n }\\n }\\n\\n /**\\n * @notice Gets balance of this contract including active flash loans\\n * @return The quantity of underlying owned by this contract plus active flash loan amount\\n */\\n function _getCashPriorWithFlashLoan() internal view returns (uint) {\\n return getCashPrior() + flashLoanAmount;\\n }\\n\\n function ensureAllowed(string memory functionSig) private view {\\n require(\\n IAccessControlManagerV8(accessControlManager).isAllowedToCall(msg.sender, functionSig),\\n \\\"access denied\\\"\\n );\\n }\\n\\n function ensureAdmin(address caller_) private view {\\n require(caller_ == admin, \\\"Unauthorized\\\");\\n }\\n\\n function ensureNoMathError(MathError mErr) private pure {\\n require(mErr == MathError.NO_ERROR, \\\"math error\\\");\\n }\\n\\n function ensureNonZeroAddress(address address_) private pure {\\n require(address_ != address(0), \\\"zero address\\\");\\n }\\n\\n /*** Safe Token ***/\\n\\n /**\\n * @notice Gets balance of this contract in terms of the underlying\\n * @dev This excludes the value of the current message, if any\\n * @return The quantity of underlying owned by this contract\\n */\\n function getCashPrior() internal view virtual returns (uint);\\n}\\n\",\"keccak256\":\"0xa71b46dc3b3133980a99068c0e65a547317208f666196e28d38cc66a3808869b\",\"license\":\"BSD-3-Clause\"},\"contracts/Tokens/VTokens/VTokenInterfaces.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { ComptrollerInterface } from \\\"../../Comptroller/ComptrollerInterface.sol\\\";\\nimport { InterestRateModelV8 } from \\\"../../InterestRateModels/InterestRateModelV8.sol\\\";\\n\\ncontract VTokenStorageBase {\\n /**\\n * @notice Container for borrow balance information\\n * @member principal Total balance (with accrued interest), after applying the most recent balance-changing action\\n * @member interestIndex Global borrowIndex as of the most recent balance-changing action\\n */\\n struct BorrowSnapshot {\\n uint principal;\\n uint interestIndex;\\n }\\n\\n /**\\n * @dev Guard variable for re-entrancy checks\\n */\\n bool internal _notEntered;\\n\\n /**\\n * @notice EIP-20 token name for this token\\n */\\n string public name;\\n\\n /**\\n * @notice EIP-20 token symbol for this token\\n */\\n string public symbol;\\n\\n /**\\n * @notice EIP-20 token decimals for this token\\n */\\n uint8 public decimals;\\n\\n /**\\n * @notice Maximum borrow rate that can ever be applied (.0005% / block)\\n */\\n\\n uint internal constant borrowRateMaxMantissa = 0.0005e16;\\n\\n /**\\n * @notice Maximum fraction of interest that can be set aside for reserves\\n */\\n uint internal constant reserveFactorMaxMantissa = 1e18;\\n\\n /**\\n * @notice Administrator for this contract\\n */\\n address payable public admin;\\n\\n /**\\n * @notice Pending administrator for this contract\\n */\\n address payable public pendingAdmin;\\n\\n /**\\n * @notice Contract which oversees inter-vToken operations\\n */\\n ComptrollerInterface public comptroller;\\n\\n /**\\n * @notice Model which tells what the current interest rate should be\\n */\\n InterestRateModelV8 public interestRateModel;\\n\\n /**\\n * @notice Initial exchange rate used when minting the first VTokens (used when totalSupply = 0)\\n */\\n uint internal initialExchangeRateMantissa;\\n\\n /**\\n * @notice Fraction of interest currently set aside for reserves\\n */\\n uint public reserveFactorMantissa;\\n\\n /**\\n * @notice Block number that interest was last accrued at\\n */\\n uint public accrualBlockNumber;\\n\\n /**\\n * @notice Accumulator of the total earned interest rate since the opening of the market\\n */\\n uint public borrowIndex;\\n\\n /**\\n * @notice Total amount of outstanding borrows of the underlying in this market\\n */\\n uint public totalBorrows;\\n\\n /**\\n * @notice Total amount of reserves of the underlying held in this market\\n */\\n uint public totalReserves;\\n\\n /**\\n * @notice Total number of tokens in circulation\\n */\\n uint public totalSupply;\\n\\n /**\\n * @notice Official record of token balances for each account\\n */\\n mapping(address => uint) internal accountTokens;\\n\\n /**\\n * @notice Approved token transfer amounts on behalf of others\\n */\\n mapping(address => mapping(address => uint)) internal transferAllowances;\\n\\n /**\\n * @notice Mapping of account addresses to outstanding borrow balances\\n */\\n mapping(address => BorrowSnapshot) internal accountBorrows;\\n\\n /**\\n * @notice Underlying asset for this VToken\\n */\\n address public underlying;\\n\\n /**\\n * @notice Implementation address for this contract\\n */\\n address public implementation;\\n\\n /**\\n * @notice delta block after which reserves will be reduced\\n */\\n uint public reduceReservesBlockDelta;\\n\\n /**\\n * @notice last block number at which reserves were reduced\\n */\\n uint public reduceReservesBlockNumber;\\n\\n /**\\n * @notice address of protocol share reserve contract\\n */\\n address payable public protocolShareReserve;\\n\\n /**\\n * @notice address of accessControlManager\\n */\\n\\n address public accessControlManager;\\n}\\n\\ncontract VTokenStorage is VTokenStorageBase {\\n /**\\n * @notice flashLoan is enabled for this market or not\\n */\\n bool public isFlashLoanEnabled;\\n\\n /**\\n * @notice total fee percentage collected on flashLoan (scaled by 1e18)\\n */\\n uint256 public flashLoanFeeMantissa;\\n\\n /**\\n * @notice fee percentage of flashLoan that goes to protocol (scaled by 1e18)\\n */\\n uint256 public flashLoanProtocolShareMantissa;\\n\\n /**\\n * @notice Amount of flashLoan taken by the receiver\\n * @dev This is used to track the amount of flashLoan taken to correctly calculate the exchange rate\\n * during the flashLoan process. It is added to the total cash when calculating the exchange rate.\\n */\\n uint256 public flashLoanAmount;\\n\\n /**\\n * @notice Tracked internal cash balance, immune to direct token transfers (donation attacks)\\n * @dev Updated only via doTransferIn/doTransferOut. Must be initialized via sweepTokenAndSync() after upgrade.\\n */\\n uint256 public internalCash;\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[45] private __gap;\\n}\\n\\nabstract contract VTokenInterface is VTokenStorage {\\n /**\\n * @notice Indicator that this is a vToken contract (for inspection)\\n */\\n bool public constant isVToken = true;\\n\\n /*** Market Events ***/\\n\\n /**\\n * @notice Event emitted when interest is accrued\\n */\\n event AccrueInterest(uint cashPrior, uint interestAccumulated, uint borrowIndex, uint totalBorrows);\\n\\n /**\\n * @notice Event emitted when tokens are minted\\n */\\n event Mint(address minter, uint mintAmount, uint mintTokens, uint256 totalSupply);\\n\\n /**\\n * @notice Event emitted when tokens are minted behalf by payer to receiver\\n */\\n event MintBehalf(address payer, address receiver, uint mintAmount, uint mintTokens, uint256 totalSupply);\\n\\n /**\\n * @notice Event emitted when tokens are redeemed\\n */\\n event Redeem(address redeemer, uint redeemAmount, uint redeemTokens, uint256 totalSupply);\\n\\n /**\\n * @notice Event emitted when tokens are redeemed and fee is transferred\\n */\\n event RedeemFee(address redeemer, uint feeAmount, uint redeemTokens);\\n\\n /**\\n * @notice Event emitted when underlying is borrowed\\n */\\n event Borrow(address borrower, uint borrowAmount, uint accountBorrows, uint totalBorrows);\\n\\n /**\\n * @notice Event emitted when a borrow is repaid\\n */\\n event RepayBorrow(address payer, address borrower, uint repayAmount, uint accountBorrows, uint totalBorrows);\\n\\n /**\\n * @notice Event emitted when a borrow is liquidated\\n */\\n event LiquidateBorrow(\\n address liquidator,\\n address borrower,\\n uint repayAmount,\\n address vTokenCollateral,\\n uint seizeTokens\\n );\\n\\n /*** Admin Events ***/\\n\\n /**\\n * @notice Event emitted when pendingAdmin is changed\\n */\\n event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);\\n\\n /**\\n * @notice Event emitted when pendingAdmin is accepted, which means admin has been updated\\n */\\n event NewAdmin(address oldAdmin, address newAdmin);\\n\\n /**\\n * @notice Event emitted when comptroller is changed\\n */\\n event NewComptroller(ComptrollerInterface oldComptroller, ComptrollerInterface newComptroller);\\n\\n /**\\n * @notice Event emitted when interestRateModel is changed\\n */\\n event NewMarketInterestRateModel(\\n InterestRateModelV8 oldInterestRateModel,\\n InterestRateModelV8 newInterestRateModel\\n );\\n\\n /**\\n * @notice Event emitted when the reserve factor is changed\\n */\\n event NewReserveFactor(uint oldReserveFactorMantissa, uint newReserveFactorMantissa);\\n\\n /**\\n * @notice Event emitted when the reserves are added\\n */\\n event ReservesAdded(address benefactor, uint addAmount, uint newTotalReserves);\\n\\n /**\\n * @notice Event emitted when the reserves are reduced\\n */\\n event ReservesReduced(address protocolShareReserve, uint reduceAmount, uint newTotalReserves);\\n\\n /**\\n * @notice EIP20 Transfer event\\n */\\n event Transfer(address indexed from, address indexed to, uint amount);\\n\\n /**\\n * @notice EIP20 Approval event\\n */\\n event Approval(address indexed owner, address indexed spender, uint amount);\\n\\n /**\\n * @notice Event emitted when block delta for reduce reserves get updated\\n */\\n event NewReduceReservesBlockDelta(uint256 oldReduceReservesBlockDelta, uint256 newReduceReservesBlockDelta);\\n\\n /**\\n * @notice Event emitted when address of ProtocolShareReserve contract get updated\\n */\\n event NewProtocolShareReserve(address indexed oldProtocolShareReserve, address indexed newProtocolShareReserve);\\n\\n /**\\n * @notice Emitted when access control address is changed by admin\\n */\\n event NewAccessControlManager(address oldAccessControlAddress, address newAccessControlAddress);\\n\\n /**\\n * @notice Event emitted when flashLoanEnabled status is changed\\n */\\n event FlashLoanStatusChanged(bool previousStatus, bool newStatus);\\n\\n /**\\n * @notice Event emitted when asset is transferred to receiver\\n */\\n event TransferOutUnderlyingFlashLoan(address asset, address receiver, uint256 amount);\\n\\n /**\\n * @notice Event emitted when asset is transferred from sender and verified\\n */\\n event TransferInUnderlyingFlashLoan(\\n address indexed asset,\\n address indexed sender,\\n uint256 amount,\\n uint256 totalFee,\\n uint256 protocolFee\\n );\\n\\n /**\\n * @notice Event emitted when internalCash is synced with actual token balance\\n */\\n event CashSynced(uint256 oldInternalCash, uint256 newInternalCash);\\n\\n /**\\n * @notice Event emitted when excess tokens are swept by admin\\n */\\n event TokenSwept(address indexed recipient, uint256 amount);\\n\\n /**\\n * @notice Event emitted when flashLoan fee mantissa is updated\\n */\\n event FlashLoanFeeUpdated(\\n uint256 oldFlashLoanFeeMantissa,\\n uint256 newFlashLoanFeeMantissa,\\n uint256 oldFlashLoanProtocolShare,\\n uint256 newFlashLoanProtocolShare\\n );\\n\\n // @notice Thrown when comptroller is not valid\\n error InvalidComptroller();\\n\\n // @notice Thrown when there is already an active flashLoan\\n error FlashLoanAlreadyActive();\\n\\n /// @notice Thrown when flash loan fee exceeds maximum allowed\\n error FlashLoanFeeTooHigh(uint256 fee, uint256 maxFee);\\n\\n /// @notice Thrown when flash loan fee protocol share exceeds maximum allowed\\n error FlashLoanProtocolShareTooHigh(uint256 fee, uint256 maxFee);\\n\\n // @notice Thrown when the vToken does not have enough cash to lend\\n error InsufficientCash();\\n\\n /// @notice Thrown when the repayment amount is insufficient to cover the total fee\\n error InsufficientRepayment(uint256 actualAmount, uint256 requiredTotalFee);\\n\\n /*** User Interface ***/\\n\\n function transfer(address dst, uint amount) external virtual returns (bool);\\n\\n function transferFrom(address src, address dst, uint amount) external virtual returns (bool);\\n\\n function approve(address spender, uint amount) external virtual returns (bool);\\n\\n function balanceOfUnderlying(address owner) external virtual returns (uint);\\n\\n function totalBorrowsCurrent() external virtual returns (uint);\\n\\n function borrowBalanceCurrent(address account) external virtual returns (uint);\\n\\n function seize(address liquidator, address borrower, uint seizeTokens) external virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _setPendingAdmin(address payable newPendingAdmin) external virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _acceptAdmin() external virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _setReserveFactor(uint newReserveFactorMantissa) external virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _reduceReserves(uint reduceAmount) external virtual returns (uint);\\n\\n function balanceOf(address owner) external view virtual returns (uint);\\n\\n function allowance(address owner, address spender) external view virtual returns (uint);\\n\\n function getAccountSnapshot(address account) external view virtual returns (uint, uint, uint, uint);\\n\\n function borrowRatePerBlock() external view virtual returns (uint);\\n\\n function supplyRatePerBlock() external view virtual returns (uint);\\n\\n function getCash() external view virtual returns (uint);\\n\\n function exchangeRateCurrent() public virtual returns (uint);\\n\\n function accrueInterest() public virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _setComptroller(ComptrollerInterface newComptroller) public virtual returns (uint);\\n\\n /*** Admin Function ***/\\n function _setInterestRateModel(InterestRateModelV8 newInterestRateModel) public virtual returns (uint);\\n\\n function borrowBalanceStored(address account) public view virtual returns (uint);\\n\\n function exchangeRateStored() public view virtual returns (uint);\\n}\\n\\ninterface VBep20Interface {\\n /*** User Interface ***/\\n\\n function mint(uint mintAmount) external returns (uint);\\n\\n function mintBehalf(address receiver, uint mintAmount) external returns (uint);\\n\\n function redeem(uint redeemTokens) external returns (uint);\\n\\n function redeemUnderlying(uint redeemAmount) external returns (uint);\\n\\n function borrow(uint borrowAmount) external returns (uint);\\n\\n function repayBorrow(uint repayAmount) external returns (uint);\\n\\n function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint);\\n\\n function liquidateBorrow(\\n address borrower,\\n uint repayAmount,\\n VTokenInterface vTokenCollateral\\n ) external returns (uint);\\n\\n /*** Admin Functions ***/\\n\\n function _addReserves(uint addAmount) external returns (uint);\\n}\\n\\ninterface VDelegatorInterface {\\n /**\\n * @notice Emitted when implementation is changed\\n */\\n event NewImplementation(address oldImplementation, address newImplementation);\\n\\n /**\\n * @notice Called by the admin to update the implementation of the delegator\\n * @param implementation_ The address of the new implementation for delegation\\n * @param allowResign Flag to indicate whether to call _resignImplementation on the old implementation\\n * @param becomeImplementationData The encoded bytes data to be passed to _becomeImplementation\\n */\\n function _setImplementation(\\n address implementation_,\\n bool allowResign,\\n bytes memory becomeImplementationData\\n ) external;\\n}\\n\\ninterface VDelegateInterface {\\n /**\\n * @notice Called by the delegator on a delegate to initialize it for duty\\n * @dev Should revert if any issues arise which make it unfit for delegation\\n * @param data The encoded bytes data for any initialization\\n */\\n function _becomeImplementation(bytes memory data) external;\\n\\n /**\\n * @notice Called by the delegator on a delegate to forfeit its responsibility\\n */\\n function _resignImplementation() external;\\n}\\n\",\"keccak256\":\"0xa892e7d531228f2bdc92ddf37be5c43fc29a7d952335397dede149d3ac182017\",\"license\":\"BSD-3-Clause\"},\"contracts/Utils/CarefulMath.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\n/**\\n * @title Careful Math\\n * @author Venus\\n * @notice Derived from OpenZeppelin's SafeMath library\\n * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol\\n */\\ncontract CarefulMath {\\n /**\\n * @dev Possible error codes that we can return\\n */\\n enum MathError {\\n NO_ERROR,\\n DIVISION_BY_ZERO,\\n INTEGER_OVERFLOW,\\n INTEGER_UNDERFLOW\\n }\\n\\n /**\\n * @dev Multiplies two numbers, returns an error on overflow.\\n */\\n function mulUInt(uint a, uint b) internal pure returns (MathError, uint) {\\n if (a == 0) {\\n return (MathError.NO_ERROR, 0);\\n }\\n\\n uint c;\\n unchecked {\\n c = a * b;\\n }\\n\\n if (c / a != b) {\\n return (MathError.INTEGER_OVERFLOW, 0);\\n } else {\\n return (MathError.NO_ERROR, c);\\n }\\n }\\n\\n /**\\n * @dev Integer division of two numbers, truncating the quotient.\\n */\\n function divUInt(uint a, uint b) internal pure returns (MathError, uint) {\\n if (b == 0) {\\n return (MathError.DIVISION_BY_ZERO, 0);\\n }\\n\\n return (MathError.NO_ERROR, a / b);\\n }\\n\\n /**\\n * @dev Subtracts two numbers, returns an error on overflow (i.e. if subtrahend is greater than minuend).\\n */\\n function subUInt(uint a, uint b) internal pure returns (MathError, uint) {\\n if (b <= a) {\\n unchecked {\\n return (MathError.NO_ERROR, a - b);\\n }\\n } else {\\n return (MathError.INTEGER_UNDERFLOW, 0);\\n }\\n }\\n\\n /**\\n * @dev Adds two numbers, returns an error on overflow.\\n */\\n function addUInt(uint a, uint b) internal pure returns (MathError, uint) {\\n uint c;\\n unchecked {\\n c = a + b;\\n }\\n\\n if (c >= a) {\\n return (MathError.NO_ERROR, c);\\n } else {\\n return (MathError.INTEGER_OVERFLOW, 0);\\n }\\n }\\n\\n /**\\n * @dev add a and b and then subtract c\\n */\\n function addThenSubUInt(uint a, uint b, uint c) internal pure returns (MathError, uint) {\\n (MathError err0, uint sum) = addUInt(a, b);\\n\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, 0);\\n }\\n\\n return subUInt(sum, c);\\n }\\n}\\n\",\"keccak256\":\"0xb7ca049dc6f4a31c8994ad5fd2093b9f7f60c21495ff8386c7802ef13d9858a7\",\"license\":\"BSD-3-Clause\"},\"contracts/Utils/ErrorReporter.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { WeightFunction } from \\\"../Comptroller/Diamond/interfaces/IFacetBase.sol\\\";\\n\\ncontract ComptrollerErrorReporter {\\n /// @notice Thrown when You are already in the selected pool.\\n error AlreadyInSelectedPool();\\n\\n /// @notice Thrown when One or more of your assets are not compatible with the selected pool.\\n error IncompatibleBorrowedAssets();\\n\\n /// @notice Thrown when Switching to this pool would fail the liquidity check or lead to liquidation.\\n error LiquidityCheckFailed(uint256 errorCode, uint256 shortfall);\\n\\n /// @notice Thrown when trying to call pool-specific methods on the Core Pool\\n error InvalidOperationForCorePool();\\n\\n /// @notice Thrown when input array lengths do not match\\n error ArrayLengthMismatch();\\n\\n /// @notice Thrown when market trying to add in a pool is not listed in the core pool\\n error MarketNotListedInCorePool();\\n\\n /// @notice Thrown when market is not set in the _poolMarkets mapping\\n error MarketConfigNotFound();\\n\\n /// @notice Thrown when borrowing is not allowed in the selected pool for a given market.\\n error BorrowNotAllowedInPool();\\n\\n /// @notice Thrown when trying to remove a market that is not listed in the given pool.\\n error PoolMarketNotFound(uint96 poolId, address vToken);\\n\\n /// @notice Thrown when a given pool ID does not exist\\n error PoolDoesNotExist(uint96 poolId);\\n\\n /// @notice Thrown when the pool label is empty\\n error EmptyPoolLabel();\\n\\n /// @notice Thrown when a vToken is already listed in the specified pool\\n error MarketAlreadyListed(uint96 poolId, address vToken);\\n\\n /// @notice Thrown when an invalid weighting strategy is provided\\n error InvalidWeightingStrategy(WeightFunction strategy);\\n\\n // @notice Thrown when no assets are requested for flash loan\\n error NoAssetsRequested();\\n\\n // @notice Thrown when invalid flash loan parameters are provided\\n error InvalidFlashLoanParams();\\n\\n // @notice Thrown when flash loan is not enabled on the vToken\\n error FlashLoanNotEnabled();\\n\\n // @notice Thrown when the sender is not authorized to use flashloan onBehalfOf\\n error SenderNotAuthorizedForFlashLoan(address sender);\\n\\n // @notice Thrown when the onBehalfOf didn't approve the contract that receives flashloan\\n error NotAnApprovedDelegate();\\n\\n // @notice Thrown when executeOperation on the receiver contract fails\\n error ExecuteFlashLoanFailed();\\n\\n // @notice Thrown when the requested amount is zero\\n error InvalidAmount();\\n\\n // @notice Thrown when failing to create a debt position in mode 1\\n error FailedToCreateDebtPosition();\\n\\n /// @notice Thrown when attempting to interact with an inactive pool\\n error InactivePool(uint96 poolId);\\n\\n /// @notice Thrown when repayment amount is insufficient to cover the fee\\n error NotEnoughRepayment(uint256 repaid, uint256 required);\\n\\n /// @notice Thrown when the vToken market is not listed\\n error MarketNotListed(address vToken);\\n\\n /// @notice Thrown when flash loans are paused system-wide\\n error FlashLoanPausedSystemWide();\\n\\n /// @notice Thrown when too many assets are requested in a single flash loan\\n error TooManyAssetsRequested(uint256 requested, uint256 maximum);\\n\\n enum Error {\\n NO_ERROR,\\n UNAUTHORIZED,\\n COMPTROLLER_MISMATCH,\\n INSUFFICIENT_SHORTFALL,\\n INSUFFICIENT_LIQUIDITY,\\n INVALID_CLOSE_FACTOR,\\n INVALID_COLLATERAL_FACTOR,\\n INVALID_LIQUIDATION_INCENTIVE,\\n MARKET_NOT_ENTERED, // no longer possible\\n MARKET_NOT_LISTED,\\n MARKET_ALREADY_LISTED,\\n MATH_ERROR,\\n NONZERO_BORROW_BALANCE,\\n PRICE_ERROR,\\n REJECTION,\\n SNAPSHOT_ERROR,\\n TOO_MANY_ASSETS,\\n TOO_MUCH_REPAY,\\n INSUFFICIENT_BALANCE_FOR_VAI,\\n MARKET_NOT_COLLATERAL,\\n INVALID_LIQUIDATION_THRESHOLD\\n }\\n\\n enum FailureInfo {\\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\\n ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK,\\n EXIT_MARKET_BALANCE_OWED,\\n EXIT_MARKET_REJECTION,\\n SET_CLOSE_FACTOR_OWNER_CHECK,\\n SET_CLOSE_FACTOR_VALIDATION,\\n SET_COLLATERAL_FACTOR_OWNER_CHECK,\\n SET_COLLATERAL_FACTOR_NO_EXISTS,\\n SET_COLLATERAL_FACTOR_VALIDATION,\\n SET_COLLATERAL_FACTOR_WITHOUT_PRICE,\\n SET_IMPLEMENTATION_OWNER_CHECK,\\n SET_LIQUIDATION_INCENTIVE_OWNER_CHECK,\\n SET_LIQUIDATION_INCENTIVE_VALIDATION,\\n SET_MAX_ASSETS_OWNER_CHECK,\\n SET_PENDING_ADMIN_OWNER_CHECK,\\n SET_PENDING_IMPLEMENTATION_OWNER_CHECK,\\n SET_PRICE_ORACLE_OWNER_CHECK,\\n SUPPORT_MARKET_EXISTS,\\n SUPPORT_MARKET_OWNER_CHECK,\\n SET_PAUSE_GUARDIAN_OWNER_CHECK,\\n SET_VAI_MINT_RATE_CHECK,\\n SET_VAICONTROLLER_OWNER_CHECK,\\n SET_MINTED_VAI_REJECTION,\\n SET_TREASURY_OWNER_CHECK,\\n UNLIST_MARKET_NOT_LISTED,\\n SET_LIQUIDATION_THRESHOLD_VALIDATION,\\n COLLATERAL_FACTOR_GREATER_THAN_LIQUIDATION_THRESHOLD\\n }\\n\\n /**\\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\\n **/\\n event Failure(uint error, uint info, uint detail);\\n\\n /**\\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\\n */\\n function fail(Error err, FailureInfo info) internal returns (uint) {\\n emit Failure(uint(err), uint(info), 0);\\n\\n return uint(err);\\n }\\n\\n /**\\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\\n */\\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\\n emit Failure(uint(err), uint(info), opaqueError);\\n\\n return uint(err);\\n }\\n}\\n\\ncontract TokenErrorReporter {\\n enum Error {\\n NO_ERROR,\\n UNAUTHORIZED,\\n BAD_INPUT,\\n COMPTROLLER_REJECTION,\\n COMPTROLLER_CALCULATION_ERROR,\\n INTEREST_RATE_MODEL_ERROR,\\n INVALID_ACCOUNT_PAIR,\\n INVALID_CLOSE_AMOUNT_REQUESTED,\\n INVALID_COLLATERAL_FACTOR,\\n MATH_ERROR,\\n MARKET_NOT_FRESH,\\n MARKET_NOT_LISTED,\\n TOKEN_INSUFFICIENT_ALLOWANCE,\\n TOKEN_INSUFFICIENT_BALANCE,\\n TOKEN_INSUFFICIENT_CASH,\\n TOKEN_TRANSFER_IN_FAILED,\\n TOKEN_TRANSFER_OUT_FAILED,\\n TOKEN_PRICE_ERROR\\n }\\n\\n /*\\n * Note: FailureInfo (but not Error) is kept in alphabetical order\\n * This is because FailureInfo grows significantly faster, and\\n * the order of Error has some meaning, while the order of FailureInfo\\n * is entirely arbitrary.\\n */\\n enum FailureInfo {\\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\\n ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED,\\n ACCRUE_INTEREST_BORROW_RATE_CALCULATION_FAILED,\\n ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED,\\n ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED,\\n ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED,\\n ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED,\\n BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\\n BORROW_ACCRUE_INTEREST_FAILED,\\n BORROW_CASH_NOT_AVAILABLE,\\n BORROW_FRESHNESS_CHECK,\\n BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED,\\n BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED,\\n BORROW_MARKET_NOT_LISTED,\\n BORROW_COMPTROLLER_REJECTION,\\n LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED,\\n LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED,\\n LIQUIDATE_COLLATERAL_FRESHNESS_CHECK,\\n LIQUIDATE_COMPTROLLER_REJECTION,\\n LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED,\\n LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX,\\n LIQUIDATE_CLOSE_AMOUNT_IS_ZERO,\\n LIQUIDATE_FRESHNESS_CHECK,\\n LIQUIDATE_LIQUIDATOR_IS_BORROWER,\\n LIQUIDATE_REPAY_BORROW_FRESH_FAILED,\\n LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED,\\n LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED,\\n LIQUIDATE_SEIZE_COMPTROLLER_REJECTION,\\n LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER,\\n LIQUIDATE_SEIZE_TOO_MUCH,\\n MINT_ACCRUE_INTEREST_FAILED,\\n MINT_COMPTROLLER_REJECTION,\\n MINT_EXCHANGE_CALCULATION_FAILED,\\n MINT_EXCHANGE_RATE_READ_FAILED,\\n MINT_FRESHNESS_CHECK,\\n MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED,\\n MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED,\\n MINT_TRANSFER_IN_FAILED,\\n MINT_TRANSFER_IN_NOT_POSSIBLE,\\n REDEEM_ACCRUE_INTEREST_FAILED,\\n REDEEM_COMPTROLLER_REJECTION,\\n REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED,\\n REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED,\\n REDEEM_EXCHANGE_RATE_READ_FAILED,\\n REDEEM_FRESHNESS_CHECK,\\n REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED,\\n REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED,\\n REDEEM_TRANSFER_OUT_NOT_POSSIBLE,\\n REDUCE_RESERVES_ACCRUE_INTEREST_FAILED,\\n REDUCE_RESERVES_ADMIN_CHECK,\\n REDUCE_RESERVES_CASH_NOT_AVAILABLE,\\n REDUCE_RESERVES_FRESH_CHECK,\\n REDUCE_RESERVES_VALIDATION,\\n REPAY_BEHALF_ACCRUE_INTEREST_FAILED,\\n REPAY_BORROW_ACCRUE_INTEREST_FAILED,\\n REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\\n REPAY_BORROW_COMPTROLLER_REJECTION,\\n REPAY_BORROW_FRESHNESS_CHECK,\\n REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED,\\n REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED,\\n REPAY_BORROW_TRANSFER_IN_NOT_POSSIBLE,\\n SET_COLLATERAL_FACTOR_OWNER_CHECK,\\n SET_COLLATERAL_FACTOR_VALIDATION,\\n SET_COMPTROLLER_OWNER_CHECK,\\n SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED,\\n SET_INTEREST_RATE_MODEL_FRESH_CHECK,\\n SET_INTEREST_RATE_MODEL_OWNER_CHECK,\\n SET_MAX_ASSETS_OWNER_CHECK,\\n SET_ORACLE_MARKET_NOT_LISTED,\\n SET_PENDING_ADMIN_OWNER_CHECK,\\n SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED,\\n SET_RESERVE_FACTOR_ADMIN_CHECK,\\n SET_RESERVE_FACTOR_FRESH_CHECK,\\n SET_RESERVE_FACTOR_BOUNDS_CHECK,\\n TRANSFER_COMPTROLLER_REJECTION,\\n TRANSFER_NOT_ALLOWED,\\n TRANSFER_NOT_ENOUGH,\\n TRANSFER_TOO_MUCH,\\n ADD_RESERVES_ACCRUE_INTEREST_FAILED,\\n ADD_RESERVES_FRESH_CHECK,\\n ADD_RESERVES_TRANSFER_IN_NOT_POSSIBLE,\\n TOKEN_GET_UNDERLYING_PRICE_ERROR,\\n REPAY_VAI_COMPTROLLER_REJECTION,\\n REPAY_VAI_FRESHNESS_CHECK,\\n VAI_MINT_EXCHANGE_CALCULATION_FAILED,\\n SFT_MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED\\n }\\n\\n /**\\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\\n **/\\n event Failure(uint error, uint info, uint detail);\\n\\n /**\\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\\n */\\n function fail(Error err, FailureInfo info) internal returns (uint) {\\n emit Failure(uint(err), uint(info), 0);\\n\\n return uint(err);\\n }\\n\\n /**\\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\\n */\\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\\n emit Failure(uint(err), uint(info), opaqueError);\\n\\n return uint(err);\\n }\\n}\\n\\ncontract VAIControllerErrorReporter {\\n enum Error {\\n NO_ERROR,\\n UNAUTHORIZED, // The sender is not authorized to perform this action.\\n REJECTION, // The action would violate the comptroller, vaicontroller policy.\\n SNAPSHOT_ERROR, // The comptroller could not get the account borrows and exchange rate from the market.\\n PRICE_ERROR, // The comptroller could not obtain a required price of an asset.\\n MATH_ERROR, // A math calculation error occurred.\\n INSUFFICIENT_BALANCE_FOR_VAI // Caller does not have sufficient balance to mint VAI.\\n }\\n\\n enum FailureInfo {\\n SET_PENDING_ADMIN_OWNER_CHECK,\\n SET_PENDING_IMPLEMENTATION_OWNER_CHECK,\\n SET_COMPTROLLER_OWNER_CHECK,\\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\\n ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK,\\n VAI_MINT_REJECTION,\\n VAI_BURN_REJECTION,\\n VAI_LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED,\\n VAI_LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED,\\n VAI_LIQUIDATE_COLLATERAL_FRESHNESS_CHECK,\\n VAI_LIQUIDATE_COMPTROLLER_REJECTION,\\n VAI_LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED,\\n VAI_LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX,\\n VAI_LIQUIDATE_CLOSE_AMOUNT_IS_ZERO,\\n VAI_LIQUIDATE_FRESHNESS_CHECK,\\n VAI_LIQUIDATE_LIQUIDATOR_IS_BORROWER,\\n VAI_LIQUIDATE_REPAY_BORROW_FRESH_FAILED,\\n VAI_LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED,\\n VAI_LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED,\\n VAI_LIQUIDATE_SEIZE_COMPTROLLER_REJECTION,\\n VAI_LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER,\\n VAI_LIQUIDATE_SEIZE_TOO_MUCH,\\n MINT_FEE_CALCULATION_FAILED,\\n SET_TREASURY_OWNER_CHECK\\n }\\n\\n /**\\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\\n **/\\n event Failure(uint error, uint info, uint detail);\\n\\n /**\\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\\n */\\n function fail(Error err, FailureInfo info) internal returns (uint) {\\n emit Failure(uint(err), uint(info), 0);\\n\\n return uint(err);\\n }\\n\\n /**\\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\\n */\\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\\n emit Failure(uint(err), uint(info), opaqueError);\\n\\n return uint(err);\\n }\\n}\\n\",\"keccak256\":\"0x9b498db9a2d5f5178b48e5724849f7561c1c0100c1faef4d6dbd741f599861f2\",\"license\":\"BSD-3-Clause\"},\"contracts/Utils/Exponential.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { CarefulMath } from \\\"./CarefulMath.sol\\\";\\nimport { ExponentialNoError } from \\\"./ExponentialNoError.sol\\\";\\n\\n/**\\n * @title Exponential module for storing fixed-precision decimals\\n * @author Venus\\n * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places.\\n * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is:\\n * `Exp({mantissa: 5100000000000000000})`.\\n */\\ncontract Exponential is CarefulMath, ExponentialNoError {\\n /**\\n * @dev Creates an exponential from numerator and denominator values.\\n * Note: Returns an error if (`num` * 10e18) > MAX_INT,\\n * or if `denom` is zero.\\n */\\n function getExp(uint num, uint denom) internal pure returns (MathError, Exp memory) {\\n (MathError err0, uint scaledNumerator) = mulUInt(num, expScale);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n\\n (MathError err1, uint rational) = divUInt(scaledNumerator, denom);\\n if (err1 != MathError.NO_ERROR) {\\n return (err1, Exp({ mantissa: 0 }));\\n }\\n\\n return (MathError.NO_ERROR, Exp({ mantissa: rational }));\\n }\\n\\n /**\\n * @dev Adds two exponentials, returning a new exponential.\\n */\\n function addExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\\n (MathError error, uint result) = addUInt(a.mantissa, b.mantissa);\\n\\n return (error, Exp({ mantissa: result }));\\n }\\n\\n /**\\n * @dev Subtracts two exponentials, returning a new exponential.\\n */\\n function subExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\\n (MathError error, uint result) = subUInt(a.mantissa, b.mantissa);\\n\\n return (error, Exp({ mantissa: result }));\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, returning a new Exp.\\n */\\n function mulScalar(Exp memory a, uint scalar) internal pure returns (MathError, Exp memory) {\\n (MathError err0, uint scaledMantissa) = mulUInt(a.mantissa, scalar);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n\\n return (MathError.NO_ERROR, Exp({ mantissa: scaledMantissa }));\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer.\\n */\\n function mulScalarTruncate(Exp memory a, uint scalar) internal pure returns (MathError, uint) {\\n (MathError err, Exp memory product) = mulScalar(a, scalar);\\n if (err != MathError.NO_ERROR) {\\n return (err, 0);\\n }\\n\\n return (MathError.NO_ERROR, truncate(product));\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer.\\n */\\n function mulScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) internal pure returns (MathError, uint) {\\n (MathError err, Exp memory product) = mulScalar(a, scalar);\\n if (err != MathError.NO_ERROR) {\\n return (err, 0);\\n }\\n\\n return addUInt(truncate(product), addend);\\n }\\n\\n /**\\n * @dev Divide an Exp by a scalar, returning a new Exp.\\n */\\n function divScalar(Exp memory a, uint scalar) internal pure returns (MathError, Exp memory) {\\n (MathError err0, uint descaledMantissa) = divUInt(a.mantissa, scalar);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n\\n return (MathError.NO_ERROR, Exp({ mantissa: descaledMantissa }));\\n }\\n\\n /**\\n * @dev Divide a scalar by an Exp, returning a new Exp.\\n */\\n function divScalarByExp(uint scalar, Exp memory divisor) internal pure returns (MathError, Exp memory) {\\n /*\\n We are doing this as:\\n getExp(mulUInt(expScale, scalar), divisor.mantissa)\\n\\n How it works:\\n Exp = a / b;\\n Scalar = s;\\n `s / (a / b)` = `b * s / a` and since for an Exp `a = mantissa, b = expScale`\\n */\\n (MathError err0, uint numerator) = mulUInt(expScale, scalar);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n return getExp(numerator, divisor.mantissa);\\n }\\n\\n /**\\n * @dev Divide a scalar by an Exp, then truncate to return an unsigned integer.\\n */\\n function divScalarByExpTruncate(uint scalar, Exp memory divisor) internal pure returns (MathError, uint) {\\n (MathError err, Exp memory fraction) = divScalarByExp(scalar, divisor);\\n if (err != MathError.NO_ERROR) {\\n return (err, 0);\\n }\\n\\n return (MathError.NO_ERROR, truncate(fraction));\\n }\\n\\n /**\\n * @dev Multiplies two exponentials, returning a new exponential.\\n */\\n function mulExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\\n (MathError err0, uint doubleScaledProduct) = mulUInt(a.mantissa, b.mantissa);\\n if (err0 != MathError.NO_ERROR) {\\n return (err0, Exp({ mantissa: 0 }));\\n }\\n\\n // We add half the scale before dividing so that we get rounding instead of truncation.\\n // See \\\"Listing 6\\\" and text above it at https://accu.org/index.php/journals/1717\\n // Without this change, a result like 6.6...e-19 will be truncated to 0 instead of being rounded to 1e-18.\\n (MathError err1, uint doubleScaledProductWithHalfScale) = addUInt(halfExpScale, doubleScaledProduct);\\n if (err1 != MathError.NO_ERROR) {\\n return (err1, Exp({ mantissa: 0 }));\\n }\\n\\n (MathError err2, uint product) = divUInt(doubleScaledProductWithHalfScale, expScale);\\n // The only error `div` can return is MathError.DIVISION_BY_ZERO but we control `expScale` and it is not zero.\\n assert(err2 == MathError.NO_ERROR);\\n\\n return (MathError.NO_ERROR, Exp({ mantissa: product }));\\n }\\n\\n /**\\n * @dev Multiplies two exponentials given their mantissas, returning a new exponential.\\n */\\n function mulExp(uint a, uint b) internal pure returns (MathError, Exp memory) {\\n return mulExp(Exp({ mantissa: a }), Exp({ mantissa: b }));\\n }\\n\\n /**\\n * @dev Multiplies three exponentials, returning a new exponential.\\n */\\n function mulExp3(Exp memory a, Exp memory b, Exp memory c) internal pure returns (MathError, Exp memory) {\\n (MathError err, Exp memory ab) = mulExp(a, b);\\n if (err != MathError.NO_ERROR) {\\n return (err, ab);\\n }\\n return mulExp(ab, c);\\n }\\n\\n /**\\n * @dev Divides two exponentials, returning a new exponential.\\n * (a/scale) / (b/scale) = (a/scale) * (scale/b) = a/b,\\n * which we can scale as an Exp by calling getExp(a.mantissa, b.mantissa)\\n */\\n function divExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\\n return getExp(a.mantissa, b.mantissa);\\n }\\n}\\n\",\"keccak256\":\"0x6b9b293a09a3ec69ac16f58dce449a553b44121eb9aad666a777d8f6f4ae83f0\",\"license\":\"BSD-3-Clause\"},\"contracts/Utils/ExponentialNoError.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\n/**\\n * @title Exponential module for storing fixed-precision decimals\\n * @author Compound\\n * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places.\\n * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is:\\n * `Exp({mantissa: 5100000000000000000})`.\\n */\\ncontract ExponentialNoError {\\n uint internal constant expScale = 1e18;\\n uint internal constant doubleScale = 1e36;\\n uint internal constant halfExpScale = expScale / 2;\\n uint internal constant mantissaOne = expScale;\\n\\n struct Exp {\\n uint mantissa;\\n }\\n\\n struct Double {\\n uint mantissa;\\n }\\n\\n /**\\n * @dev Truncates the given exp to a whole number value.\\n * For example, truncate(Exp{mantissa: 15 * expScale}) = 15\\n */\\n function truncate(Exp memory exp) internal pure returns (uint) {\\n // Note: We are not using careful math here as we're performing a division that cannot fail\\n return exp.mantissa / expScale;\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer.\\n */\\n function mul_ScalarTruncate(Exp memory a, uint scalar) internal pure returns (uint) {\\n Exp memory product = mul_(a, scalar);\\n return truncate(product);\\n }\\n\\n /**\\n * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer.\\n */\\n function mul_ScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) internal pure returns (uint) {\\n Exp memory product = mul_(a, scalar);\\n return add_(truncate(product), addend);\\n }\\n\\n /**\\n * @dev Checks if first Exp is less than second Exp.\\n */\\n function lessThanExp(Exp memory left, Exp memory right) internal pure returns (bool) {\\n return left.mantissa < right.mantissa;\\n }\\n\\n /**\\n * @dev Checks if left Exp <= right Exp.\\n */\\n function lessThanOrEqualExp(Exp memory left, Exp memory right) internal pure returns (bool) {\\n return left.mantissa <= right.mantissa;\\n }\\n\\n /**\\n * @dev Checks if left Exp > right Exp.\\n */\\n function greaterThanExp(Exp memory left, Exp memory right) internal pure returns (bool) {\\n return left.mantissa > right.mantissa;\\n }\\n\\n /**\\n * @dev returns true if Exp is exactly zero\\n */\\n function isZeroExp(Exp memory value) internal pure returns (bool) {\\n return value.mantissa == 0;\\n }\\n\\n function safe224(uint n, string memory errorMessage) internal pure returns (uint224) {\\n require(n < 2 ** 224, errorMessage);\\n return uint224(n);\\n }\\n\\n function safe32(uint n, string memory errorMessage) internal pure returns (uint32) {\\n require(n < 2 ** 32, errorMessage);\\n return uint32(n);\\n }\\n\\n function add_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: add_(a.mantissa, b.mantissa) });\\n }\\n\\n function add_(Double memory a, Double memory b) internal pure returns (Double memory) {\\n return Double({ mantissa: add_(a.mantissa, b.mantissa) });\\n }\\n\\n function add_(uint a, uint b) internal pure returns (uint) {\\n return add_(a, b, \\\"addition overflow\\\");\\n }\\n\\n function add_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\\n uint c = a + b;\\n require(c >= a, errorMessage);\\n return c;\\n }\\n\\n function sub_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: sub_(a.mantissa, b.mantissa) });\\n }\\n\\n function sub_(Double memory a, Double memory b) internal pure returns (Double memory) {\\n return Double({ mantissa: sub_(a.mantissa, b.mantissa) });\\n }\\n\\n function sub_(uint a, uint b) internal pure returns (uint) {\\n return sub_(a, b, \\\"subtraction underflow\\\");\\n }\\n\\n function sub_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\\n require(b <= a, errorMessage);\\n return a - b;\\n }\\n\\n function mul_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: mul_(a.mantissa, b.mantissa) / expScale });\\n }\\n\\n function mul_(Exp memory a, uint b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: mul_(a.mantissa, b) });\\n }\\n\\n function mul_(uint a, Exp memory b) internal pure returns (uint) {\\n return mul_(a, b.mantissa) / expScale;\\n }\\n\\n function mul_(Double memory a, Double memory b) internal pure returns (Double memory) {\\n return Double({ mantissa: mul_(a.mantissa, b.mantissa) / doubleScale });\\n }\\n\\n function mul_(Double memory a, uint b) internal pure returns (Double memory) {\\n return Double({ mantissa: mul_(a.mantissa, b) });\\n }\\n\\n function mul_(uint a, Double memory b) internal pure returns (uint) {\\n return mul_(a, b.mantissa) / doubleScale;\\n }\\n\\n function mul_(uint a, uint b) internal pure returns (uint) {\\n return mul_(a, b, \\\"multiplication overflow\\\");\\n }\\n\\n function mul_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\\n if (a == 0 || b == 0) {\\n return 0;\\n }\\n uint c = a * b;\\n require(c / a == b, errorMessage);\\n return c;\\n }\\n\\n function div_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: div_(mul_(a.mantissa, expScale), b.mantissa) });\\n }\\n\\n function div_(Exp memory a, uint b) internal pure returns (Exp memory) {\\n return Exp({ mantissa: div_(a.mantissa, b) });\\n }\\n\\n function div_(uint a, Exp memory b) internal pure returns (uint) {\\n return div_(mul_(a, expScale), b.mantissa);\\n }\\n\\n function div_(Double memory a, Double memory b) internal pure returns (Double memory) {\\n return Double({ mantissa: div_(mul_(a.mantissa, doubleScale), b.mantissa) });\\n }\\n\\n function div_(Double memory a, uint b) internal pure returns (Double memory) {\\n return Double({ mantissa: div_(a.mantissa, b) });\\n }\\n\\n function div_(uint a, Double memory b) internal pure returns (uint) {\\n return div_(mul_(a, doubleScale), b.mantissa);\\n }\\n\\n function div_(uint a, uint b) internal pure returns (uint) {\\n return div_(a, b, \\\"divide by zero\\\");\\n }\\n\\n function div_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\\n require(b > 0, errorMessage);\\n return a / b;\\n }\\n\\n function fraction(uint a, uint b) internal pure returns (Double memory) {\\n return Double({ mantissa: div_(mul_(a, doubleScale), b) });\\n }\\n}\\n\",\"keccak256\":\"0xce75802a56763fb96b2046b374127f080a425cfe3634670767ec040ccb6ea7e0\",\"license\":\"BSD-3-Clause\"},\"contracts/external/IProtocolShareReserve.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\ninterface IProtocolShareReserve {\\n enum IncomeType {\\n SPREAD,\\n LIQUIDATION,\\n ERC4626_WRAPPER_REWARDS,\\n FLASHLOAN\\n }\\n\\n function updateAssetsState(address comptroller, address asset, IncomeType incomeType) external;\\n}\\n\",\"keccak256\":\"0x029861e068e9c4d802650eaf6dd8cfed0980d2ce9a45e6fcc1e1e628ecc1f523\",\"license\":\"BSD-3-Clause\"},\"contracts/external/IWBNB.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\n\\npragma solidity 0.8.25;\\n\\nimport { IERC20Upgradeable } from \\\"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\\\";\\n\\ninterface IWBNB is IERC20Upgradeable {\\n function deposit() external payable;\\n\\n function withdraw(uint256 amount) external;\\n}\\n\",\"keccak256\":\"0x2602fa3bfe075d8ab4608464f02154788a09d0848b3b6e656b0712b176989193\",\"license\":\"BSD-3-Clause\"}},\"version\":1}", + "bytecode": "0x610100604052348015610010575f80fd5b506040516130b83803806130b883398101604081905261002f9161017d565b61003884610083565b61004183610083565b61004a82610083565b61005381610083565b6001600160a01b0380851660805283811660a05282811660c052811660e05261007a6100ad565b505050506101d9565b6001600160a01b0381166100aa576040516342bcdf7f60e11b815260040160405180910390fd5b50565b5f54610100900460ff16156101185760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b5f5460ff90811614610167575f805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b6001600160a01b03811681146100aa575f80fd5b5f805f8060808587031215610190575f80fd5b845161019b81610169565b60208601519094506101ac81610169565b60408601519093506101bd81610169565b60608601519092506101ce81610169565b939692955090935050565b60805160a05160c05160e051612e4a61026e5f395f81816103020152818161125601528181611662015261180c01525f818161014501528181610d7e015261100501525f818161019501528181610d2901528181610fbe015281816111b0015261149a01525f818161021d01528181610c4001528181610e1101528181610f3a015281816114d701526117610152612e4a5ff3fe608060405260043610610129575f3560e01c80638d72647e116100a8578063c4d66de81161006d578063c4d66de81461039e578063e30c3978146103bd578063e58b51e9146103da578063f2fde38b146103f9578063f3d7d28214610418578063fc08f9f614610446575f80fd5b80638d72647e146102f15780638da5cb5b146103245780639646f3ea146103415780639e010bd814610360578063c3c646741461037f575f80fd5b806360d6acc3116100ee57806360d6acc31461023f57806362c067671461026c5780636d70f7ae1461028b578063715018a6146102c957806379ba5097146102dd575f80fd5b806314ba4a101461013457806333e1567f14610184578063558a7297146101b75780635cc47b16146101d85780635fe3b5671461020c575f80fd5b3661013057005b5f80fd5b34801561013f575f80fd5b506101677f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561018f575f80fd5b506101677f000000000000000000000000000000000000000000000000000000000000000081565b3480156101c2575f80fd5b506101d66101d13660046125cf565b610473565b005b3480156101e3575f80fd5b506101676101f2366004612606565b60cb6020525f90815260409020546001600160a01b031681565b348015610217575f80fd5b506101677f000000000000000000000000000000000000000000000000000000000000000081565b34801561024a575f80fd5b5061025e610259366004612628565b6104e3565b60405190815260200161017b565b348015610277575f80fd5b506101d6610286366004612660565b610699565b348015610296575f80fd5b506102b96102a5366004612606565b60c96020525f908152604090205460ff1681565b604051901515815260200161017b565b3480156102d4575f80fd5b506101d6610719565b3480156102e8575f80fd5b506101d6610723565b3480156102fc575f80fd5b506101677f000000000000000000000000000000000000000000000000000000000000000081565b34801561032f575f80fd5b506033546001600160a01b0316610167565b34801561034c575f80fd5b506101d661035b36600461269e565b61079d565b34801561036b575f80fd5b506101d661037a3660046126c8565b610878565b34801561038a575f80fd5b506101d66103993660046125cf565b610962565b3480156103a9575f80fd5b506101d66103b8366004612606565b610a4e565b3480156103c8575f80fd5b506065546001600160a01b0316610167565b3480156103e5575f80fd5b506101d66103f4366004612628565b610b73565b348015610404575f80fd5b506101d6610413366004612606565b610ebb565b348015610423575f80fd5b506102b9610432366004612606565b60ca6020525f908152604090205460ff1681565b348015610451575f80fd5b5061046561046036600461277a565b610f2c565b60405161017b929190612895565b61047b61131f565b61048482611379565b6001600160a01b0382165f81815260c96020908152604091829020805460ff191685151590811790915591519182527f1a594081ae893ab78e67d9b9e843547318164322d32c65369d78a96172d9dc8f91015b60405180910390a25050565b5f6104f66033546001600160a01b031690565b6001600160a01b0316336001600160a01b0316141580156105265750335f90815260c9602052604090205460ff16155b1561054457604051631f0853c160e21b815260040160405180910390fd5b61054c6113a0565b61057561055f60a0840160808501612606565b610570610100850160e08601612606565b6113f9565b8160c001355f0361059957604051630a1c302560e21b815260040160405180910390fd5b8161014001354211156105d1576040516302a07ebf60e31b815261014083013560048201524260248201526044015b60405180910390fd5b5f6105e36105de84612a6c565b611495565b90925090506105f86040840160208501612606565b6001600160a01b03166106116060850160408601612606565b6001600160a01b03166106276020860186612606565b6001600160a01b03167fbfd65b4425967651bb217826b6511e83ef29c5c5ad92f273bb22e7507ed16afb866060013585875f6040516106819493929190938452602084019290925260408301521515606082015260800190565b60405180910390a4506106946001609755565b919050565b6106a161131f565b6106aa83611379565b6106b382611379565b6106c76001600160a01b0384168383611bb2565b816001600160a01b0316836001600160a01b03167f7b09c29f9106defeccc9ac3b823f3aad0b470d120e5df7aed033b5c43a4bf7188360405161070c91815260200190565b60405180910390a3505050565b61072161131f565b565b60655433906001600160a01b031681146107915760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016105c8565b61079a81611c1a565b50565b6107a561131f565b6107ad6113a0565b6107b682611379565b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f81146107ff576040519150601f19603f3d011682016040523d82523d5f602084013e610804565b606091505b505090508061082657604051633d2cec6f60e21b815260040160405180910390fd5b826001600160a01b03167f1aedae30ba52b882825adc6b3ed1febc9a717c20eaca0e2c50fd510e4bdc4cd68360405161086191815260200190565b60405180910390a2506108746001609755565b5050565b61088061131f565b6001600160a01b0382165f90815260ca602052604090205460ff166108c35760405163c665406f60e01b81526001600160a01b03831660048201526024016105c8565b6001600160a01b038116158015906108e357506001600160a01b0381163b155b1561090c576040516319e7368160e01b81526001600160a01b03821660048201526024016105c8565b6001600160a01b038281165f81815260cb602052604080822080546001600160a01b0319169486169485179055517facd82cb4c60f7bdd489fac3eb37cf1e0689fc105c6bacac16cd382fe868a18c49190a35050565b61096a61131f565b61097382611379565b6001600160a01b0382165f90815260ca60205260409020805460ff1916821580159182179092556109bc57506001600160a01b038281165f90815260cb60205260409020541615155b15610a11576001600160a01b0382165f81815260cb602052604080822080546001600160a01b0319169055519091907facd82cb4c60f7bdd489fac3eb37cf1e0689fc105c6bacac16cd382fe868a18c4908390a35b816001600160a01b03167f8443a265af33599abe6c9ed0c3cabd8e2a6c5b8ea35c1d1ed40c513242f95d45826040516104d7911515815260200190565b5f54610100900460ff1615808015610a6c57505f54600160ff909116105b80610a855750303b158015610a8557505f5460ff166001145b610ae85760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016105c8565b5f805460ff191660011790558015610b09575f805461ff0019166101001790555b610b1282611379565b610b1a611c33565b610b22611c61565b610b2b82611c1a565b8015610874575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050565b6033546001600160a01b03163314801590610b9d5750335f90815260c9602052604090205460ff16155b15610bbb57604051631f0853c160e21b815260040160405180910390fd5b610bc36113a0565b610be7610bd660a0830160808401612606565b610570610100840160e08501612606565b8060c001355f03610c0b57604051630a1c302560e21b815260040160405180910390fd5b806101400135421115610c3e576040516302a07ebf60e31b815261014082013560048201524260248201526044016105c8565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316639254f5e56040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c9a573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cbe9190612a77565b6001600160a01b0316610cd76040830160208401612606565b6001600160a01b031603610cfe57604051632100d47360e01b815260040160405180910390fd5b6040805160018082528183019092525f91602080830190803683370190505090506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016610d596040840160208501612606565b6001600160a01b031614610d7c57610d776040830160208401612606565b610d9e565b7f00000000000000000000000000000000000000000000000000000000000000005b815f81518110610db057610db0612a92565b6001600160a01b0392909216602092830291909101909101526040805160018082528183019092525f918160200160208202803683370190505090508260600135815f81518110610e0357610e03612a92565b6020026020010181815250507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316635544ed9c3030858588604051602001610e539190612b10565b6040516020818303038152906040526040518663ffffffff1660e01b8152600401610e82959493929190612c78565b5f604051808303815f87803b158015610e99575f80fd5b505af1158015610eab573d5f803e3d5ffd5b50505050505061079a6001609755565b610ec361131f565b606580546001600160a01b0383166001600160a01b03199091168117909155610ef46033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b5f6060336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610f785760405163880ed0f360e01b815260040160405180910390fd5b6001600160a01b0386163014610fac576040516322c7df1960e21b81526001600160a01b03871660048201526024016105c8565b5f610fb984860186612d03565b90505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031682602001516001600160a01b031614611003578160200151611025565b7f00000000000000000000000000000000000000000000000000000000000000005b9050806001600160a01b03168e8e5f81811061104357611043612a92565b90506020020160208101906110589190612606565b6001600160a01b03161461107f5760405163e60249cb60e01b815260040160405180910390fd5b505f8061108b83611495565b604080516001808252818301909252929450909250602080830190803683370190505093508a8a5f8181106110c2576110c2612a92565b905060200201358d8d5f8181106110db576110db612a92565b905060200201356110ec9190612d49565b845f815181106110fe576110fe612a92565b602002602001018181525050835f8151811061111c5761111c612a92565b602002602001015182101561116a5781845f8151811061113e5761113e612a92565b602002602001015160405163f447a23960e01b81526004016105c8929190918252602082015260400190565b6112868f8f5f81811061117f5761117f612a92565b90506020020160208101906111949190612606565b855f815181106111a6576111a6612a92565b60200260200101517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031686602001516001600160a01b0316146112545785602001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa15801561122b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061124f9190612a77565b611276565b7f00000000000000000000000000000000000000000000000000000000000000005b6001600160a01b03169190611c8f565b82602001516001600160a01b031683604001516001600160a01b0316845f01516001600160a01b03167fbfd65b4425967651bb217826b6511e83ef29c5c5ad92f273bb22e7507ed16afb8660600151858760016040516113019493929190938452602084019290925260408301521515606082015260800190565b60405180910390a4600194505050509a509a98505050505050505050565b6033546001600160a01b031633146107215760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016105c8565b6001600160a01b03811661079a576040516342bcdf7f60e11b815260040160405180910390fd5b6002609754036113f25760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064016105c8565b6002609755565b6001600160a01b0382165f90815260ca602052604090205460ff1661143c5760405163c665406f60e01b81526001600160a01b03831660048201526024016105c8565b6001600160a01b0381161580159061146c57506001600160a01b0381165f90815260ca602052604090205460ff16155b156108745760405163c665406f60e01b81526001600160a01b03821660048201526024016105c8565b5f805f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031684602001516001600160a01b03161490505f807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316639254f5e56040518163ffffffff1660e01b8152600401602060405180830381865afa158015611531573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115559190612a77565b90505f816001600160a01b031687602001516001600160a01b0316149050838061157c5750805b8015611593575060e08701516001600160a01b0316155b156115b157604051631a7a563960e21b815260040160405180910390fd5b8361166057806116245786602001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa1580156115fb573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061161f9190612a77565b611682565b816001600160a01b031663cbeb2b286040518163ffffffff1660e01b8152600401602060405180830381865afa1580156115fb573d5f803e3d5ffd5b7f00000000000000000000000000000000000000000000000000000000000000005b925050505f85604001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa1580156116c7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116eb9190612a77565b60408088015190516370a0823160e01b81523060048201529192505f916001600160a01b03909116906370a0823190602401602060405180830381865afa158015611738573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061175c9190612d5c565b90505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316639bb27d626040518163ffffffff1660e01b8152600401602060405180830381865afa1580156117bb573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117df9190612a77565b90506117ea81611379565b84156118ef576060880151604051632e1a7d4d60e01b815260048101919091527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690632e1a7d4d906024015f604051808303815f87803b158015611855575f80fd5b505af1158015611867573d5f803e3d5ffd5b505050506060880151602089015189516040808c01519051630c9fae0f60e31b81526001600160a01b03938416600482015291831660248301526044820184905282166064820152908316916364fd7078916084015f604051808303818588803b1580156118d3575f80fd5b505af11580156118e5573d5f803e3d5ffd5b505050505061199e565b606088015161190a906001600160a01b038616908390611c8f565b6020880151885160608a01516040808c01519051630c9fae0f60e31b81526001600160a01b0394851660048201529284166024840152604483019190915282166064820152908216906364fd7078906084015f604051808303815f87803b158015611973575f80fd5b505af1158015611985573d5f803e3d5ffd5b5061199e925050506001600160a01b038516825f611c8f565b60408089015190516370a0823160e01b81523060048201525f9184916001600160a01b03909116906370a0823190602401602060405180830381865afa1580156119ea573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a0e9190612d5c565b611a189190612d73565b6040516370a0823160e01b81523060048201529091505f906001600160a01b038616906370a0823190602401602060405180830381865afa158015611a5f573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a839190612d5c565b90505f8a604001516001600160a01b031663db006a75846040518263ffffffff1660e01b8152600401611ab891815260200190565b6020604051808303815f875af1158015611ad4573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611af89190612d5c565b90508015611b1c5760405163eeddaac560e01b8152600481018290526024016105c8565b6040516370a0823160e01b815230600482015282906001600160a01b038816906370a0823190602401602060405180830381865afa158015611b60573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b849190612d5c565b611b8e9190612d73565b9850611b9c87878b8e611d23565b99505050505050505050915091565b6001609755565b6040516001600160a01b038316602482015260448101829052611c1590849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152611ff0565b505050565b606580546001600160a01b031916905561079a816120c3565b5f54610100900460ff16611c595760405162461bcd60e51b81526004016105c890612d86565b610721612114565b5f54610100900460ff16611c875760405162461bcd60e51b81526004016105c890612d86565b610721612143565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b179052611ce08482612169565b611d1d576040516001600160a01b03841660248201525f6044820152611d1390859063095ea7b360e01b90606401611bde565b611d1d8482611ff0565b50505050565b6040516370a0823160e01b81523060048201525f9081906001600160a01b038716906370a0823190602401602060405180830381865afa158015611d69573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d8d9190612d5c565b60e08401519091506001600160a01b0316611dbb57611db68584608001518560a001518761220c565b611f3e565b6101208301516001600160a01b03161580611dec5750856001600160a01b03168361012001516001600160a01b0316145b80611e0d5750846001600160a01b03168361012001516001600160a01b0316145b15611e2b57604051631a7a563960e21b815260040160405180910390fd5b6101208301516040516370a0823160e01b81523060048201525f906001600160a01b038316906370a0823190602401602060405180830381865afa158015611e75573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e999190612d5c565b9050611eaf8786608001518760a001518961220c565b6040516370a0823160e01b81523060048201525f9082906001600160a01b038516906370a0823190602401602060405180830381865afa158015611ef5573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611f199190612d5c565b611f239190612d73565b9050611f3a838760e001518861010001518461220c565b5050505b6040516370a0823160e01b815230600482015281906001600160a01b038816906370a0823190602401602060405180830381865afa158015611f82573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611fa69190612d5c565b611fb09190612d73565b91508260c00151821015611fe75760c083015160405163f447a23960e01b81526105c8918491600401918252602082015260400190565b50949350505050565b5f612044826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166124199092919063ffffffff16565b905080515f14806120645750808060200190518101906120649190612dd1565b611c155760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016105c8565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f54610100900460ff1661213a5760405162461bcd60e51b81526004016105c890612d86565b61072133611c1a565b5f54610100900460ff16611bab5760405162461bcd60e51b81526004016105c890612d86565b5f805f846001600160a01b0316846040516121849190612dec565b5f604051808303815f865af19150503d805f81146121bd576040519150601f19603f3d011682016040523d82523d5f602084013e6121c2565b606091505b50915091508180156121ec5750805115806121ec5750808060200190518101906121ec9190612dd1565b801561220157506001600160a01b0385163b15155b925050505b92915050565b6040516370a0823160e01b81523060048201525f906001600160a01b038616906370a0823190602401602060405180830381865afa158015612250573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122749190612d5c565b6001600160a01b038086165f90815260cb602052604090205491925016806122995750835b6122ad6001600160a01b0387168285611c8f565b5f80866001600160a01b0316866040516122c79190612dec565b5f604051808303815f865af19150503d805f8114612300576040519150601f19603f3d011682016040523d82523d5f602084013e612305565b606091505b5091509150816123375780511561231e57805160208201fd5b60405163081ceff360e41b815260040160405180910390fd5b61234b6001600160a01b038916845f611c8f565b6040516370a0823160e01b81523060048201525f906001600160a01b038a16906370a0823190602401602060405180830381865afa15801561238f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906123b39190612d5c565b6123bd9086612d73565b90508581101561240e576001600160a01b0389167fdb9d488e7e87663eadcaa8fd73fa9c1d93f6bf58cacddfc492a0754443b77c7f6123fc8389612d73565b60405190815260200160405180910390a25b505050505050505050565b606061242784845f8561242f565b949350505050565b6060824710156124905760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016105c8565b5f80866001600160a01b031685876040516124ab9190612dec565b5f6040518083038185875af1925050503d805f81146124e5576040519150601f19603f3d011682016040523d82523d5f602084013e6124ea565b606091505b50915091506124fb87838387612506565b979650505050505050565b606083156125745782515f0361256d576001600160a01b0385163b61256d5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016105c8565b5081612427565b61242783838151156125895781518083602001fd5b8060405162461bcd60e51b81526004016105c89190612e02565b6001600160a01b038116811461079a575f80fd5b8035610694816125a3565b801515811461079a575f80fd5b5f80604083850312156125e0575f80fd5b82356125eb816125a3565b915060208301356125fb816125c2565b809150509250929050565b5f60208284031215612616575f80fd5b8135612621816125a3565b9392505050565b5f60208284031215612638575f80fd5b813567ffffffffffffffff81111561264e575f80fd5b82016101608185031215612621575f80fd5b5f805f60608486031215612672575f80fd5b833561267d816125a3565b9250602084013561268d816125a3565b929592945050506040919091013590565b5f80604083850312156126af575f80fd5b82356126ba816125a3565b946020939093013593505050565b5f80604083850312156126d9575f80fd5b82356126e4816125a3565b915060208301356125fb816125a3565b5f8083601f840112612704575f80fd5b50813567ffffffffffffffff81111561271b575f80fd5b6020830191508360208260051b8501011115612735575f80fd5b9250929050565b5f8083601f84011261274c575f80fd5b50813567ffffffffffffffff811115612763575f80fd5b602083019150836020828501011115612735575f80fd5b5f805f805f805f805f8060c08b8d031215612793575f80fd5b8a3567ffffffffffffffff808211156127aa575f80fd5b6127b68e838f016126f4565b909c509a5060208d01359150808211156127ce575f80fd5b6127da8e838f016126f4565b909a50985060408d01359150808211156127f2575f80fd5b6127fe8e838f016126f4565b909850965086915061281260608e016125b7565b955061282060808e016125b7565b945060a08d0135915080821115612835575f80fd5b506128428d828e0161273c565b915080935050809150509295989b9194979a5092959850565b5f815180845260208085019450602084015f5b8381101561288a5781518752958201959082019060010161286e565b509495945050505050565b8215158152604060208201525f612427604083018461285b565b634e487b7160e01b5f52604160045260245ffd5b604051610160810167ffffffffffffffff811182821017156128e7576128e76128af565b60405290565b5f82601f8301126128fc575f80fd5b813567ffffffffffffffff80821115612917576129176128af565b604051601f8301601f19908116603f0116810190828211818310171561293f5761293f6128af565b81604052838152866020858801011115612957575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f6101608284031215612987575f80fd5b61298f6128c3565b905061299a826125b7565b81526129a8602083016125b7565b60208201526129b9604083016125b7565b6040820152606082013560608201526129d4608083016125b7565b608082015260a082013567ffffffffffffffff808211156129f3575f80fd5b6129ff858386016128ed565b60a084015260c084013560c0840152612a1a60e085016125b7565b60e084015261010091508184013581811115612a34575f80fd5b612a40868287016128ed565b83850152505050610120612a558184016125b7565b818301525061014080830135818301525092915050565b5f6122063683612976565b5f60208284031215612a87575f80fd5b8151612621816125a3565b634e487b7160e01b5f52603260045260245ffd5b5f808335601e19843603018112612abb575f80fd5b830160208101925035905067ffffffffffffffff811115612ada575f80fd5b803603821315612735575f80fd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b60208152612b3160208201612b24846125b7565b6001600160a01b03169052565b5f612b3e602084016125b7565b6001600160a01b038116604084015250612b5a604084016125b7565b6001600160a01b03811660608401525060608301356080830152612b80608084016125b7565b6001600160a01b03811660a084015250612b9d60a0840184612aa6565b6101608060c0860152612bb561018086018385612ae8565b925060c086013560e0860152612bcd60e087016125b7565b9150610100612be6818701846001600160a01b03169052565b612bf281880188612aa6565b93509050610120601f198786030181880152612c0f858584612ae8565b9450612c1c8189016125b7565b93505050610140612c37818701846001600160a01b03169052565b9590950135939094019290925250919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f60a0820160018060a01b0380891684526020818916602086015260a0604086015282885180855260c08701915060208a0194505f5b81811015612ccc578551851683529483019491830191600101612cae565b50508581036060870152612ce0818961285b565b93505050508281036080840152612cf78185612c4a565b98975050505050505050565b5f60208284031215612d13575f80fd5b813567ffffffffffffffff811115612d29575f80fd5b61242784828501612976565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561220657612206612d35565b5f60208284031215612d6c575f80fd5b5051919050565b8181038181111561220657612206612d35565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f60208284031215612de1575f80fd5b8151612621816125c2565b5f82518060208501845e5f920191825250919050565b602081525f6126216020830184612c4a56fea26469706673582212204f8dd3e383ca499cc91de759bb0f999303bdf6f8803540295e6d6c2f03801a2264736f6c63430008190033", + "deployedBytecode": "0x608060405260043610610129575f3560e01c80638d72647e116100a8578063c4d66de81161006d578063c4d66de81461039e578063e30c3978146103bd578063e58b51e9146103da578063f2fde38b146103f9578063f3d7d28214610418578063fc08f9f614610446575f80fd5b80638d72647e146102f15780638da5cb5b146103245780639646f3ea146103415780639e010bd814610360578063c3c646741461037f575f80fd5b806360d6acc3116100ee57806360d6acc31461023f57806362c067671461026c5780636d70f7ae1461028b578063715018a6146102c957806379ba5097146102dd575f80fd5b806314ba4a101461013457806333e1567f14610184578063558a7297146101b75780635cc47b16146101d85780635fe3b5671461020c575f80fd5b3661013057005b5f80fd5b34801561013f575f80fd5b506101677f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561018f575f80fd5b506101677f000000000000000000000000000000000000000000000000000000000000000081565b3480156101c2575f80fd5b506101d66101d13660046125cf565b610473565b005b3480156101e3575f80fd5b506101676101f2366004612606565b60cb6020525f90815260409020546001600160a01b031681565b348015610217575f80fd5b506101677f000000000000000000000000000000000000000000000000000000000000000081565b34801561024a575f80fd5b5061025e610259366004612628565b6104e3565b60405190815260200161017b565b348015610277575f80fd5b506101d6610286366004612660565b610699565b348015610296575f80fd5b506102b96102a5366004612606565b60c96020525f908152604090205460ff1681565b604051901515815260200161017b565b3480156102d4575f80fd5b506101d6610719565b3480156102e8575f80fd5b506101d6610723565b3480156102fc575f80fd5b506101677f000000000000000000000000000000000000000000000000000000000000000081565b34801561032f575f80fd5b506033546001600160a01b0316610167565b34801561034c575f80fd5b506101d661035b36600461269e565b61079d565b34801561036b575f80fd5b506101d661037a3660046126c8565b610878565b34801561038a575f80fd5b506101d66103993660046125cf565b610962565b3480156103a9575f80fd5b506101d66103b8366004612606565b610a4e565b3480156103c8575f80fd5b506065546001600160a01b0316610167565b3480156103e5575f80fd5b506101d66103f4366004612628565b610b73565b348015610404575f80fd5b506101d6610413366004612606565b610ebb565b348015610423575f80fd5b506102b9610432366004612606565b60ca6020525f908152604090205460ff1681565b348015610451575f80fd5b5061046561046036600461277a565b610f2c565b60405161017b929190612895565b61047b61131f565b61048482611379565b6001600160a01b0382165f81815260c96020908152604091829020805460ff191685151590811790915591519182527f1a594081ae893ab78e67d9b9e843547318164322d32c65369d78a96172d9dc8f91015b60405180910390a25050565b5f6104f66033546001600160a01b031690565b6001600160a01b0316336001600160a01b0316141580156105265750335f90815260c9602052604090205460ff16155b1561054457604051631f0853c160e21b815260040160405180910390fd5b61054c6113a0565b61057561055f60a0840160808501612606565b610570610100850160e08601612606565b6113f9565b8160c001355f0361059957604051630a1c302560e21b815260040160405180910390fd5b8161014001354211156105d1576040516302a07ebf60e31b815261014083013560048201524260248201526044015b60405180910390fd5b5f6105e36105de84612a6c565b611495565b90925090506105f86040840160208501612606565b6001600160a01b03166106116060850160408601612606565b6001600160a01b03166106276020860186612606565b6001600160a01b03167fbfd65b4425967651bb217826b6511e83ef29c5c5ad92f273bb22e7507ed16afb866060013585875f6040516106819493929190938452602084019290925260408301521515606082015260800190565b60405180910390a4506106946001609755565b919050565b6106a161131f565b6106aa83611379565b6106b382611379565b6106c76001600160a01b0384168383611bb2565b816001600160a01b0316836001600160a01b03167f7b09c29f9106defeccc9ac3b823f3aad0b470d120e5df7aed033b5c43a4bf7188360405161070c91815260200190565b60405180910390a3505050565b61072161131f565b565b60655433906001600160a01b031681146107915760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016105c8565b61079a81611c1a565b50565b6107a561131f565b6107ad6113a0565b6107b682611379565b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f81146107ff576040519150601f19603f3d011682016040523d82523d5f602084013e610804565b606091505b505090508061082657604051633d2cec6f60e21b815260040160405180910390fd5b826001600160a01b03167f1aedae30ba52b882825adc6b3ed1febc9a717c20eaca0e2c50fd510e4bdc4cd68360405161086191815260200190565b60405180910390a2506108746001609755565b5050565b61088061131f565b6001600160a01b0382165f90815260ca602052604090205460ff166108c35760405163c665406f60e01b81526001600160a01b03831660048201526024016105c8565b6001600160a01b038116158015906108e357506001600160a01b0381163b155b1561090c576040516319e7368160e01b81526001600160a01b03821660048201526024016105c8565b6001600160a01b038281165f81815260cb602052604080822080546001600160a01b0319169486169485179055517facd82cb4c60f7bdd489fac3eb37cf1e0689fc105c6bacac16cd382fe868a18c49190a35050565b61096a61131f565b61097382611379565b6001600160a01b0382165f90815260ca60205260409020805460ff1916821580159182179092556109bc57506001600160a01b038281165f90815260cb60205260409020541615155b15610a11576001600160a01b0382165f81815260cb602052604080822080546001600160a01b0319169055519091907facd82cb4c60f7bdd489fac3eb37cf1e0689fc105c6bacac16cd382fe868a18c4908390a35b816001600160a01b03167f8443a265af33599abe6c9ed0c3cabd8e2a6c5b8ea35c1d1ed40c513242f95d45826040516104d7911515815260200190565b5f54610100900460ff1615808015610a6c57505f54600160ff909116105b80610a855750303b158015610a8557505f5460ff166001145b610ae85760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016105c8565b5f805460ff191660011790558015610b09575f805461ff0019166101001790555b610b1282611379565b610b1a611c33565b610b22611c61565b610b2b82611c1a565b8015610874575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050565b6033546001600160a01b03163314801590610b9d5750335f90815260c9602052604090205460ff16155b15610bbb57604051631f0853c160e21b815260040160405180910390fd5b610bc36113a0565b610be7610bd660a0830160808401612606565b610570610100840160e08501612606565b8060c001355f03610c0b57604051630a1c302560e21b815260040160405180910390fd5b806101400135421115610c3e576040516302a07ebf60e31b815261014082013560048201524260248201526044016105c8565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316639254f5e56040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c9a573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cbe9190612a77565b6001600160a01b0316610cd76040830160208401612606565b6001600160a01b031603610cfe57604051632100d47360e01b815260040160405180910390fd5b6040805160018082528183019092525f91602080830190803683370190505090506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016610d596040840160208501612606565b6001600160a01b031614610d7c57610d776040830160208401612606565b610d9e565b7f00000000000000000000000000000000000000000000000000000000000000005b815f81518110610db057610db0612a92565b6001600160a01b0392909216602092830291909101909101526040805160018082528183019092525f918160200160208202803683370190505090508260600135815f81518110610e0357610e03612a92565b6020026020010181815250507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316635544ed9c3030858588604051602001610e539190612b10565b6040516020818303038152906040526040518663ffffffff1660e01b8152600401610e82959493929190612c78565b5f604051808303815f87803b158015610e99575f80fd5b505af1158015610eab573d5f803e3d5ffd5b50505050505061079a6001609755565b610ec361131f565b606580546001600160a01b0383166001600160a01b03199091168117909155610ef46033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b5f6060336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610f785760405163880ed0f360e01b815260040160405180910390fd5b6001600160a01b0386163014610fac576040516322c7df1960e21b81526001600160a01b03871660048201526024016105c8565b5f610fb984860186612d03565b90505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031682602001516001600160a01b031614611003578160200151611025565b7f00000000000000000000000000000000000000000000000000000000000000005b9050806001600160a01b03168e8e5f81811061104357611043612a92565b90506020020160208101906110589190612606565b6001600160a01b03161461107f5760405163e60249cb60e01b815260040160405180910390fd5b505f8061108b83611495565b604080516001808252818301909252929450909250602080830190803683370190505093508a8a5f8181106110c2576110c2612a92565b905060200201358d8d5f8181106110db576110db612a92565b905060200201356110ec9190612d49565b845f815181106110fe576110fe612a92565b602002602001018181525050835f8151811061111c5761111c612a92565b602002602001015182101561116a5781845f8151811061113e5761113e612a92565b602002602001015160405163f447a23960e01b81526004016105c8929190918252602082015260400190565b6112868f8f5f81811061117f5761117f612a92565b90506020020160208101906111949190612606565b855f815181106111a6576111a6612a92565b60200260200101517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031686602001516001600160a01b0316146112545785602001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa15801561122b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061124f9190612a77565b611276565b7f00000000000000000000000000000000000000000000000000000000000000005b6001600160a01b03169190611c8f565b82602001516001600160a01b031683604001516001600160a01b0316845f01516001600160a01b03167fbfd65b4425967651bb217826b6511e83ef29c5c5ad92f273bb22e7507ed16afb8660600151858760016040516113019493929190938452602084019290925260408301521515606082015260800190565b60405180910390a4600194505050509a509a98505050505050505050565b6033546001600160a01b031633146107215760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016105c8565b6001600160a01b03811661079a576040516342bcdf7f60e11b815260040160405180910390fd5b6002609754036113f25760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064016105c8565b6002609755565b6001600160a01b0382165f90815260ca602052604090205460ff1661143c5760405163c665406f60e01b81526001600160a01b03831660048201526024016105c8565b6001600160a01b0381161580159061146c57506001600160a01b0381165f90815260ca602052604090205460ff16155b156108745760405163c665406f60e01b81526001600160a01b03821660048201526024016105c8565b5f805f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031684602001516001600160a01b03161490505f807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316639254f5e56040518163ffffffff1660e01b8152600401602060405180830381865afa158015611531573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115559190612a77565b90505f816001600160a01b031687602001516001600160a01b0316149050838061157c5750805b8015611593575060e08701516001600160a01b0316155b156115b157604051631a7a563960e21b815260040160405180910390fd5b8361166057806116245786602001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa1580156115fb573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061161f9190612a77565b611682565b816001600160a01b031663cbeb2b286040518163ffffffff1660e01b8152600401602060405180830381865afa1580156115fb573d5f803e3d5ffd5b7f00000000000000000000000000000000000000000000000000000000000000005b925050505f85604001516001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa1580156116c7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116eb9190612a77565b60408088015190516370a0823160e01b81523060048201529192505f916001600160a01b03909116906370a0823190602401602060405180830381865afa158015611738573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061175c9190612d5c565b90505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316639bb27d626040518163ffffffff1660e01b8152600401602060405180830381865afa1580156117bb573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117df9190612a77565b90506117ea81611379565b84156118ef576060880151604051632e1a7d4d60e01b815260048101919091527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690632e1a7d4d906024015f604051808303815f87803b158015611855575f80fd5b505af1158015611867573d5f803e3d5ffd5b505050506060880151602089015189516040808c01519051630c9fae0f60e31b81526001600160a01b03938416600482015291831660248301526044820184905282166064820152908316916364fd7078916084015f604051808303818588803b1580156118d3575f80fd5b505af11580156118e5573d5f803e3d5ffd5b505050505061199e565b606088015161190a906001600160a01b038616908390611c8f565b6020880151885160608a01516040808c01519051630c9fae0f60e31b81526001600160a01b0394851660048201529284166024840152604483019190915282166064820152908216906364fd7078906084015f604051808303815f87803b158015611973575f80fd5b505af1158015611985573d5f803e3d5ffd5b5061199e925050506001600160a01b038516825f611c8f565b60408089015190516370a0823160e01b81523060048201525f9184916001600160a01b03909116906370a0823190602401602060405180830381865afa1580156119ea573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a0e9190612d5c565b611a189190612d73565b6040516370a0823160e01b81523060048201529091505f906001600160a01b038616906370a0823190602401602060405180830381865afa158015611a5f573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a839190612d5c565b90505f8a604001516001600160a01b031663db006a75846040518263ffffffff1660e01b8152600401611ab891815260200190565b6020604051808303815f875af1158015611ad4573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611af89190612d5c565b90508015611b1c5760405163eeddaac560e01b8152600481018290526024016105c8565b6040516370a0823160e01b815230600482015282906001600160a01b038816906370a0823190602401602060405180830381865afa158015611b60573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b849190612d5c565b611b8e9190612d73565b9850611b9c87878b8e611d23565b99505050505050505050915091565b6001609755565b6040516001600160a01b038316602482015260448101829052611c1590849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152611ff0565b505050565b606580546001600160a01b031916905561079a816120c3565b5f54610100900460ff16611c595760405162461bcd60e51b81526004016105c890612d86565b610721612114565b5f54610100900460ff16611c875760405162461bcd60e51b81526004016105c890612d86565b610721612143565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b179052611ce08482612169565b611d1d576040516001600160a01b03841660248201525f6044820152611d1390859063095ea7b360e01b90606401611bde565b611d1d8482611ff0565b50505050565b6040516370a0823160e01b81523060048201525f9081906001600160a01b038716906370a0823190602401602060405180830381865afa158015611d69573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d8d9190612d5c565b60e08401519091506001600160a01b0316611dbb57611db68584608001518560a001518761220c565b611f3e565b6101208301516001600160a01b03161580611dec5750856001600160a01b03168361012001516001600160a01b0316145b80611e0d5750846001600160a01b03168361012001516001600160a01b0316145b15611e2b57604051631a7a563960e21b815260040160405180910390fd5b6101208301516040516370a0823160e01b81523060048201525f906001600160a01b038316906370a0823190602401602060405180830381865afa158015611e75573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e999190612d5c565b9050611eaf8786608001518760a001518961220c565b6040516370a0823160e01b81523060048201525f9082906001600160a01b038516906370a0823190602401602060405180830381865afa158015611ef5573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611f199190612d5c565b611f239190612d73565b9050611f3a838760e001518861010001518461220c565b5050505b6040516370a0823160e01b815230600482015281906001600160a01b038816906370a0823190602401602060405180830381865afa158015611f82573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611fa69190612d5c565b611fb09190612d73565b91508260c00151821015611fe75760c083015160405163f447a23960e01b81526105c8918491600401918252602082015260400190565b50949350505050565b5f612044826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166124199092919063ffffffff16565b905080515f14806120645750808060200190518101906120649190612dd1565b611c155760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016105c8565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f54610100900460ff1661213a5760405162461bcd60e51b81526004016105c890612d86565b61072133611c1a565b5f54610100900460ff16611bab5760405162461bcd60e51b81526004016105c890612d86565b5f805f846001600160a01b0316846040516121849190612dec565b5f604051808303815f865af19150503d805f81146121bd576040519150601f19603f3d011682016040523d82523d5f602084013e6121c2565b606091505b50915091508180156121ec5750805115806121ec5750808060200190518101906121ec9190612dd1565b801561220157506001600160a01b0385163b15155b925050505b92915050565b6040516370a0823160e01b81523060048201525f906001600160a01b038616906370a0823190602401602060405180830381865afa158015612250573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122749190612d5c565b6001600160a01b038086165f90815260cb602052604090205491925016806122995750835b6122ad6001600160a01b0387168285611c8f565b5f80866001600160a01b0316866040516122c79190612dec565b5f604051808303815f865af19150503d805f8114612300576040519150601f19603f3d011682016040523d82523d5f602084013e612305565b606091505b5091509150816123375780511561231e57805160208201fd5b60405163081ceff360e41b815260040160405180910390fd5b61234b6001600160a01b038916845f611c8f565b6040516370a0823160e01b81523060048201525f906001600160a01b038a16906370a0823190602401602060405180830381865afa15801561238f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906123b39190612d5c565b6123bd9086612d73565b90508581101561240e576001600160a01b0389167fdb9d488e7e87663eadcaa8fd73fa9c1d93f6bf58cacddfc492a0754443b77c7f6123fc8389612d73565b60405190815260200160405180910390a25b505050505050505050565b606061242784845f8561242f565b949350505050565b6060824710156124905760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016105c8565b5f80866001600160a01b031685876040516124ab9190612dec565b5f6040518083038185875af1925050503d805f81146124e5576040519150601f19603f3d011682016040523d82523d5f602084013e6124ea565b606091505b50915091506124fb87838387612506565b979650505050505050565b606083156125745782515f0361256d576001600160a01b0385163b61256d5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016105c8565b5081612427565b61242783838151156125895781518083602001fd5b8060405162461bcd60e51b81526004016105c89190612e02565b6001600160a01b038116811461079a575f80fd5b8035610694816125a3565b801515811461079a575f80fd5b5f80604083850312156125e0575f80fd5b82356125eb816125a3565b915060208301356125fb816125c2565b809150509250929050565b5f60208284031215612616575f80fd5b8135612621816125a3565b9392505050565b5f60208284031215612638575f80fd5b813567ffffffffffffffff81111561264e575f80fd5b82016101608185031215612621575f80fd5b5f805f60608486031215612672575f80fd5b833561267d816125a3565b9250602084013561268d816125a3565b929592945050506040919091013590565b5f80604083850312156126af575f80fd5b82356126ba816125a3565b946020939093013593505050565b5f80604083850312156126d9575f80fd5b82356126e4816125a3565b915060208301356125fb816125a3565b5f8083601f840112612704575f80fd5b50813567ffffffffffffffff81111561271b575f80fd5b6020830191508360208260051b8501011115612735575f80fd5b9250929050565b5f8083601f84011261274c575f80fd5b50813567ffffffffffffffff811115612763575f80fd5b602083019150836020828501011115612735575f80fd5b5f805f805f805f805f8060c08b8d031215612793575f80fd5b8a3567ffffffffffffffff808211156127aa575f80fd5b6127b68e838f016126f4565b909c509a5060208d01359150808211156127ce575f80fd5b6127da8e838f016126f4565b909a50985060408d01359150808211156127f2575f80fd5b6127fe8e838f016126f4565b909850965086915061281260608e016125b7565b955061282060808e016125b7565b945060a08d0135915080821115612835575f80fd5b506128428d828e0161273c565b915080935050809150509295989b9194979a5092959850565b5f815180845260208085019450602084015f5b8381101561288a5781518752958201959082019060010161286e565b509495945050505050565b8215158152604060208201525f612427604083018461285b565b634e487b7160e01b5f52604160045260245ffd5b604051610160810167ffffffffffffffff811182821017156128e7576128e76128af565b60405290565b5f82601f8301126128fc575f80fd5b813567ffffffffffffffff80821115612917576129176128af565b604051601f8301601f19908116603f0116810190828211818310171561293f5761293f6128af565b81604052838152866020858801011115612957575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f6101608284031215612987575f80fd5b61298f6128c3565b905061299a826125b7565b81526129a8602083016125b7565b60208201526129b9604083016125b7565b6040820152606082013560608201526129d4608083016125b7565b608082015260a082013567ffffffffffffffff808211156129f3575f80fd5b6129ff858386016128ed565b60a084015260c084013560c0840152612a1a60e085016125b7565b60e084015261010091508184013581811115612a34575f80fd5b612a40868287016128ed565b83850152505050610120612a558184016125b7565b818301525061014080830135818301525092915050565b5f6122063683612976565b5f60208284031215612a87575f80fd5b8151612621816125a3565b634e487b7160e01b5f52603260045260245ffd5b5f808335601e19843603018112612abb575f80fd5b830160208101925035905067ffffffffffffffff811115612ada575f80fd5b803603821315612735575f80fd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b60208152612b3160208201612b24846125b7565b6001600160a01b03169052565b5f612b3e602084016125b7565b6001600160a01b038116604084015250612b5a604084016125b7565b6001600160a01b03811660608401525060608301356080830152612b80608084016125b7565b6001600160a01b03811660a084015250612b9d60a0840184612aa6565b6101608060c0860152612bb561018086018385612ae8565b925060c086013560e0860152612bcd60e087016125b7565b9150610100612be6818701846001600160a01b03169052565b612bf281880188612aa6565b93509050610120601f198786030181880152612c0f858584612ae8565b9450612c1c8189016125b7565b93505050610140612c37818701846001600160a01b03169052565b9590950135939094019290925250919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f60a0820160018060a01b0380891684526020818916602086015260a0604086015282885180855260c08701915060208a0194505f5b81811015612ccc578551851683529483019491830191600101612cae565b50508581036060870152612ce0818961285b565b93505050508281036080840152612cf78185612c4a565b98975050505050505050565b5f60208284031215612d13575f80fd5b813567ffffffffffffffff811115612d29575f80fd5b61242784828501612976565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561220657612206612d35565b5f60208284031215612d6c575f80fd5b5051919050565b8181038181111561220657612206612d35565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f60208284031215612de1575f80fd5b8151612621816125c2565b5f82518060208501845e5f920191825250919050565b602081525f6126216020830184612c4a56fea26469706673582212204f8dd3e383ca499cc91de759bb0f999303bdf6f8803540295e6d6c2f03801a2264736f6c63430008190033", "devdoc": { "author": "Venus", "events": { @@ -801,6 +892,12 @@ "vBStock": "The seized bStock collateral market.", "vDebt": "The repaid debt market." } + }, + "PartialSwapLeftover(address,uint256)": { + "params": { + "amount": "The residual amount not consumed by the swap.", + "token": "The input token left over (bStock on hop 1, the intermediate on hop 2)." + } } }, "kind": "dev", @@ -858,9 +955,6 @@ "pendingOwner()": { "details": "Returns the address of the pending owner." }, - "renounceOwnership()": { - "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." - }, "setOperator(address,bool)": { "params": { "allowed": "True to allow, false to remove.", @@ -868,11 +962,19 @@ } }, "setRouter(address,bool)": { + "details": "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.", "params": { "allowed": "True to allow, false to remove.", "router": "Address to allowlist or remove." } }, + "setRouterSpender(address,address)": { + "details": "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.", + "params": { + "router": "The allowlisted swap target (call target).", + "spender": "The contract that pulls the input token via `transferFrom` during settlement." + } + }, "sweep(address,address,uint256)": { "params": { "amount": "Amount to withdraw.", @@ -881,6 +983,7 @@ } }, "sweepNative(address,uint256)": { + "details": "`nonReentrant` is defense-in-depth only (the function is `onlyOwner`, snapshots no state, and reads no balance after the native `.call`), added for consistency with the liquidation entrypoints.", "params": { "amount": "Amount of native BNB to withdraw.", "to": "Recipient." @@ -922,6 +1025,11 @@ "notice": "Thrown when the call is submitted after `params.deadline`." } ], + "FlashNotSupportedForVai()": [ + { + "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." + } + ], "InsufficientOut(uint256,uint256)": [ { "notice": "Thrown when swap proceeds are below `minOut`." @@ -957,6 +1065,11 @@ "notice": "Thrown when the supplied swap router is not allowlisted." } ], + "SpenderNotContract(address)": [ + { + "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." + } + ], "SwapFailed()": [ { "notice": "Thrown when the low-level call to the router reverts." @@ -985,9 +1098,15 @@ "OperatorSet(address,bool)": { "notice": "Emitted when an operator is allowlisted or removed." }, + "PartialSwapLeftover(address,uint256)": { + "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`." + }, "RouterSet(address,bool)": { "notice": "Emitted when a swap router is allowlisted or removed." }, + "RouterSpenderSet(address,address)": { + "notice": "Emitted when a router's token-approval target (spender) is set or cleared." + }, "Swept(address,address,uint256)": { "notice": "Emitted when the owner withdraws a token." }, @@ -1021,12 +1140,21 @@ "liquidate((address,address,address,uint256,address,bytes,uint256,address,bytes,address,uint256))": { "notice": "Liquidate using the contract's own debt-asset inventory." }, + "renounceOwnership()": { + "notice": "Disabled. This backstop custodies protocol capital (debt-asset inventory, native BNB) and every admin function (`sweep`, `sweepNative`, `setOperator`, `setRouter`) is `onlyOwner`, so renouncing ownership would permanently strand those funds and brick the contract. The override is an `onlyOwner` no-op: an accidental owner call cannot zero the owner, and a non-owner call reverts. Ownership is still transferable via the two-step `transferOwnership` flow." + }, + "routerSpender(address)": { + "notice": "Optional token-approval target (spender) per router, for aggregators whose settlement contract that pulls the input token differs from the call target (e.g. Liquid Mesh, where the router is the call target but a separate spender pulls the token). When unset (address(0)), the approval defaults to the router itself — the Native behaviour, where the call target IS the puller — so existing routers need no spender entry." + }, "setOperator(address,bool)": { "notice": "Allow or disallow an address to trigger liquidations." }, "setRouter(address,bool)": { "notice": "Allow or disallow a router as the swap target (e.g. the Native router)." }, + "setRouterSpender(address,address)": { + "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." + }, "sweep(address,address,uint256)": { "notice": "Withdraw any token (profit, leftover inventory, stuck dust) to `to`." }, @@ -1043,7 +1171,7 @@ "notice": "WBNB token: the debt-accounting asset for BNB debt, unwrapped to native BNB for the repay." } }, - "notice": "Atomic backstop liquidator for bStock (ERC-8056 tokenized stock) collateral. In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken, redeems it to the raw bStock, and sells that bStock to the debt asset in one or two hops. Hop 1 always sells bStock through the Native RFQ router using a pre-fetched, MM-signed firm-quote `txRequest`. For USDT debt that single hop lands the debt asset directly. For non-USDT debt an OPTIONAL hop 2 (Native quotes bStock->USDT only) converts the USDT to the debt asset through a second allowlisted router (an AMM/aggregator). Because seize and sell happen in the same tx there is no price-drift window, and the realized debt-asset amount must clear `minOut` or the whole call reverts — the protocol never ends up holding the RFQ-only asset or the intermediate. Two funding modes share the same core (`_liquidate`): - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance. - FLASH: the debt asset is flash-borrowed from Venus (`Comptroller.executeFlashLoan`) and repaid (+ premium) within the same tx; no capital is locked in the contract. Native BNB debt (vBNB): supported in both modes with WBNB as the debt-accounting token. The repay must be native BNB, so exactly the repay amount of WBNB is unwrapped and forwarded to the gate's payable path; the two-hop swap lands WBNB (bStock->USDT->WBNB) and `minOut` is measured in WBNB (1:1 with BNB). FLASH mode borrows from vWBNB, NOT vBNB: vBNB cannot be flash-repaid (its `doTransferIn` requires `msg.value`), whereas vWBNB's underlying is a plain ERC20. Ownership / scope: this is Venus's OWN backstop tool, NOT a public utility — `liquidate` and `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus path with their own funds and their own offload. This contract is intentionally gated because it custodies funds (debt-asset inventory / flash principal) and forwards a CALLER-SUPPLIED calldata blob to an external router — the swap's recipient (`to`) lives inside that calldata, so an open entrypoint would let anyone route the proceeds to themselves and drain the contract. The router allowlist and `minOut` bound the blast radius but cannot replace operator-gating. Security model: - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator). - BOTH swap targets (`router` and, when set, `router2`) must be allowlisted (`isRouter`) — defends the low-level `router.call(swapCalldata)` on each hop. - the approval to each router is the exact amount being sold on that hop, reset to 0 afterwards (bStock on hop 1; the measured intermediate balance delta on hop 2, so pre-existing inventory is never exposed). - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started). - the realized debt-asset amount must clear `minOut` or the tx reverts. Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`, which is always configured on the networks this contract targets. While it is set, a direct `vToken.liquidateBorrow` from an arbitrary caller reverts UNAUTHORIZED, so this contract reads the gate at call time and routes the repay through that Venus Liquidator (the permissionless entry anyone may call), reverting if the gate is ever unset. Routing through the gate needs no governance change, and no other Core market is affected. Note: setting THIS contract as `liquidatorContract` is NOT an option — the gate is pool-wide, so every other market's liquidations would be forced through here.", + "notice": "Atomic backstop liquidator for bStock (ERC-8056 tokenized stock) collateral. In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken, redeems it to the raw bStock, and sells that bStock to the debt asset in one or two hops. Hop 1 sells bStock (to USDT) through an allowlisted RFQ router using a pre-fetched, off-chain-signed `swapCalldata` — Native firm-quote or Liquid Mesh (see `routerSpender`) or any future allowlisted source; the contract is router-agnostic and just forwards the opaque blob. For USDT debt that single hop lands the debt asset directly. For non-USDT debt an OPTIONAL hop 2 (RFQ sources quote bStock->USDT only) converts the USDT to the debt asset through a second allowlisted router (an AMM/aggregator). Because seize and sell happen in the same tx there is no price-drift window, and the realized debt-asset amount must clear `minOut` or the whole call reverts — the protocol never ends up holding the RFQ-only asset or the intermediate. Two funding modes share the same core (`_liquidate`): - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance. - FLASH: the debt asset is flash-borrowed from Venus (`Comptroller.executeFlashLoan`) and repaid (+ premium) within the same tx; no capital is locked in the contract. Native BNB debt (vBNB): supported in both modes with WBNB as the debt-accounting token. The repay must be native BNB, so exactly the repay amount of WBNB is unwrapped and forwarded to the gate's payable path; the two-hop swap lands WBNB (bStock->USDT->WBNB) and `minOut` is measured in WBNB (1:1 with BNB). FLASH mode borrows from vWBNB, NOT vBNB: vBNB cannot be flash-repaid (its `doTransferIn` requires `msg.value`), whereas vWBNB's underlying is a plain ERC20. VAI debt (VAIController): supported in INVENTORY mode only. VAI is not a vToken — a `vDebt` equal to `comptroller.vaiController()` is VAI, and like vBNB it has no `underlying()`, so the debt token is resolved via `getVAIAddress()`. The repay is a plain ERC20 approval to the gate, which takes its `_liquidateVAI` branch (pulls the VAI from us, then burns it via `VAIController.liquidateVAI`). Because RFQ sources quote bStock->USDT only, a VAI debt is inherently two-hop (bStock->USDT->VAI); hop 2 is expected to be the Peg Stability Module (`swapStableForVAI`, allowlisted as `router2`), which mints VAI from USDT at the oracle rate. FLASH mode is rejected (`FlashNotSupportedForVai`): `executeFlashLoan` lends a vToken's underlying, and VAI is minted/burned with no vVAI market. Ownership / scope: this is Venus's OWN backstop tool, NOT a public utility — `liquidate` and `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus path with their own funds and their own offload. This contract is intentionally gated because it custodies funds (debt-asset inventory / flash principal) and forwards a CALLER-SUPPLIED calldata blob to an external router — the swap's recipient (`to`) lives inside that calldata, so an open entrypoint would let anyone route the proceeds to themselves and drain the contract. The router allowlist and `minOut` bound the blast radius but cannot replace operator-gating. Security model: - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator). - BOTH swap targets (`router` and, when set, `router2`) must be allowlisted (`isRouter`) — defends the low-level `router.call(swapCalldata)` on each hop. - the approval for each hop is the exact amount being sold, granted to the router's configured spender (the router itself when unset — see `routerSpender`) and reset to 0 afterwards (bStock on hop 1; the measured intermediate balance delta on hop 2, so pre-existing inventory is never exposed). - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started). - the realized debt-asset amount must clear `minOut` or the tx reverts. Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`, which is always configured on the networks this contract targets. While it is set, a direct `vToken.liquidateBorrow` from an arbitrary caller reverts UNAUTHORIZED, so this contract reads the gate at call time and routes the repay through that Venus Liquidator (the permissionless entry anyone may call), reverting if the gate is ever unset. Routing through the gate needs no governance change, and no other Core market is affected. Note: setting THIS contract as `liquidatorContract` is NOT an option — the gate is pool-wide, so every other market's liquidations would be forced through here.", "version": 1 }, "storageLayout": { @@ -1121,7 +1249,7 @@ "type": "t_array(t_uint256)49_storage" }, { - "astId": 2833, + "astId": 2834, "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", "label": "isOperator", "offset": 0, @@ -1129,7 +1257,7 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 2838, + "astId": 2839, "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", "label": "isRouter", "offset": 0, @@ -1137,12 +1265,20 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 2843, + "astId": 2844, "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", - "label": "__gap", + "label": "routerSpender", "offset": 0, "slot": "203", - "type": "t_array(t_uint256)50_storage" + "type": "t_mapping(t_address,t_address)" + }, + { + "astId": 2849, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "__gap", + "offset": 0, + "slot": "204", + "type": "t_array(t_uint256)49_storage" } ], "types": { @@ -1168,6 +1304,13 @@ "label": "bool", "numberOfBytes": "1" }, + "t_mapping(t_address,t_address)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => address)", + "numberOfBytes": "32", + "value": "t_address" + }, "t_mapping(t_address,t_bool)": { "encoding": "mapping", "key": "t_address", diff --git a/deployments/bscmainnet/BStockLiquidator_Proxy.json b/deployments/bscmainnet/BStockLiquidator_Proxy.json index 8030cbf20..117f5a100 100644 --- a/deployments/bscmainnet/BStockLiquidator_Proxy.json +++ b/deployments/bscmainnet/BStockLiquidator_Proxy.json @@ -1,5 +1,5 @@ { - "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "abi": [ { "inputs": [ @@ -146,86 +146,86 @@ "type": "receive" } ], - "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", "receipt": { "to": null, "from": "0x8D43F1776B4d5eB69cDbE2e79899520754a0cd9A", - "contractAddress": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", - "transactionIndex": 41, - "gasUsed": "797587", - "logsBloom": "0x000000040000000000000000000000004020000000000000008000000000000000000000000000000000000000000800200000000000000000000000000000000000000000000000000000000000020000010000000000000000000000000000000000000200000000000010000008000000008000000000000000801000004000000000000080000000000000000000000000000000c0000000000000800000000000000000010000000000000400000000000000400000000000000000000000000220000000000000000000040000000000000400000000000000000020000000000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db", - "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", + "contractAddress": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "transactionIndex": 81, + "gasUsed": "797497", + "logsBloom": "0x000000040000000000000000000000004020000000000000008000000000000000000000040000000000000000000800200000000000000000000000000000000000000000000000000000000000020000010000000000000000000000000000000000000200000000000000000008000000008000000000000000001000004000000000000000000000000000000000400000000000c0000000000000800020000000000000000000000000000400000000000000000000000000000000000000000020000000000000000000040000000001000400000000000800000020002000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58", + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", "logs": [ { - "transactionIndex": 41, - "blockNumber": 107817335, - "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", - "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "topics": [ "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", - "0x000000000000000000000000883c36bade4babe393fef2fcfcfc24a5e8e1a3d5" + "0x000000000000000000000000de9734dd7aead610dd41ffe9abc25c5ccf142487" ], "data": "0x", - "logIndex": 131, - "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + "logIndex": 390, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" }, { - "transactionIndex": 41, - "blockNumber": 107817335, - "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", - "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "topics": [ "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000008d43f1776b4d5eb69cdbe2e79899520754a0cd9a" ], "data": "0x", - "logIndex": 132, - "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + "logIndex": 391, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" }, { - "transactionIndex": 41, - "blockNumber": 107817335, - "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", - "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "topics": [ "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", "0x0000000000000000000000008d43f1776b4d5eb69cdbe2e79899520754a0cd9a", "0x00000000000000000000000083f426233b358a36953f6951161e76fb7c866a7a" ], "data": "0x", - "logIndex": 133, - "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + "logIndex": 392, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" }, { - "transactionIndex": 41, - "blockNumber": 107817335, - "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", - "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], "data": "0x0000000000000000000000000000000000000000000000000000000000000001", - "logIndex": 134, - "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + "logIndex": 393, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" }, { - "transactionIndex": 41, - "blockNumber": 107817335, - "transactionHash": "0x5582e926e52d93aa47933914cb6c875bb65368416cbe29135d50891349899082", - "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "topics": ["0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f"], "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001bb765b741a5f3c2a338369dab539385534e3343", - "logIndex": 135, - "blockHash": "0x15997ab314e42864c0342ec3e35dfd1e61930c19e7058d2dff5e80d13e5bd8db" + "logIndex": 394, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" } ], - "blockNumber": 107817335, - "cumulativeGasUsed": "5080085", + "blockNumber": 111096720, + "cumulativeGasUsed": "15406242", "status": 1, "byzantium": true }, "args": [ - "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", + "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", "0x1BB765b741A5f3C2A338369DAb539385534E3343", "0xc4d66de800000000000000000000000083f426233b358a36953f6951161e76fb7c866a7a" ], diff --git a/deployments/bscmainnet/solcInputs/97b5bdb91b047141f90bacdfba4b885e.json b/deployments/bscmainnet/solcInputs/97b5bdb91b047141f90bacdfba4b885e.json new file mode 100644 index 000000000..9cf144188 --- /dev/null +++ b/deployments/bscmainnet/solcInputs/97b5bdb91b047141f90bacdfba4b885e.json @@ -0,0 +1,142 @@ +{ + "language": "Solidity", + "sources": { + "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./OwnableUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {\n address private _pendingOwner;\n\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n function __Ownable2Step_init() internal onlyInitializing {\n __Ownable_init_unchained();\n }\n\n function __Ownable2Step_init_unchained() internal onlyInitializing {\n }\n /**\n * @dev Returns the address of the pending owner.\n */\n function pendingOwner() public view virtual returns (address) {\n return _pendingOwner;\n }\n\n /**\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual override onlyOwner {\n _pendingOwner = newOwner;\n emit OwnershipTransferStarted(owner(), newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual override {\n delete _pendingOwner;\n super._transferOwnership(newOwner);\n }\n\n /**\n * @dev The new owner accepts the ownership transfer.\n */\n function acceptOwnership() public virtual {\n address sender = _msgSender();\n require(pendingOwner() == sender, \"Ownable2Step: caller is not the new owner\");\n _transferOwnership(sender);\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/ContextUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n function __Ownable_init() internal onlyInitializing {\n __Ownable_init_unchained();\n }\n\n function __Ownable_init_unchained() internal onlyInitializing {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)\n\npragma solidity ^0.8.2;\n\nimport \"../../utils/AddressUpgradeable.sol\";\n\n/**\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\n *\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\n * reused. This mechanism prevents re-execution of each \"step\" but allows the creation of new initialization steps in\n * case an upgrade adds a module that needs to be initialized.\n *\n * For example:\n *\n * [.hljs-theme-light.nopadding]\n * ```solidity\n * contract MyToken is ERC20Upgradeable {\n * function initialize() initializer public {\n * __ERC20_init(\"MyToken\", \"MTK\");\n * }\n * }\n *\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\n * function initializeV2() reinitializer(2) public {\n * __ERC20Permit_init(\"MyToken\");\n * }\n * }\n * ```\n *\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\n *\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\n *\n * [CAUTION]\n * ====\n * Avoid leaving a contract uninitialized.\n *\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * /// @custom:oz-upgrades-unsafe-allow constructor\n * constructor() {\n * _disableInitializers();\n * }\n * ```\n * ====\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n * @custom:oz-retyped-from bool\n */\n uint8 private _initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private _initializing;\n\n /**\n * @dev Triggered when the contract has been initialized or reinitialized.\n */\n event Initialized(uint8 version);\n\n /**\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\n * `onlyInitializing` functions can be used to initialize parent contracts.\n *\n * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a\n * constructor.\n *\n * Emits an {Initialized} event.\n */\n modifier initializer() {\n bool isTopLevelCall = !_initializing;\n require(\n (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),\n \"Initializable: contract is already initialized\"\n );\n _initialized = 1;\n if (isTopLevelCall) {\n _initializing = true;\n }\n _;\n if (isTopLevelCall) {\n _initializing = false;\n emit Initialized(1);\n }\n }\n\n /**\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\n * used to initialize parent contracts.\n *\n * A reinitializer may be used after the original initialization step. This is essential to configure modules that\n * are added through upgrades and that require initialization.\n *\n * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`\n * cannot be nested. If one is invoked in the context of another, execution will revert.\n *\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\n * a contract, executing them in the right order is up to the developer or operator.\n *\n * WARNING: setting the version to 255 will prevent any future reinitialization.\n *\n * Emits an {Initialized} event.\n */\n modifier reinitializer(uint8 version) {\n require(!_initializing && _initialized < version, \"Initializable: contract is already initialized\");\n _initialized = version;\n _initializing = true;\n _;\n _initializing = false;\n emit Initialized(version);\n }\n\n /**\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\n */\n modifier onlyInitializing() {\n require(_initializing, \"Initializable: contract is not initializing\");\n _;\n }\n\n /**\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\n * through proxies.\n *\n * Emits an {Initialized} event the first time it is successfully executed.\n */\n function _disableInitializers() internal virtual {\n require(!_initializing, \"Initializable: contract is initializing\");\n if (_initialized != type(uint8).max) {\n _initialized = type(uint8).max;\n emit Initialized(type(uint8).max);\n }\n }\n\n /**\n * @dev Returns the highest version that has been initialized. See {reinitializer}.\n */\n function _getInitializedVersion() internal view returns (uint8) {\n return _initialized;\n }\n\n /**\n * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.\n */\n function _isInitializing() internal view returns (bool) {\n return _initializing;\n }\n}\n" + }, + "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)\n\npragma solidity ^0.8.0;\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuardUpgradeable is Initializable {\n // Booleans are more expensive than uint256 or any type that takes up a full\n // word because each write operation emits an extra SLOAD to first read the\n // slot's contents, replace the bits taken up by the boolean, and then write\n // back. This is the compiler's defense against contract upgrades and\n // pointer aliasing, and it cannot be disabled.\n\n // The values being non-zero value makes deployment a bit more expensive,\n // but in exchange the refund on every call to nonReentrant will be lower in\n // amount. Since refunds are capped to a percentage of the total\n // transaction's gas, it is best to keep them low in cases like this one, to\n // increase the likelihood of the full refund coming into effect.\n uint256 private constant _NOT_ENTERED = 1;\n uint256 private constant _ENTERED = 2;\n\n uint256 private _status;\n\n function __ReentrancyGuard_init() internal onlyInitializing {\n __ReentrancyGuard_init_unchained();\n }\n\n function __ReentrancyGuard_init_unchained() internal onlyInitializing {\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and making it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n _nonReentrantBefore();\n _;\n _nonReentrantAfter();\n }\n\n function _nonReentrantBefore() private {\n // On the first call to nonReentrant, _status will be _NOT_ENTERED\n require(_status != _ENTERED, \"ReentrancyGuard: reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n _status = _ENTERED;\n }\n\n function _nonReentrantAfter() private {\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev Returns true if the reentrancy guard is currently set to \"entered\", which indicates there is a\n * `nonReentrant` function in the call stack.\n */\n function _reentrancyGuardEntered() internal view returns (bool) {\n return _status == _ENTERED;\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n *\n * ==== Security Considerations\n *\n * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature\n * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be\n * considered as an intention to spend the allowance in any specific way. The second is that because permits have\n * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should\n * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be\n * generally recommended is:\n *\n * ```solidity\n * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {\n * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}\n * doThing(..., value);\n * }\n *\n * function doThing(..., uint256 value) public {\n * token.safeTransferFrom(msg.sender, address(this), value);\n * ...\n * }\n * ```\n *\n * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of\n * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also\n * {SafeERC20-safeTransferFrom}).\n *\n * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so\n * contracts should have entry points that don't rely on permit.\n */\ninterface IERC20PermitUpgradeable {\n /**\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\n * given ``owner``'s signed approval.\n *\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\n * ordering also apply here.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `deadline` must be a timestamp in the future.\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\n * over the EIP712-formatted function arguments.\n * - the signature must use ``owner``'s current nonce (see {nonces}).\n *\n * For more information on the signature format, see the\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\n * section].\n *\n * CAUTION: See Security Considerations above.\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n /**\n * @dev Returns the current nonce for `owner`. This value must be\n * included whenever a signature is generated for {permit}.\n *\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\n * prevents a signature from being used multiple times.\n */\n function nonces(address owner) external view returns (uint256);\n\n /**\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n}\n" + }, + "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20Upgradeable {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20Upgradeable.sol\";\nimport \"../extensions/IERC20PermitUpgradeable.sol\";\nimport \"../../../utils/AddressUpgradeable.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20Upgradeable {\n using AddressUpgradeable for address;\n\n /**\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n */\n function safeTransfer(IERC20Upgradeable token, address to, uint256 value) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n /**\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\n */\n function safeTransferFrom(IERC20Upgradeable token, address from, address to, uint256 value) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(IERC20Upgradeable token, address spender, uint256 value) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n /**\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n */\n function safeIncreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {\n uint256 oldAllowance = token.allowance(address(this), spender);\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));\n }\n\n /**\n * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n */\n function safeDecreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));\n }\n }\n\n /**\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\n * to be set to zero before setting it to a non-zero value, such as USDT.\n */\n function forceApprove(IERC20Upgradeable token, address spender, uint256 value) internal {\n bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);\n\n if (!_callOptionalReturnBool(token, approvalCall)) {\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));\n _callOptionalReturn(token, approvalCall);\n }\n }\n\n /**\n * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.\n * Revert on invalid signature.\n */\n function safePermit(\n IERC20PermitUpgradeable token,\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal {\n uint256 nonceBefore = token.nonces(owner);\n token.permit(owner, spender, value, deadline, v, r, s);\n uint256 nonceAfter = token.nonces(owner);\n require(nonceAfter == nonceBefore + 1, \"SafeERC20: permit did not succeed\");\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20Upgradeable token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n require(returndata.length == 0 || abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n *\n * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.\n */\n function _callOptionalReturnBool(IERC20Upgradeable token, bytes memory data) private returns (bool) {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false\n // and not revert is the subcall reverts.\n\n (bool success, bytes memory returndata) = address(token).call(data);\n return\n success && (returndata.length == 0 || abi.decode(returndata, (bool))) && AddressUpgradeable.isContract(address(token));\n }\n}\n" + }, + "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary AddressUpgradeable {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n *\n * Furthermore, `isContract` will also return true if the target contract within\n * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,\n * which only has an effect at the end of a transaction.\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\n *\n * _Available since v4.8._\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n if (success) {\n if (returndata.length == 0) {\n // only check isContract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n require(isContract(target), \"Address: call to non-contract\");\n }\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason or using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n}\n" + }, + "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\n\npragma solidity ^0.8.0;\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract ContextUpgradeable is Initializable {\n function __Context_init() internal onlyInitializing {\n }\n\n function __Context_init_unchained() internal onlyInitializing {\n }\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[50] private __gap;\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC20.sol\";\nimport \"./extensions/IERC20Metadata.sol\";\nimport \"../../utils/Context.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC20\n * applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is Context, IERC20, IERC20Metadata {\n mapping(address => uint256) private _balances;\n\n mapping(address => mapping(address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * All two of these values are immutable: they can only be set once during\n * construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual override returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual override returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the default value returned by this function, unless\n * it's overridden.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual override returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address to, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `amount`.\n */\n function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, amount);\n _transfer(from, to, amount);\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, allowance(owner, spender) + addedValue);\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n address owner = _msgSender();\n uint256 currentAllowance = allowance(owner, spender);\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\n unchecked {\n _approve(owner, spender, currentAllowance - subtractedValue);\n }\n\n return true;\n }\n\n /**\n * @dev Moves `amount` of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n */\n function _transfer(address from, address to, uint256 amount) internal virtual {\n require(from != address(0), \"ERC20: transfer from the zero address\");\n require(to != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(from, to, amount);\n\n uint256 fromBalance = _balances[from];\n require(fromBalance >= amount, \"ERC20: transfer amount exceeds balance\");\n unchecked {\n _balances[from] = fromBalance - amount;\n // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by\n // decrementing then incrementing.\n _balances[to] += amount;\n }\n\n emit Transfer(from, to, amount);\n\n _afterTokenTransfer(from, to, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply += amount;\n unchecked {\n // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.\n _balances[account] += amount;\n }\n emit Transfer(address(0), account, amount);\n\n _afterTokenTransfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n uint256 accountBalance = _balances[account];\n require(accountBalance >= amount, \"ERC20: burn amount exceeds balance\");\n unchecked {\n _balances[account] = accountBalance - amount;\n // Overflow not possible: amount <= accountBalance <= totalSupply.\n _totalSupply -= amount;\n }\n\n emit Transfer(account, address(0), amount);\n\n _afterTokenTransfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(address owner, address spender, uint256 amount) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Updates `owner` s allowance for `spender` based on spent `amount`.\n *\n * Does not update the allowance amount in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Might emit an {Approval} event.\n */\n function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance != type(uint256).max) {\n require(currentAllowance >= amount, \"ERC20: insufficient allowance\");\n unchecked {\n _approve(owner, spender, currentAllowance - amount);\n }\n }\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}\n\n /**\n * @dev Hook that is called after any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * has been transferred to `to`.\n * - when `from` is zero, `amount` tokens have been minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC20 standard.\n *\n * _Available since v4.1._\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n" + }, + "@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nimport \"@openzeppelin/contracts/access/IAccessControl.sol\";\n\n/**\n * @title IAccessControlManagerV8\n * @author Venus\n * @notice Interface implemented by the `AccessControlManagerV8` contract.\n */\ninterface IAccessControlManagerV8 is IAccessControl {\n function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external;\n\n function revokeCallPermission(\n address contractAddress,\n string calldata functionSig,\n address accountToRevoke\n ) external;\n\n function isAllowedToCall(address account, string calldata functionSig) external view returns (bool);\n\n function hasPermission(\n address account,\n address contractAddress,\n string calldata functionSig\n ) external view returns (bool);\n}\n" + }, + "@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IDeviationBoundedOracle {\n // --- Enums ---\n\n /// @notice Identifies whether a price bound is a minimum or maximum\n enum PriceBoundType {\n MIN,\n MAX\n }\n\n /// @notice Identifies which keeper action a single syncPriceBoundsAndProtections item performs\n enum KeeperAction {\n SetMinPrice,\n SetMaxPrice,\n ExitProtectionMode\n }\n\n // --- Structs ---\n\n /// @notice Per-asset protection state tracking the min/max price window\n struct MarketProtectionState {\n /// @notice Lowest price observed in the current window (packed with maxPrice in one slot)\n uint128 minPrice;\n /// @notice Highest price observed in the current window\n uint128 maxPrice;\n /// @notice Whether protected price is currently being used\n bool currentlyUsingProtectedPrice;\n /// @notice Whether this market is whitelisted for bounded pricing\n bool isBoundedPricingEnabled;\n /// @notice Timestamp of the last protection trigger — reset on every trigger\n uint64 lastProtectionTriggeredAt;\n /// @notice Minimum time protection stays active after last trigger\n uint64 cooldownPeriod;\n /// @notice The underlying asset address, used to verify initialization\n address asset;\n /// @notice Entry deviation threshold (mantissa, e.g. 0.1667e18 = 16.67%); packed with resetThreshold\n uint128 triggerThreshold;\n /// @notice Exit threshold (mantissa); window must converge below this for protection to be disabled\n uint128 resetThreshold;\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\n bool cachingEnabled;\n }\n\n /// @notice One item in an syncPriceBoundsAndProtections payload\n /// @dev `value` is interpreted per-action: the new bound price for SetMinPrice / SetMaxPrice, ignored for ExitProtectionMode\n struct KeeperActionItem {\n address asset;\n KeeperAction action;\n uint256 value;\n }\n\n /// @notice One item in a setTokenConfigs payload\n struct TokenConfigInput {\n /// @notice The underlying asset address\n address asset;\n /// @notice Minimum time protection stays active after the last trigger (seconds)\n uint64 cooldownPeriod;\n /// @notice Entry deviation threshold (mantissa). Must be between 5% and 50%.\n uint256 triggerThreshold;\n /// @notice Exit deviation threshold (mantissa). Must be non-zero and below triggerThreshold.\n uint256 resetThreshold;\n /// @notice Whether to enable bounded pricing immediately upon initialization\n bool enableBoundedPricing;\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\n bool enableCaching;\n }\n\n // --- Events ---\n\n /// @notice Emitted when protection is initialized for an asset\n event ProtectionInitialized(\n address indexed asset,\n uint128 minPrice,\n uint128 maxPrice,\n uint64 cooldownPeriod,\n uint256 triggerThreshold\n );\n\n /// @notice Emitted when protection mode is triggered for an asset\n event ProtectionTriggered(address indexed asset, uint256 spotPrice, uint128 minPrice, uint128 maxPrice);\n\n /// @notice Emitted when protection mode is disabled for an asset\n event ProtectionModeExited(address indexed asset);\n\n /// @notice Emitted when the keeper updates the minimum price for an asset\n event MinPriceUpdated(address indexed asset, uint128 oldMin, uint128 newMin);\n\n /// @notice Emitted when the keeper updates the maximum price for an asset\n event MaxPriceUpdated(address indexed asset, uint128 oldMax, uint128 newMax);\n\n /// @notice Emitted when the entry threshold is updated for an asset\n event TriggerThresholdSet(address indexed asset, uint256 oldThreshold, uint256 newThreshold);\n\n /// @notice Emitted when the exit threshold is updated for an asset\n event ResetThresholdSet(address indexed asset, uint256 oldExitThreshold, uint256 newExitThreshold);\n\n /// @notice Emitted when the cooldown period is updated for an asset\n event CooldownPeriodSet(address indexed asset, uint64 oldCooldown, uint64 newCooldown);\n\n /// @notice Emitted when an asset's whitelist status changes\n event BoundedPricingWhitelistUpdated(address indexed asset, bool whitelisted);\n\n /// @notice Emitted when the per-asset transient caching flag is toggled\n event CachingEnabledUpdated(address indexed asset, bool oldEnabled, bool newEnabled);\n\n // --- Errors ---\n\n /// @notice Thrown when trying to use or update protection for an asset that has not been initialized\n error MarketNotInitialized(address asset);\n\n /// @notice Thrown when trying to initialize an already initialized market\n error MarketAlreadyInitialized(address asset);\n\n /// @notice Thrown when trying to disable protection that is not active\n error ProtectedPriceInactive(address asset);\n\n /// @notice Thrown when trying to disable protection before cooldown has elapsed\n error CooldownNotElapsed(address asset, uint64 lastProtectionTriggeredAt, uint64 cooldownPeriod);\n\n /// @notice Thrown when trying to disable protection before price range has converged\n error PriceRangeNotConverged(address asset, uint256 currentRangeRatio, uint256 resetThreshold);\n\n /// @notice Thrown when keeper tries to set minPrice above current spot\n error InvalidMinPrice(address asset, uint128 newMin, uint256 currentSpot);\n\n /// @notice Thrown when keeper tries to set maxPrice below current spot\n error InvalidMaxPrice(address asset, uint128 newMax, uint256 currentSpot);\n\n /// @notice Thrown when threshold is set below the minimum allowed value\n error ThresholdBelowMinimum(uint256 threshold, uint256 minimum);\n\n /// @notice Thrown when threshold is set above the maximum allowed value\n error ThresholdAboveMaximum(uint256 threshold, uint256 maximum);\n\n /// @notice Thrown when a price exceeds uint128 max\n error PriceExceedsUint128(uint256 price);\n\n /// @notice Thrown when a zero price is provided where a non-zero price is required\n error ZeroPriceNotAllowed();\n\n /// @notice Thrown when trying to initialize protection for VAI\n error VAINotAllowed();\n\n /// @notice Thrown when trying to disable bounded pricing for an asset while protection is active\n error ProtectedPriceActive(address asset);\n\n /// @notice Thrown when the lengths of the arrays are not equal\n error InvalidArrayLength();\n\n /// @notice Thrown when the exit threshold is set at or above the trigger threshold\n error InvalidResetThreshold(uint256 resetThreshold);\n\n /// @notice Thrown when an syncPriceBoundsAndProtections item carries an unsupported action enum value\n error InvalidKeeperAction(uint8 action);\n\n // --- Non-view price functions (update window + trigger protection) ---\n\n /**\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice);\n\n /**\n * @notice Gets the bounded debt price for a given vToken, updating protection state\n * @param vToken vToken address\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice);\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice);\n\n // --- State update (call before view price reads to populate transient cache) ---\n\n /**\n * @notice Updates the protection state for a given vToken, caching the resolved collateral and debt prices\n * @dev Called by PolicyFacet before liquidity calculations so subsequent view price\n * reads in the same transaction are served from transient storage. The transient\n * cache is only populated when the asset's `cachingEnabled` flag is `true`.\n * @param vToken vToken address\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function updateProtectionState(address vToken) external;\n\n // --- View price functions (read stored/cached state only) ---\n\n /**\n * @notice Gets the bounded collateral price for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\n * falls back to ResilientOracle on cache miss or when caching is disabled.\n * @param vToken vToken address\n * @return price The bounded collateral price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 price);\n\n /**\n * @notice Gets the bounded debt price for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\n * falls back to ResilientOracle on cache miss or when caching is disabled.\n * @param vToken vToken address\n * @return price The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 price);\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\n * falls back to ResilientOracle on cache miss or when caching is disabled.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice);\n\n // --- Keeper functions ---\n\n /**\n * @notice Updates the minimum price in the rolling window for a given asset\n * @param asset The underlying asset address\n * @param newMin The new minimum price; must be at or below the current spot and below maxPrice\n * @custom:access Only authorized keeper addresses\n * @custom:error ZeroPriceNotAllowed if newMin is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMinPrice if newMin exceeds the current spot or is at or above maxPrice\n * @custom:event MinPriceUpdated\n */\n function updateMinPrice(address asset, uint128 newMin) external;\n\n /**\n * @notice Updates the maximum price in the rolling window for a given asset\n * @param asset The underlying asset address\n * @param newMax The new maximum price; must be at or above the current spot and above minPrice\n * @custom:access Only authorized keeper addresses\n * @custom:error ZeroPriceNotAllowed if newMax is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMaxPrice if newMax is below the current spot or is at or below minPrice\n * @custom:event MaxPriceUpdated\n */\n function updateMaxPrice(address asset, uint128 newMax) external;\n\n /**\n * @notice Exits protection mode for a given asset once conditions are met\n * @param asset The underlying asset address\n * @custom:access Only authorized monitor/keeper addresses\n * @custom:error ProtectedPriceInactive if protection is not currently active\n * @custom:error CooldownNotElapsed if the cooldown period has not elapsed since the last trigger\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\n * @custom:event ProtectionModeExited\n */\n function exitProtectionMode(address asset) external;\n\n /**\n * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\n * @dev Each item is processed in array order; any item revert rolls back the whole batch.\n * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode.\n * Empty `actions` is a no-op success.\n * @param actions The list of keeper actions to apply\n * @custom:access Only authorized keeper addresses\n * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value\n * @custom:error PriceExceedsUint128 if a SetMin/SetMax item value overflows uint128\n * @custom:error ZeroPriceNotAllowed if a SetMin/SetMax item value is zero\n * @custom:error MarketNotInitialized if any referenced asset has not been initialized\n * @custom:error InvalidMinPrice if a SetMinPrice item violates the spot/maxPrice constraints\n * @custom:error InvalidMaxPrice if a SetMaxPrice item violates the spot/minPrice constraints\n * @custom:error ProtectedPriceInactive if an ExitProtectionMode item targets an asset whose protection is not active\n * @custom:error CooldownNotElapsed if an ExitProtectionMode item is submitted before cooldown elapsed\n * @custom:error PriceRangeNotConverged if an ExitProtectionMode item is submitted before window convergence\n * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\n */\n function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external;\n\n // --- Admin functions (governance-gated) ---\n\n /**\n * @notice Initializes protection parameters for a new asset\n * @dev Seeds the initial min/max window from the current ResilientOracle spot price,\n * confirming the oracle is live for this asset before it is listed.\n * @param tokenConfig_ Token config input for the asset\n * @custom:access Only Governance\n * @custom:error ZeroAddressNotAllowed if asset is the zero address\n * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\n * @custom:error VAINotAllowed if asset is the VAI token\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event ProtectionInitialized\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setTokenConfig(TokenConfigInput calldata tokenConfig_) external;\n\n /**\n * @notice Batch-initializes protection parameters for multiple assets in a single transaction\n * @param tokenConfigs_ Array of token config inputs, one per asset\n * @custom:access Only Governance\n * @custom:error InvalidArrayLength if the input array is empty\n * @custom:error ZeroAddressNotAllowed if any asset is the zero address\n * @custom:error ZeroValueNotAllowed if any cooldownPeriod, triggerThreshold, or resetThreshold is zero\n * @custom:error MarketAlreadyInitialized if any asset has already been initialized\n * @custom:error ThresholdBelowMinimum if any triggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if any triggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if any resetThreshold is at or above its triggerThreshold\n * @custom:error VAINotAllowed if any asset is the VAI token\n * @custom:error PriceExceedsUint128 if the spot price for any asset overflows uint128\n * @custom:event ProtectionInitialized for each asset\n * @custom:event BoundedPricingWhitelistUpdated for each asset\n */\n function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external;\n\n /**\n * @notice Sets the cooldown period for an asset\n * @param asset The underlying asset address\n * @param newCooldown The new cooldown period in seconds; must be non-zero\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:event CooldownPeriodSet\n */\n function setCooldownPeriod(address asset, uint64 newCooldown) external;\n\n /**\n * @notice Sets the trigger and reset thresholds for an asset\n * @param asset The underlying asset address\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\n * @custom:event TriggerThresholdSet if the trigger threshold changed\n * @custom:event ResetThresholdSet if the reset threshold changed\n */\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external;\n\n /**\n * @notice Sets whether bounded pricing is enabled for an asset\n * @param asset The underlying asset address\n * @param enabled Whether bounded pricing should be enabled for the asset\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external;\n\n /**\n * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset\n * @dev When disabled, each view/non-view price call recomputes bounded prices from the\n * live spot instead of reading or writing the transient slots. The initial value is\n * set via the `enableCaching` argument of `setTokenConfig`.\n * @param asset The underlying asset address\n * @param enabled Whether transient caching is enabled for this asset\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:event CachingEnabledUpdated\n */\n function setCachingEnabled(address asset, bool enabled) external;\n\n // --- View helpers ---\n\n /**\n * @notice Returns the full protection state for an asset\n * @param asset The underlying asset address\n * @return minPrice Lowest price observed in the current window\n * @return maxPrice Highest price observed in the current window\n * @return currentlyUsingProtectedPrice Whether protected price is currently active\n * @return isBoundedPricingEnabled Whether the asset is whitelisted for bounded pricing\n * @return lastProtectionTriggeredAt Timestamp of the last protection trigger\n * @return cooldownPeriod Minimum time protection stays active after last trigger\n * @return assetAddr The underlying asset address stored in the struct\n * @return triggerThreshold Entry deviation threshold (mantissa) that activates protection\n * @return resetThreshold Exit deviation threshold (mantissa) below which protection can be disabled\n * @return cachingEnabled Whether transient caching of the bounded pair is enabled for the asset\n */\n function assetProtectionConfig(\n address asset\n )\n external\n view\n returns (\n uint128 minPrice,\n uint128 maxPrice,\n bool currentlyUsingProtectedPrice,\n bool isBoundedPricingEnabled,\n uint64 lastProtectionTriggeredAt,\n uint64 cooldownPeriod,\n address assetAddr,\n uint128 triggerThreshold,\n uint128 resetThreshold,\n bool cachingEnabled\n );\n\n /**\n * @notice Checks if an asset is whitelisted for bounded pricing\n * @param asset The underlying asset address\n * @return True if the asset is whitelisted\n */\n function isBoundedPricingEnabled(address asset) external view returns (bool);\n\n /**\n * @notice Checks if the asset is currently using the protected (bounded) price\n * @param asset The underlying asset address\n * @return True if the asset is currently using the protected price instead of spot\n */\n function currentlyUsingProtectedPrice(address asset) external view returns (bool);\n\n /**\n * @notice Checks if protection can be exited for a given asset\n * @param asset The underlying asset address\n * @return True if both the cooldown has elapsed and the price range has converged below the exit threshold\n */\n function canExitProtection(address asset) external view returns (bool);\n\n /**\n * @notice Returns the initialized asset at the given index (auto-generated array getter)\n * @param index Array index\n * @return The asset address at the given index\n */\n function allAssets(uint256 index) external view returns (address);\n\n /**\n * @notice Returns all currently whitelisted asset addresses\n * @return result Array of whitelisted asset addresses\n */\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory result);\n\n /**\n * @notice Returns all asset addresses that have ever been initialized\n * @return Array of all initialized asset addresses\n */\n function getInitializedAssets() external view returns (address[] memory);\n\n /**\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the keeper deadband\n * @param assets Array of asset addresses to check\n * @param proposedMins Keeper's proposed window minimum prices\n * @param proposedMaxs Keeper's proposed window maximum prices\n * @return needsMinUpdate Whether minPrice drift exceeds the deadband for each asset\n * @return needsMaxUpdate Whether maxPrice drift exceeds the deadband for each asset\n * @custom:error InvalidArrayLength if the input array lengths do not match\n */\n function checkAndGetWindowDrift(\n address[] calldata assets,\n uint128[] calldata proposedMins,\n uint128[] calldata proposedMaxs\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate);\n}\n" + }, + "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface OracleInterface {\n function getPrice(address asset) external view returns (uint256);\n}\n\ninterface ResilientOracleInterface is OracleInterface {\n function updatePrice(address vToken) external;\n\n function updateAssetPrice(address asset) external;\n\n function getUnderlyingPrice(address vToken) external view returns (uint256);\n}\n\ninterface BoundValidatorInterface {\n function validatePriceWithAnchorPrice(\n address asset,\n uint256 reporterPrice,\n uint256 anchorPrice\n ) external view returns (bool);\n}\n" + }, + "@venusprotocol/solidity-utilities/contracts/constants.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\n/// @dev Base unit for computations, usually used in scaling (multiplications, divisions)\nuint256 constant EXP_SCALE = 1e18;\n\n/// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions\nuint256 constant MANTISSA_ONE = EXP_SCALE;\n\n/// @dev The approximate number of seconds per year\nuint256 constant SECONDS_PER_YEAR = 31_536_000;\n" + }, + "@venusprotocol/solidity-utilities/contracts/validators.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n/// @notice Thrown if the supplied address is a zero address where it is not allowed\nerror ZeroAddressNotAllowed();\n\n/// @notice Thrown if the supplied value is 0 where it is not allowed\nerror ZeroValueNotAllowed();\n\n/// @notice Checks if the provided address is nonzero, reverts otherwise\n/// @param address_ Address to check\n/// @custom:error ZeroAddressNotAllowed is thrown if the provided address is a zero address\nfunction ensureNonzeroAddress(address address_) pure {\n if (address_ == address(0)) {\n revert ZeroAddressNotAllowed();\n }\n}\n\n/// @notice Checks if the provided value is nonzero, reverts otherwise\n/// @param value_ Value to check\n/// @custom:error ZeroValueNotAllowed is thrown if the provided value is 0\nfunction ensureNonzeroValue(uint256 value_) pure {\n if (value_ == 0) {\n revert ZeroValueNotAllowed();\n }\n}\n" + }, + "contracts/BStock/BStockLiquidator.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IERC20Upgradeable } from \"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\";\nimport { SafeERC20Upgradeable } from \"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol\";\nimport { Ownable2StepUpgradeable } from \"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\";\nimport { ReentrancyGuardUpgradeable } from \"@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\n\nimport { VToken } from \"../Tokens/VTokens/VToken.sol\";\nimport { IFlashLoanReceiver } from \"../FlashLoan/interfaces/IFlashLoanReceiver.sol\";\nimport { IWBNB } from \"../external/IWBNB.sol\";\n\n// Shared Venus interfaces: IComptroller (Core diamond — liquidator gate + flash loan), IVToken\n// (flash-loan asset array element), and ILiquidator (the pool-wide Venus Liquidator gate that pulls\n// the repay and returns our share of the seized collateral).\nimport { IComptroller, IVToken, ILiquidator, IVAIController } from \"../InterfacesV8.sol\";\nimport { IBStockLiquidator } from \"./IBStockLiquidator.sol\";\n\n/**\n * @title BStockLiquidator\n * @author Venus\n * @notice Atomic backstop liquidator for bStock (ERC-8056 tokenized stock) collateral.\n *\n * In ONE transaction it repays an undercollateralized borrow, seizes the bStock vToken,\n * redeems it to the raw bStock, and sells that bStock to the debt asset in one or two hops. Hop 1\n * sells bStock (to USDT) through an allowlisted RFQ router using a pre-fetched, off-chain-signed\n * `swapCalldata` — Native firm-quote or Liquid Mesh (see `routerSpender`) or any future allowlisted\n * source; the contract is router-agnostic and just forwards the opaque blob. For USDT debt that single\n * hop lands the debt asset directly. For non-USDT debt an OPTIONAL hop 2 (RFQ sources quote bStock->USDT\n * only) converts the USDT to the debt asset through a second allowlisted router (an AMM/aggregator).\n * Because seize and sell happen in the same tx there is no price-drift window, and the realized\n * debt-asset amount must clear `minOut` or the whole call reverts — the protocol never ends up holding\n * the RFQ-only asset or the intermediate.\n *\n * Two funding modes share the same core (`_liquidate`):\n * - INVENTORY: the contract is pre-funded with the debt asset and repays from its own balance.\n * - FLASH: the debt asset is flash-borrowed from Venus (`Comptroller.executeFlashLoan`) and repaid\n * (+ premium) within the same tx; no capital is locked in the contract.\n *\n * Native BNB debt (vBNB): supported in both modes with WBNB as the debt-accounting token. The repay\n * must be native BNB, so exactly the repay amount of WBNB is unwrapped and forwarded to the gate's\n * payable path; the two-hop swap lands WBNB (bStock->USDT->WBNB) and `minOut` is measured in WBNB\n * (1:1 with BNB). FLASH mode borrows from vWBNB, NOT vBNB: vBNB cannot be flash-repaid (its\n * `doTransferIn` requires `msg.value`), whereas vWBNB's underlying is a plain ERC20.\n *\n * VAI debt (VAIController): supported in INVENTORY mode only. VAI is not a vToken — a `vDebt` equal to\n * `comptroller.vaiController()` is VAI, and like vBNB it has no `underlying()`, so the debt token is\n * resolved via `getVAIAddress()`. The repay is a plain ERC20 approval to the gate, which takes its\n * `_liquidateVAI` branch (pulls the VAI from us, then burns it via `VAIController.liquidateVAI`).\n * Because RFQ sources quote bStock->USDT only, a VAI debt is inherently two-hop (bStock->USDT->VAI);\n * hop 2 is expected to be the Peg Stability Module (`swapStableForVAI`, allowlisted as `router2`),\n * which mints VAI from USDT at the oracle rate. FLASH mode is rejected (`FlashNotSupportedForVai`):\n * `executeFlashLoan` lends a vToken's underlying, and VAI is minted/burned with no vVAI market.\n *\n * Ownership / scope: this is Venus's OWN backstop tool, NOT a public utility — `liquidate` and\n * `flashLiquidate` are operator-only (owner + allowlisted operators). It does not make bStock\n * liquidation exclusive: anyone may still liquidate bStock through the normal permissionless Venus\n * path with their own funds and their own offload. This contract is intentionally gated because it\n * custodies funds (debt-asset inventory / flash principal) and forwards a CALLER-SUPPLIED calldata blob to\n * an external router — the swap's recipient (`to`) lives inside that calldata, so an open entrypoint\n * would let anyone route the proceeds to themselves and drain the contract. The router allowlist and\n * `minOut` bound the blast radius but cannot replace operator-gating.\n *\n * Security model:\n * - `liquidate` / `flashLiquidate` are `onlyOperator` (owner or allowlisted operator).\n * - BOTH swap targets (`router` and, when set, `router2`) must be allowlisted (`isRouter`) — defends\n * the low-level `router.call(swapCalldata)` on each hop.\n * - the approval for each hop is the exact amount being sold, granted to the router's configured spender\n * (the router itself when unset — see `routerSpender`) and reset to 0 afterwards (bStock on hop 1; the\n * measured intermediate balance delta on hop 2, so pre-existing inventory is never exposed).\n * - `executeOperation` accepts calls only from the Comptroller with `initiator == this` (i.e. a flash we started).\n * - the realized debt-asset amount must clear `minOut` or the tx reverts.\n *\n * Core liquidation gate (handled automatically): Core has a POOL-WIDE `Comptroller.liquidatorContract`,\n * which is always configured on the networks this contract targets. While it is set, a direct\n * `vToken.liquidateBorrow` from an arbitrary caller reverts UNAUTHORIZED, so this contract reads the gate\n * at call time and routes the repay through that Venus Liquidator (the permissionless entry anyone may\n * call), reverting if the gate is ever unset. Routing through the gate needs no governance change, and no\n * other Core market is affected. Note: setting THIS contract as `liquidatorContract` is NOT an option —\n * the gate is pool-wide, so every other market's liquidations would be forced through here.\n */\ncontract BStockLiquidator is\n Ownable2StepUpgradeable,\n ReentrancyGuardUpgradeable,\n IBStockLiquidator,\n IFlashLoanReceiver\n{\n using SafeERC20Upgradeable for IERC20Upgradeable;\n\n /// @notice Core Comptroller (diamond): reads the liquidation gate and provides the flash loan\n /// via `executeFlashLoan`.\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n IComptroller public immutable comptroller;\n\n /// @notice The native BNB market (vBNB). A debt equal to this address is settled with native BNB:\n /// WBNB is the debt-accounting token, and only the repay amount is unwrapped (see `_liquidate`).\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable vBNB;\n\n /// @notice The WBNB market (vWBNB). BNB debt is flash-funded from here, NOT from vBNB: vBNB cannot be\n /// flash-repaid (its `doTransferIn` needs `msg.value`), whereas vWBNB's underlying is a plain\n /// ERC20 repaid via `transferFrom`.\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n IVToken public immutable vWBNB;\n\n /// @notice WBNB token: the debt-accounting asset for BNB debt, unwrapped to native BNB for the repay.\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n IWBNB public immutable wbnb;\n\n /// @notice Addresses allowed to trigger a liquidation.\n mapping(address => bool) public isOperator;\n\n /// @notice Routers allowed as the swap target (defends the low-level call).\n mapping(address => bool) public isRouter;\n\n /// @notice Optional token-approval target (spender) per router, for aggregators whose settlement\n /// contract that pulls the input token differs from the call target (e.g. Liquid Mesh, where\n /// the router is the call target but a separate spender pulls the token). When unset\n /// (address(0)), the approval defaults to the router itself — the Native behaviour, where the\n /// call target IS the puller — so existing routers need no spender entry.\n mapping(address => address) public routerSpender;\n\n /// @dev Reserved storage to allow new state variables in future upgrades without layout clashes.\n uint256[49] private __gap;\n\n modifier onlyOperator() {\n if (msg.sender != owner() && !isOperator[msg.sender]) revert NotOperator();\n _;\n }\n\n /// @notice Constructor for the implementation contract. Sets the immutables and locks initializers.\n /// @param comptroller_ Venus Core Comptroller (diamond) — gates liquidation and provides flash loans.\n /// @param vBNB_ Native BNB market; a debt equal to this address is settled in native BNB.\n /// @param vWBNB_ WBNB market; the flash-borrow source for BNB debt (vBNB itself cannot be flash-repaid).\n /// @param wbnb_ WBNB token; the debt-accounting asset for BNB debt, unwrapped for the native repay.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(IComptroller comptroller_, address vBNB_, IVToken vWBNB_, IWBNB wbnb_) {\n ensureNonzeroAddress(address(comptroller_));\n ensureNonzeroAddress(vBNB_);\n ensureNonzeroAddress(address(vWBNB_));\n ensureNonzeroAddress(address(wbnb_));\n comptroller = comptroller_;\n vBNB = vBNB_;\n vWBNB = vWBNB_;\n wbnb = wbnb_;\n _disableInitializers();\n }\n\n /// @notice Initializes the proxy: sets the owner and the reentrancy guard.\n /// @param initialOwner Address that owns the contract (admin + default operator).\n function initialize(address initialOwner) external initializer {\n ensureNonzeroAddress(initialOwner);\n __Ownable2Step_init();\n __ReentrancyGuard_init();\n _transferOwnership(initialOwner);\n }\n\n /// @notice Accept native BNB. The expected inflow is `wbnb.withdraw` during a BNB liquidation (the\n /// unwrapped repay is forwarded to the gate in the same call, so no BNB is retained on the\n /// happy path). Left permissive (not restricted to `wbnb`) both to keep the receive body\n /// minimal for WBNB's 2300-gas `.transfer` stipend and to tolerate a stray transfer or a\n /// future gate refund — any such balance is recoverable via `sweepNative`.\n receive() external payable {}\n\n // --------------------------------------------------------------------- //\n // Admin //\n // --------------------------------------------------------------------- //\n\n /// @inheritdoc IBStockLiquidator\n function setOperator(address operator, bool allowed) external override onlyOwner {\n ensureNonzeroAddress(operator);\n isOperator[operator] = allowed;\n emit OperatorSet(operator, allowed);\n }\n\n /// @inheritdoc IBStockLiquidator\n function setRouter(address router, bool allowed) external override onlyOwner {\n ensureNonzeroAddress(router);\n isRouter[router] = allowed;\n // De-allowlisting also clears any configured spender: a stale entry must not silently\n // reactivate (with a possibly rotated-away spender) if the router is ever re-allowlisted.\n if (!allowed && routerSpender[router] != address(0)) {\n delete routerSpender[router];\n emit RouterSpenderSet(router, address(0));\n }\n emit RouterSet(router, allowed);\n }\n\n /// @inheritdoc IBStockLiquidator\n function setRouterSpender(address router, address spender) external override onlyOwner {\n // Couple the spender lifecycle to the allowlist: a spender only ever matters for an\n // allowlisted router (`_swap` approves it right before the router call), so requiring\n // `isRouter` here catches a fat-fingered router address instead of storing it silently.\n if (!isRouter[router]) revert RouterNotAllowed(router);\n // `spender == address(0)` is allowed and clears the entry, reverting the router to\n // approve-the-call-target (Native) behaviour. A non-zero spender receives a live (exact-amount,\n // same-tx) approval during `_swap`, so require it to be a deployed contract — an EOA spender is\n // always a misconfiguration.\n if (spender != address(0) && spender.code.length == 0) revert SpenderNotContract(spender);\n routerSpender[router] = spender;\n emit RouterSpenderSet(router, spender);\n }\n\n /// @inheritdoc IBStockLiquidator\n function sweep(address token, address to, uint256 amount) external override onlyOwner {\n ensureNonzeroAddress(token);\n ensureNonzeroAddress(to);\n IERC20Upgradeable(token).safeTransfer(to, amount);\n emit Swept(token, to, amount);\n }\n\n /// @inheritdoc IBStockLiquidator\n /// @dev `nonReentrant` is defense-in-depth only (the function is `onlyOwner`, snapshots no state, and\n /// reads no balance after the native `.call`), added for consistency with the liquidation entrypoints.\n function sweepNative(address to, uint256 amount) external override onlyOwner nonReentrant {\n ensureNonzeroAddress(to);\n (bool ok, ) = to.call{ value: amount }(\"\");\n if (!ok) revert NativeTransferFailed();\n emit SweptNative(to, amount);\n }\n\n /// @notice Disabled. This backstop custodies protocol capital (debt-asset inventory, native BNB) and\n /// every admin function (`sweep`, `sweepNative`, `setOperator`, `setRouter`) is `onlyOwner`,\n /// so renouncing ownership would permanently strand those funds and brick the contract. The\n /// override is an `onlyOwner` no-op: an accidental owner call cannot zero the owner, and a\n /// non-owner call reverts. Ownership is still transferable via the two-step `transferOwnership` flow.\n function renounceOwnership() public override onlyOwner {}\n\n // --------------------------------------------------------------------- //\n // INVENTORY mode //\n // --------------------------------------------------------------------- //\n\n /// @inheritdoc IBStockLiquidator\n /// @dev INVENTORY mode spends the contract's OWN debt-asset capital, so — unlike FLASH mode, where\n /// `executeOperation` forces the swap proceeds to cover principal + premium — there is no\n /// built-in floor tying `debtOut` to `repayAmount`. This asymmetry is intentional: a repay can\n /// legitimately out-cost its proceeds (e.g. the Venus Liquidator keeps a treasury cut of the\n /// seized collateral, so proceeds land a few % under the repay). `minOut` IS the operator's\n /// chosen loss floor for inventory mode — set it to the lowest acceptable debt-asset return.\n function liquidate(\n LiquidationParams calldata params\n ) external override onlyOperator nonReentrant returns (uint256 debtOut) {\n _validateRouters(params.router, params.router2);\n\n if (params.minOut == 0) revert ZeroMinOut();\n if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp);\n uint256 seizedBStock;\n (debtOut, seizedBStock) = _liquidate(params);\n emit Liquidated(\n params.borrower,\n address(params.vBStock),\n address(params.vDebt),\n params.repayAmount,\n seizedBStock,\n debtOut,\n false\n );\n }\n\n // --------------------------------------------------------------------- //\n // FLASH mode //\n // --------------------------------------------------------------------- //\n\n /// @inheritdoc IBStockLiquidator\n function flashLiquidate(LiquidationParams calldata params) external override onlyOperator nonReentrant {\n _validateRouters(params.router, params.router2);\n\n if (params.minOut == 0) revert ZeroMinOut();\n if (block.timestamp > params.deadline) revert DeadlineExpired(params.deadline, block.timestamp);\n\n // VAI has no market to flash from: `executeFlashLoan` lends a vToken's underlying, whereas VAI is\n // MINTED/BURNED by the VAIController (`repayVAIFresh` burns it) and has no vVAI. Reject up front\n // instead of passing the VAIController into `executeFlashLoan` and failing opaquely. Use\n // `liquidate` (INVENTORY) with pre-funded VAI for a VAI debt.\n if (address(params.vDebt) == address(comptroller.vaiController())) revert FlashNotSupportedForVai();\n\n // BNB debt is flash-funded from vWBNB (an ERC20 market), not vBNB: vBNB cannot be flash-repaid.\n // The flashed WBNB is unwrapped to native BNB for the repay inside `_liquidate` (see `executeOperation`).\n IVToken[] memory vTokens = new IVToken[](1);\n vTokens[0] = (address(params.vDebt) == vBNB) ? vWBNB : IVToken(address(params.vDebt));\n uint256[] memory amounts = new uint256[](1);\n amounts[0] = params.repayAmount;\n\n comptroller.executeFlashLoan(\n payable(address(this)),\n payable(address(this)),\n vTokens,\n amounts,\n abi.encode(params)\n );\n }\n\n /// @inheritdoc IFlashLoanReceiver\n function executeOperation(\n VToken[] calldata vTokens,\n uint256[] calldata amounts,\n uint256[] calldata premiums,\n address initiator,\n address /* onBehalf */,\n bytes calldata param\n ) external override returns (bool, uint256[] memory repayAmounts) {\n if (msg.sender != address(comptroller)) revert OnlyComptroller();\n // initiator == this proves the flash was started by our own flashLiquidate: the FlashLoanFacet\n // passes msg.sender (the executeFlashLoan caller) as `initiator`, and only flashLiquidate calls it.\n if (initiator != address(this)) revert BadInitiator(initiator);\n\n LiquidationParams memory params = abi.decode(param, (LiquidationParams));\n // For BNB debt the flash is drawn from vWBNB, not vBNB (see `flashLiquidate`). Scoped so the\n // temporary doesn't count against the stack depth of the rest of the function.\n {\n address expectedFlash = address(params.vDebt) == vBNB ? address(vWBNB) : address(params.vDebt);\n if (address(vTokens[0]) != expectedFlash) revert WrongFlashAsset();\n }\n\n // Repay was just funded by the flash loan; run the liquidation + swap.\n (uint256 debtOut, uint256 seizedBStock) = _liquidate(params);\n\n // The swap proceeds alone MUST cover principal + premium. Without this, any debt-asset inventory\n // held by the contract would silently backfill an underwater swap (a real loss), since the\n // flash repayment is pulled from the total balance, not just the swap output.\n repayAmounts = new uint256[](1);\n repayAmounts[0] = amounts[0] + premiums[0];\n if (debtOut < repayAmounts[0]) revert InsufficientOut(debtOut, repayAmounts[0]);\n\n // Approve the flashed vToken to pull back principal + premium. For BNB debt the flashed asset is\n // WBNB (from vWBNB); the ternary short-circuits so `underlying()` is never called on vBNB (it has none).\n IERC20Upgradeable(address(params.vDebt) == vBNB ? address(wbnb) : params.vDebt.underlying()).forceApprove(\n address(vTokens[0]),\n repayAmounts[0]\n );\n\n emit Liquidated(\n params.borrower,\n address(params.vBStock),\n address(params.vDebt),\n params.repayAmount,\n seizedBStock,\n debtOut,\n true\n );\n return (true, repayAmounts);\n }\n\n // --------------------------------------------------------------------- //\n // Core //\n // --------------------------------------------------------------------- //\n\n /// @dev Pre-flight: every swap router must be allowlisted. `router2` is optional (single-hop when\n /// zero) so it is only checked when set. Liquidatability itself is not pre-checked here — Core's\n /// `liquidateBorrowAllowed` already enforces it, and pre-checking shortfall would wrongly block\n /// forced liquidations (which liquidate healthy accounts).\n function _validateRouters(address router, address router2) private view {\n if (!isRouter[router]) revert RouterNotAllowed(router);\n if (router2 != address(0) && !isRouter[router2]) revert RouterNotAllowed(router2);\n }\n\n /// @dev One swap hop: approve the exact `amount` to the router's configured spender, forward the\n /// opaque calldata via a low-level call to the allowlisted `router`, then reset the approval to\n /// 0. The spender defaults to the router itself when unset (Native, where the call target is the\n /// puller); aggregators with a separate settlement/pull contract (e.g. Liquid Mesh) set it via\n /// `setRouterSpender`. The approval caps what the spender can pull; if the calldata sells less\n /// (e.g. a partially-filled RFQ quote), the unconsumed remainder stays in the contract and is\n /// surfaced via `PartialSwapLeftover` so operations can recover it with `sweep` — `minOut` still\n /// bounds the realized debt-asset proceeds regardless.\n function _swap(IERC20Upgradeable token, address router, bytes memory data, uint256 amount) private {\n uint256 balBefore = token.balanceOf(address(this));\n // Approve the PULLER, which is not always the call target: Liquid Mesh and similar split-settlement\n // aggregators pull through a separate spender. Defaults to the router when unset, so Native (whose\n // call target is the puller) is unaffected.\n address spender = routerSpender[router];\n if (spender == address(0)) spender = router;\n token.forceApprove(spender, amount);\n (bool ok, bytes memory returndata) = router.call(data);\n if (!ok) {\n // Bubble up the router's own revert reason for easier debugging; fall back to SwapFailed()\n // only when the call reverted without returndata.\n if (returndata.length != 0) {\n assembly {\n revert(add(returndata, 0x20), mload(returndata))\n }\n }\n revert SwapFailed();\n }\n token.forceApprove(spender, 0); // never leave a standing approval\n // `token` is the hop's INPUT: the spender can pull at most `amount` (the approval, just reset),\n // and any refund is a subset of what it pulled, so the balance can only fall (balAfter <=\n // balBefore) — the subtraction cannot underflow. A shortfall (spent < amount) means the router\n // filled less than approved; emit the residual so it can be swept.\n uint256 spent = balBefore - token.balanceOf(address(this));\n if (spent < amount) emit PartialSwapLeftover(address(token), amount - spent);\n }\n\n /**\n * @dev The atomic sequence shared by both funding modes:\n * approve debt -> liquidateBorrow (seize vBStock) -> redeem (raw bStock)\n * -> swap bStock to the debt asset (one hop, or two via an intermediate) -> assert minOut.\n * A `calldata` struct from `liquidate` is copied to memory on entry; `executeOperation`\n * already holds it in memory (decoded from the flash callback).\n * @param params Liquidation parameters.\n * @return debtOut Debt-asset proceeds realized by the swap chain (reverts if below `minOut`).\n * @return seizedBStock Raw bStock redeemed and sold (balance delta).\n */\n function _liquidate(LiquidationParams memory params) private returns (uint256 debtOut, uint256 seizedBStock) {\n // A debt equal to `vBNB` is native BNB. vBNB has no `underlying()`, so the `isBnb` check MUST come\n // first: the ternary short-circuits and `underlying()` is never evaluated for vBNB. WBNB is the\n // debt-accounting token throughout (1:1 with BNB), so the whole swap/minOut path below is reused.\n // KEEP THIS ORDER — hoisting `underlying()` above the check reverts every BNB liquidation.\n bool isBnb = address(params.vDebt) == vBNB;\n IERC20Upgradeable debt;\n // Scoped so `vaiCtrl`/`isVai` don't hold stack slots for the rest of the frame.\n {\n // A debt equal to the VAIController is VAI: it is not a vToken and has no `underlying()`\n // either, so — like vBNB — the check MUST come before any `underlying()` evaluation. The gate\n // takes its VAI branch (`Liquidator._liquidateVAI`), which pulls VAI from us and burns it via\n // `VAIController.liquidateVAI`; the repay is a plain ERC20 approval, so the non-BNB path below\n // is reused as-is.\n IVAIController vaiCtrl = comptroller.vaiController();\n bool isVai = address(params.vDebt) == address(vaiCtrl);\n // RFQ sources only quote bStock->USDT, so BNB and VAI debts are inherently two-hop\n // (...->WBNB / ...->VAI). Reject a single-hop config up front instead of failing opaquely\n // later on a zero debt-asset delta.\n if ((isBnb || isVai) && params.router2 == address(0)) revert InvalidIntermediate();\n debt = isBnb\n ? IERC20Upgradeable(address(wbnb))\n : isVai\n ? IERC20Upgradeable(vaiCtrl.getVAIAddress())\n : IERC20Upgradeable(params.vDebt.underlying());\n }\n IERC20Upgradeable bStock = IERC20Upgradeable(params.vBStock.underlying());\n\n // 1. Repay the borrow, seizing the bStock vToken to this contract.\n // Core has a POOL-WIDE liquidator gate (`liquidatorContract`), which is always configured on\n // the networks this contract targets. While it is set, a direct `vToken.liquidateBorrow`\n // reverts UNAUTHORIZED, so we route the repay through that Venus Liquidator (it pulls our\n // repay and sends us our share of the seized collateral; treasury keeps a cut). The seized\n // amount is read as a BALANCE DELTA, so the Liquidator's cut is handled correctly. We guard\n // against an unset gate so a misconfig fails loudly instead of silently no-op'ing a call to\n // address(0) (a low-level call to a codeless address returns success).\n uint256 vBefore = params.vBStock.balanceOf(address(this));\n address gate = comptroller.liquidatorContract();\n ensureNonzeroAddress(gate);\n if (isBnb) {\n // Unwrap EXACTLY the repay (WBNB held as inventory or drawn from the vWBNB flash) and forward\n // native BNB to the gate's vBNB branch (`{value:}`). Only the repay portion is unwrapped, so\n // pre-existing WBNB inventory is untouched; the swap proceeds below stay as WBNB. No approval\n // is granted (value is forwarded), so there is no standing allowance to reset.\n wbnb.withdraw(params.repayAmount);\n ILiquidator(gate).liquidateBorrow{ value: params.repayAmount }(\n address(params.vDebt),\n params.borrower,\n params.repayAmount,\n params.vBStock\n );\n } else {\n debt.forceApprove(gate, params.repayAmount);\n ILiquidator(gate).liquidateBorrow(\n address(params.vDebt),\n params.borrower,\n params.repayAmount,\n params.vBStock\n );\n // Reset the gate approval: if the Liquidator pulled less than `repayAmount` (e.g. a close-factor\n // cap), the remainder would otherwise linger as a standing allowance. Same invariant as `_swap`.\n debt.forceApprove(gate, 0);\n }\n uint256 seizedV = params.vBStock.balanceOf(address(this)) - vBefore;\n\n // 2. Redeem the seized vBStock for raw bStock. Measure by DELTA so any pre-existing bStock\n // (dust or a stray transfer) is excluded — we only sell what this redeem actually returned.\n uint256 rawBefore = bStock.balanceOf(address(this));\n uint256 redeemErr = params.vBStock.redeem(seizedV);\n if (redeemErr != 0) revert RedeemFailed(redeemErr);\n seizedBStock = bStock.balanceOf(address(this)) - rawBefore;\n\n // 3. Sell the bStock to the debt asset (one hop, or two via an intermediate) and assert minOut.\n // Extracted into `_sellToDebt` to keep this frame within the EVM stack limit.\n debtOut = _sellToDebt(debt, bStock, seizedBStock, params);\n }\n\n /**\n * @dev Sell `seizedBStock` to the debt asset and enforce `minOut`. Single hop by default\n * (bStock -> debt via the Native router). When `params.router2` is set, two hops\n * (bStock -> intermediate -> debt): hop 1 sells bStock to the intermediate (USDT) via the\n * Native router, hop 2 converts that intermediate to the debt asset via a second allowlisted\n * router (AMM/aggregator). `minOut` is measured in the debt asset across the whole chain.\n * @return debtOut Debt-asset proceeds (balance delta), reverting if below `minOut`.\n */\n function _sellToDebt(\n IERC20Upgradeable debt,\n IERC20Upgradeable bStock,\n uint256 seizedBStock,\n LiquidationParams memory params\n ) private returns (uint256 debtOut) {\n uint256 debtBefore = debt.balanceOf(address(this));\n if (params.router2 == address(0)) {\n _swap(bStock, params.router, params.swapCalldata, seizedBStock);\n } else {\n // The intermediate must be a real token distinct from both endpoints: if it equals `debt`,\n // hop 1 would inflate the balance `debtBefore` snapshots against (breaking the proceeds\n // delta); if it equals `bStock`, the hop-1 sell shrinks the balance and the midDelta\n // subtraction underflows.\n if (\n params.intermediateToken == address(0) ||\n params.intermediateToken == address(debt) ||\n params.intermediateToken == address(bStock)\n ) revert InvalidIntermediate();\n\n IERC20Upgradeable mid = IERC20Upgradeable(params.intermediateToken);\n uint256 midBefore = mid.balanceOf(address(this));\n _swap(bStock, params.router, params.swapCalldata, seizedBStock); // hop 1: bStock -> intermediate\n // Only the hop-1 proceeds are sold onward; any pre-existing intermediate inventory is excluded.\n uint256 midDelta = mid.balanceOf(address(this)) - midBefore;\n _swap(mid, params.router2, params.swapCalldata2, midDelta); // hop 2: intermediate -> debt\n }\n\n debtOut = debt.balanceOf(address(this)) - debtBefore;\n if (debtOut < params.minOut) revert InsufficientOut(debtOut, params.minOut);\n }\n}\n" + }, + "contracts/BStock/IBStockLiquidator.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IVBep20 } from \"../InterfacesV8.sol\";\n\n/// @title IBStockLiquidator\n/// @author Venus\n/// @notice External API, events, errors and parameter struct for {BStockLiquidator}.\n/// @dev The flash-loan callback `executeOperation` is intentionally NOT part of this interface — it is\n/// declared by {IFlashLoanReceiver} (owned by the Core flash-loan subsystem) and implemented directly\n/// by {BStockLiquidator}, keeping this interface free of the concrete `VToken` dependency.\ninterface IBStockLiquidator {\n /// @notice Parameters for a single liquidation.\n /// @dev The swap can be one or two hops. When `router2 == address(0)` it is a single hop\n /// (bStock -> debt) and `swapCalldata2` / `intermediateToken` are ignored — behavior is\n /// identical to the single-hop version. When `router2` is set it is two hops\n /// (bStock -> `intermediateToken` -> debt): hop 1 sells bStock via `router`, hop 2 converts\n /// the intermediate to the debt asset via `router2`. `minOut` is always the FINAL debt-asset\n /// floor across the whole chain. `deadline` is a unix-timestamp expiry: the call reverts once\n /// `block.timestamp` passes it, so a stale tx cannot settle against an expired quote.\n struct LiquidationParams {\n address borrower; // account to liquidate\n IVBep20 vDebt; // borrowed market to repay (e.g. vUSDT)\n IVBep20 vBStock; // bStock collateral market to seize (e.g. vTSLAB)\n uint256 repayAmount; // debt underlying to repay (its own decimals)\n address router; // hop-1 RFQ router (Native firm-quote target, Liquid Mesh router, …) — must be allowlisted\n bytes swapCalldata; // hop-1 calldata (off-chain-signed RFQ order): bStock -> intermediate (or -> debt if single-hop)\n uint256 minOut; // minimum FINAL debt-asset amount the swap chain must yield, else revert\n address router2; // hop-2 router (AMM/aggregator): intermediate -> debt; address(0) = single-hop\n bytes swapCalldata2; // hop-2 calldata; the swap recipient inside it MUST be this contract\n address intermediateToken; // token hop 1 outputs and hop 2 consumes (e.g. USDT); required when router2 set\n uint256 deadline; // unix timestamp after which the call reverts; guards a stale tx sitting in the mempool\n }\n\n /// @notice Emitted when an operator is allowlisted or removed.\n event OperatorSet(address indexed operator, bool allowed);\n\n /// @notice Emitted when a swap router is allowlisted or removed.\n event RouterSet(address indexed router, bool allowed);\n\n /// @notice Emitted when a router's token-approval target (spender) is set or cleared.\n event RouterSpenderSet(address indexed router, address indexed spender);\n\n /// @notice Emitted on a successful liquidation.\n /// @param borrower The liquidated account.\n /// @param vBStock The seized bStock collateral market.\n /// @param vDebt The repaid debt market.\n /// @param repayAmount Debt underlying repaid.\n /// @param seizedBStock Raw bStock redeemed and sold.\n /// @param debtOut Debt-asset proceeds of the swap.\n /// @param flash True if funded by a flash loan, false if from inventory.\n event Liquidated(\n address indexed borrower,\n address indexed vBStock,\n address indexed vDebt,\n uint256 repayAmount,\n uint256 seizedBStock,\n uint256 debtOut,\n bool flash\n );\n\n /// @notice Emitted when the owner withdraws a token.\n event Swept(address indexed token, address indexed to, uint256 amount);\n\n /// @notice Emitted when the owner withdraws stuck native BNB.\n event SweptNative(address indexed to, uint256 amount);\n\n /// @notice Emitted when a swap hop pulls less than the amount approved to the router, leaving a\n /// residual of the input token in the contract (e.g. a partially-filled RFQ quote). The\n /// residual is recoverable via `sweep`.\n /// @param token The input token left over (bStock on hop 1, the intermediate on hop 2).\n /// @param amount The residual amount not consumed by the swap.\n event PartialSwapLeftover(address indexed token, uint256 amount);\n\n /// @notice Thrown when the caller is neither the owner nor an allowlisted operator.\n error NotOperator();\n\n /// @notice Thrown when the supplied swap router is not allowlisted.\n error RouterNotAllowed(address router);\n\n /// @notice Thrown when a router spender being set is not a deployed contract. The spender receives\n /// a live token approval during the swap, so an EOA spender is always a misconfiguration.\n error SpenderNotContract(address spender);\n\n /// @notice Thrown when `vBStock.redeem` returns a non-zero error code.\n error RedeemFailed(uint256 errCode);\n\n /// @notice Thrown when the low-level call to the router reverts.\n error SwapFailed();\n\n /// @notice Thrown when swap proceeds are below `minOut`.\n error InsufficientOut(uint256 got, uint256 minOut);\n\n /// @notice Thrown when `minOut` is zero: a liquidation must set a non-zero debt-asset floor,\n /// else it would silently accept any proceeds (including zero).\n error ZeroMinOut();\n\n /// @notice Thrown when a two-hop `intermediateToken` is zero, or equals the debt or bStock token.\n error InvalidIntermediate();\n\n /// @notice Thrown when `executeOperation` is called by something other than the Comptroller.\n error OnlyComptroller();\n\n /// @notice Thrown when the flash-loan initiator is not this contract.\n error BadInitiator(address initiator);\n\n /// @notice Thrown when the flashed asset does not match `params.vDebt`.\n error WrongFlashAsset();\n\n /// @notice Thrown when `flashLiquidate` is called with a VAI debt. VAI is minted/burned by the\n /// VAIController and has no vToken market to flash from — use `liquidate` (INVENTORY mode)\n /// with pre-funded VAI instead.\n error FlashNotSupportedForVai();\n\n /// @notice Thrown when the call is submitted after `params.deadline`.\n error DeadlineExpired(uint256 deadline, uint256 nowTs);\n\n /// @notice Thrown when a native BNB transfer (the `sweepNative` payout) fails.\n error NativeTransferFailed();\n\n /// @notice Allow or disallow an address to trigger liquidations.\n /// @param operator Address to allowlist or remove.\n /// @param allowed True to allow, false to remove.\n function setOperator(address operator, bool allowed) external;\n\n /// @notice Allow or disallow a router as the swap target (e.g. the Native router).\n /// @dev Removing a router also clears its `routerSpender` entry (emitting {RouterSpenderSet} with\n /// `address(0)`), so a stale spender cannot silently reactivate on a later re-allowlist.\n /// @param router Address to allowlist or remove.\n /// @param allowed True to allow, false to remove.\n function setRouter(address router, bool allowed) external;\n\n /// @notice Set the token-approval target (spender) for a router whose settlement contract that pulls\n /// the input token differs from the call target (e.g. Liquid Mesh). When unset, the approval\n /// defaults to the router itself (Native behaviour). Setting `spender = address(0)` clears it.\n /// @dev Reverts with {RouterNotAllowed} unless `router` is currently allowlisted, and with\n /// {SpenderNotContract} when a non-zero `spender` has no code. The spender is an approval\n /// target only — it is never called; the low-level call always targets the allowlisted router.\n /// @param router The allowlisted swap target (call target).\n /// @param spender The contract that pulls the input token via `transferFrom` during settlement.\n function setRouterSpender(address router, address spender) external;\n\n /// @notice Withdraw any token (profit, leftover inventory, stuck dust) to `to`.\n /// @param token Token to withdraw.\n /// @param to Recipient.\n /// @param amount Amount to withdraw.\n function sweep(address token, address to, uint256 amount) external;\n\n /// @notice Withdraw stuck native BNB (a stray transfer, or a gate refund) to `to`.\n /// @param to Recipient.\n /// @param amount Amount of native BNB to withdraw.\n function sweepNative(address to, uint256 amount) external;\n\n /// @notice Liquidate using the contract's own debt-asset inventory.\n /// @dev The contract must already hold >= `repayAmount` of `vDebt.underlying()`.\n /// Profit (proceeds - repay) stays in the contract; withdraw it with `sweep`.\n /// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final\n /// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt).\n /// @return debtOut Debt-asset proceeds realized by the swap chain.\n function liquidate(LiquidationParams calldata params) external returns (uint256 debtOut);\n\n /// @notice Liquidate by flash-borrowing the repay amount from Venus, repaid (+ premium) in the same tx.\n /// @dev Requires this contract to be `authorizedFlashLoan` in the Comptroller and `vDebt` flash-enabled.\n /// Profit (proceeds - repay - premium) stays in the contract; withdraw it with `sweep`.\n /// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final\n /// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt).\n function flashLiquidate(LiquidationParams calldata params) external;\n}\n" + }, + "contracts/Comptroller/ComptrollerInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\nimport { ResilientOracleInterface } from \"@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol\";\nimport { IDeviationBoundedOracle } from \"@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol\";\n\nimport { VToken } from \"../Tokens/VTokens/VToken.sol\";\nimport { VAIControllerInterface } from \"../Tokens/VAI/VAIControllerInterface.sol\";\nimport { WeightFunction } from \"./Diamond/interfaces/IFacetBase.sol\";\n\nenum Action {\n MINT,\n REDEEM,\n BORROW,\n REPAY,\n SEIZE,\n LIQUIDATE,\n TRANSFER,\n ENTER_MARKET,\n EXIT_MARKET\n}\n\ninterface ComptrollerInterface {\n /// @notice Indicator that this is a Comptroller contract (for inspection)\n function isComptroller() external pure returns (bool);\n\n /*** Assets You Are In ***/\n\n function enterMarkets(address[] calldata vTokens) external returns (uint[] memory);\n\n function exitMarket(address vToken) external returns (uint);\n\n /*** Policy Hooks ***/\n\n function mintAllowed(address vToken, address minter, uint mintAmount) external returns (uint);\n\n function mintVerify(address vToken, address minter, uint mintAmount, uint mintTokens) external;\n\n function redeemAllowed(address vToken, address redeemer, uint redeemTokens) external returns (uint);\n\n function redeemVerify(address vToken, address redeemer, uint redeemAmount, uint redeemTokens) external;\n\n function borrowAllowed(address vToken, address borrower, uint borrowAmount) external returns (uint);\n\n function borrowVerify(address vToken, address borrower, uint borrowAmount) external;\n\n function executeFlashLoan(\n address payable onBehalf,\n address payable receiver,\n VToken[] calldata vTokens,\n uint256[] calldata underlyingAmounts,\n bytes calldata param\n ) external;\n\n function repayBorrowAllowed(\n address vToken,\n address payer,\n address borrower,\n uint repayAmount\n ) external returns (uint);\n\n function repayBorrowVerify(\n address vToken,\n address payer,\n address borrower,\n uint repayAmount,\n uint borrowerIndex\n ) external;\n\n function liquidateBorrowAllowed(\n address vTokenBorrowed,\n address vTokenCollateral,\n address liquidator,\n address borrower,\n uint repayAmount\n ) external returns (uint);\n\n function liquidateBorrowVerify(\n address vTokenBorrowed,\n address vTokenCollateral,\n address liquidator,\n address borrower,\n uint repayAmount,\n uint seizeTokens\n ) external;\n\n function seizeAllowed(\n address vTokenCollateral,\n address vTokenBorrowed,\n address liquidator,\n address borrower,\n uint seizeTokens\n ) external returns (uint);\n\n function seizeVerify(\n address vTokenCollateral,\n address vTokenBorrowed,\n address liquidator,\n address borrower,\n uint seizeTokens\n ) external;\n\n function transferAllowed(address vToken, address src, address dst, uint transferTokens) external returns (uint);\n\n function transferVerify(address vToken, address src, address dst, uint transferTokens) external;\n\n /*** Liquidity/Liquidation Calculations ***/\n\n function liquidateCalculateSeizeTokens(\n address vTokenBorrowed,\n address vTokenCollateral,\n uint repayAmount\n ) external view returns (uint, uint);\n\n function liquidateCalculateSeizeTokens(\n address borrower,\n address vTokenBorrowed,\n address vTokenCollateral,\n uint repayAmount\n ) external view returns (uint, uint);\n\n function setMintedVAIOf(address owner, uint amount) external returns (uint);\n\n function liquidateVAICalculateSeizeTokens(\n address vTokenCollateral,\n uint repayAmount\n ) external view returns (uint, uint);\n\n function getXVSAddress() external view returns (address);\n\n function markets(address) external view returns (bool, uint, bool, uint, uint, uint96, bool);\n\n function oracle() external view returns (ResilientOracleInterface);\n\n function deviationBoundedOracle() external view returns (IDeviationBoundedOracle);\n\n function getAccountLiquidity(address) external view returns (uint, uint, uint);\n\n function getAssetsIn(address) external view returns (VToken[] memory);\n\n function claimVenus(address) external;\n\n function venusAccrued(address) external view returns (uint);\n\n function venusSupplySpeeds(address) external view returns (uint);\n\n function venusBorrowSpeeds(address) external view returns (uint);\n\n function getAllMarkets() external view returns (VToken[] memory);\n\n function venusSupplierIndex(address, address) external view returns (uint);\n\n function venusInitialIndex() external view returns (uint224);\n\n function venusBorrowerIndex(address, address) external view returns (uint);\n\n function venusBorrowState(address) external view returns (uint224, uint32);\n\n function venusSupplyState(address) external view returns (uint224, uint32);\n\n function approvedDelegates(address borrower, address delegate) external view returns (bool);\n\n function vaiController() external view returns (VAIControllerInterface);\n\n function protocolPaused() external view returns (bool);\n\n function actionPaused(address market, Action action) external view returns (bool);\n\n function mintedVAIs(address user) external view returns (uint);\n\n function vaiMintRate() external view returns (uint);\n\n function authorizedFlashLoan(address account) external view returns (bool);\n\n function userPoolId(address account) external view returns (uint96);\n\n function getLiquidationIncentive(address vToken) external view returns (uint256);\n\n function getEffectiveLiquidationIncentive(address account, address vToken) external view returns (uint256);\n\n function getEffectiveLtvFactor(\n address account,\n address vToken,\n WeightFunction weightingStrategy\n ) external view returns (uint256);\n\n function lastPoolId() external view returns (uint96);\n\n function corePoolId() external pure returns (uint96);\n\n function pools(\n uint96 poolId\n ) external view returns (string memory label, bool isActive, bool allowCorePoolFallback);\n\n function getPoolVTokens(uint96 poolId) external view returns (address[] memory);\n\n function poolMarkets(\n uint96 poolId,\n address vToken\n )\n external\n view\n returns (\n bool isListed,\n uint256 collateralFactorMantissa,\n bool isVenus,\n uint256 liquidationThresholdMantissa,\n uint256 liquidationIncentiveMantissa,\n uint96 marketPoolId,\n bool isBorrowAllowed\n );\n\n function isFlashLoanPaused() external view returns (bool);\n}\n\ninterface IVAIVault {\n function updatePendingRewards() external;\n}\n\ninterface IComptroller {\n /*** Treasury Data ***/\n function treasuryAddress() external view returns (address);\n\n function treasuryPercent() external view returns (uint);\n}\n" + }, + "contracts/Comptroller/Diamond/interfaces/IFacetBase.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\nimport { Action } from \"../../../Comptroller/ComptrollerInterface.sol\";\nimport { PoolMarketId } from \"../../../Comptroller/Types/PoolMarketId.sol\";\n\nenum WeightFunction {\n /// @notice Use the collateral factor of the asset for weighting\n USE_COLLATERAL_FACTOR,\n /// @notice Use the liquidation threshold of the asset for weighting\n USE_LIQUIDATION_THRESHOLD\n}\n\ninterface IFacetBase {\n /**\n * @notice The initial XVS rewards index for a market\n */\n function venusInitialIndex() external pure returns (uint224);\n\n /**\n * @notice Checks if a certain action is paused on a market\n * @param action Action id\n * @param market vToken address\n */\n function actionPaused(address market, Action action) external view returns (bool);\n\n /**\n * @notice Returns the XVS address\n * @return The address of XVS token\n */\n function getXVSAddress() external view returns (address);\n\n function getPoolMarketIndex(uint96 poolId, address vToken) external pure returns (PoolMarketId);\n\n function corePoolId() external pure returns (uint96);\n}\n" + }, + "contracts/Comptroller/Types/PoolMarketId.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\n/// @notice Strongly-typed identifier for pool markets mapping keys\n/// @dev Underlying storage is bytes32: first 12 bytes (96 bits) = poolId, last 20 bytes = vToken address\ntype PoolMarketId is bytes32;\n\n " + }, + "contracts/external/IProtocolShareReserve.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\ninterface IProtocolShareReserve {\n enum IncomeType {\n SPREAD,\n LIQUIDATION,\n ERC4626_WRAPPER_REWARDS,\n FLASHLOAN\n }\n\n function updateAssetsState(address comptroller, address asset, IncomeType incomeType) external;\n}\n" + }, + "contracts/external/IWBNB.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\nimport { IERC20Upgradeable } from \"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\";\n\ninterface IWBNB is IERC20Upgradeable {\n function deposit() external payable;\n\n function withdraw(uint256 amount) external;\n}\n" + }, + "contracts/FlashLoan/interfaces/IFlashLoanReceiver.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { VToken } from \"../../Tokens/VTokens/VToken.sol\";\n\n/// @title IFlashLoanReceiver\n/// @notice Interface for flashLoan receiver contract, which executes custom logic with flash-borrowed assets.\n/// @dev This interface defines the method that must be implemented by any contract wishing to interact with the flashLoan system.\n/// Contracts must ensure they have the means to repay at least the premium (fee), with any unpaid balance becoming debt.\ninterface IFlashLoanReceiver {\n /**\n * @notice Executes an operation after receiving the flash-borrowed assets.\n * @dev Implementation of this function must ensure at least the premium (fee) is repaid within the same transaction.\n * Any unpaid balance (principal + premium - repaid amount) will be added to the onBehalf address's borrow balance.\n * @param vTokens The vToken contracts corresponding to the flash-borrowed underlying assets.\n * @param amounts The amounts of each underlying asset that were flash-borrowed.\n * @param premiums The premiums (fees) associated with each flash-borrowed asset.\n * @param initiator The address that initiated the flash loan.\n * @param onBehalf The address of the user whose debt position will be used for any unpaid flash loan balance.\n * @param param Additional parameters encoded as bytes. These can be used to pass custom data to the receiver contract.\n * @return success True if the operation succeeds (regardless of repayment amount), false if the operation fails.\n * @return repayAmounts Array of uint256 representing the amounts to be repaid for each asset. The receiver contract\n * must approve these amounts to the respective vToken contracts before this function returns.\n */\n function executeOperation(\n VToken[] calldata vTokens,\n uint256[] calldata amounts,\n uint256[] calldata premiums,\n address initiator,\n address onBehalf,\n bytes calldata param\n ) external returns (bool success, uint256[] memory repayAmounts);\n}\n" + }, + "contracts/InterestRateModels/InterestRateModelV8.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n/**\n * @title Venus's InterestRateModelV8 Interface\n * @author Venus\n */\nabstract contract InterestRateModelV8 {\n /// @notice Indicator that this is an InterestRateModel contract (for inspection)\n bool public constant isInterestRateModel = true;\n\n /**\n * @notice Calculates the current borrow interest rate per block\n * @param cash The total amount of cash the market has\n * @param borrows The total amount of borrows the market has outstanding\n * @param reserves The total amnount of reserves the market has\n * @return The borrow rate per block (as a percentage, and scaled by 1e18)\n */\n function getBorrowRate(uint256 cash, uint256 borrows, uint256 reserves) external view virtual returns (uint256);\n\n /**\n * @notice Calculates the current supply interest rate per block\n * @param cash The total amount of cash the market has\n * @param borrows The total amount of borrows the market has outstanding\n * @param reserves The total amnount of reserves the market has\n * @param reserveFactorMantissa The current reserve factor the market has\n * @return The supply rate per block (as a percentage, and scaled by 1e18)\n */\n function getSupplyRate(\n uint256 cash,\n uint256 borrows,\n uint256 reserves,\n uint256 reserveFactorMantissa\n ) external view virtual returns (uint256);\n}\n" + }, + "contracts/InterfacesV8.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nimport { IERC20Upgradeable } from \"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol\";\nimport { ResilientOracleInterface } from \"@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol\";\n\ninterface IVToken is IERC20Upgradeable {\n function accrueInterest() external returns (uint256);\n\n function redeem(uint256 redeemTokens) external returns (uint256);\n\n function redeemUnderlying(uint256 redeemAmount) external returns (uint256);\n\n function borrowBalanceCurrent(address borrower) external returns (uint256);\n\n function balanceOfUnderlying(address owner) external returns (uint256);\n\n function comptroller() external view returns (IComptroller);\n\n function borrowBalanceStored(address account) external view returns (uint256);\n}\n\ninterface IVBep20 is IVToken {\n function borrowBehalf(address borrower, uint256 borrowAmount) external returns (uint256);\n\n function repayBorrowBehalf(address borrower, uint256 repayAmount) external returns (uint256);\n\n function liquidateBorrow(\n address borrower,\n uint256 repayAmount,\n IVToken vTokenCollateral\n ) external returns (uint256);\n\n function underlying() external view returns (address);\n}\n\ninterface IVBNB is IVToken {\n function repayBorrowBehalf(address borrower) external payable;\n\n function liquidateBorrow(address borrower, IVToken vTokenCollateral) external payable;\n}\n\ninterface IVAIController {\n function accrueVAIInterest() external;\n\n function liquidateVAI(\n address borrower,\n uint256 repayAmount,\n IVToken vTokenCollateral\n ) external returns (uint256, uint256);\n\n function repayVAIBehalf(address borrower, uint256 amount) external returns (uint256, uint256);\n\n function getVAIAddress() external view returns (address);\n\n function getVAIRepayAmount(address borrower) external view returns (uint256);\n}\n\ninterface IComptroller {\n enum Action {\n MINT,\n REDEEM,\n BORROW,\n REPAY,\n SEIZE,\n LIQUIDATE,\n TRANSFER,\n ENTER_MARKET,\n EXIT_MARKET\n }\n\n function _setActionsPaused(address[] calldata markets_, Action[] calldata actions_, bool paused_) external;\n\n function executeFlashLoan(\n address payable onBehalf,\n address payable receiver,\n IVToken[] calldata vTokens,\n uint256[] calldata underlyingAmounts,\n bytes calldata param\n ) external;\n\n function vaiController() external view returns (IVAIController);\n\n function liquidatorContract() external view returns (address);\n\n function getAccountLiquidity(address account) external view returns (uint256, uint256, uint256);\n\n function oracle() external view returns (ResilientOracleInterface);\n\n function actionPaused(address market, Action action) external view returns (bool);\n\n function markets(address) external view returns (bool, uint256, bool);\n\n function isForcedLiquidationEnabled(address) external view returns (bool);\n\n function getEffectiveLiquidationIncentive(address account, address vToken) external view returns (uint256);\n}\n\ninterface ILiquidator {\n function restrictLiquidation(address borrower) external;\n\n function unrestrictLiquidation(address borrower) external;\n\n function addToAllowlist(address borrower, address liquidator) external;\n\n function removeFromAllowlist(address borrower, address liquidator) external;\n\n function liquidateBorrow(\n address vToken,\n address borrower,\n uint256 repayAmount,\n IVToken vTokenCollateral\n ) external payable;\n\n function setTreasuryPercent(uint256 newTreasuryPercentMantissa) external;\n\n function treasuryPercentMantissa() external view returns (uint256);\n}\n" + }, + "contracts/test/BStockLiquidationMocks.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\n/// @notice Test-only mocks for exercising the BStockLiquidator contract (and the off-chain\n/// scripts) on a local network, mimicking the Venus Core + Native interfaces they call.\n/// Not for production.\n\ncontract MockMintableERC20 is ERC20 {\n uint8 private _decimals;\n\n constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {\n _decimals = decimals_;\n }\n\n function decimals() public view override returns (uint8) {\n return _decimals;\n }\n\n function mint(address to, uint256 amount) external {\n _mint(to, amount);\n }\n}\n\n/// @dev Receiver surface the flash comptroller calls back into. `address[]` matches the\n/// `VToken[]` selector (a contract type canonicalizes to `address`).\ninterface IFlashReceiverLike {\n function executeOperation(\n address[] calldata vTokens,\n uint256[] calldata amounts,\n uint256[] calldata premiums,\n address initiator,\n address onBehalf,\n bytes calldata param\n ) external returns (bool, uint256[] memory);\n}\n\n/// @dev Minimal VAIController stand-in. VAI is not a vToken and has no `underlying()`; the debt token\n/// is resolved through `getVAIAddress()`, exactly as the real VAIController exposes it.\ncontract MockVAIController {\n address public immutable vai;\n\n constructor(address vai_) {\n vai = vai_;\n }\n\n function getVAIAddress() external view returns (address) {\n return vai;\n }\n}\n\n/// @dev Minimal Comptroller surface the script + BStockLiquidator read, plus a flash lender.\ncontract MockComptrollerLite {\n uint256 public shortfall;\n uint256 public liquidityErr; // getAccountLiquidity error slot; non-zero -> a failed reading (oracle etc.)\n uint256 public closeFactorMantissa = 0.5e18;\n uint256 public liquidationIncentiveMantissa = 1.1e18;\n // Borrower-aware effective incentive. 0 -> falls back to liquidationIncentiveMantissa (the common\n // case: a single-pool borrower). Set non-zero to model a borrower in a non-core pool whose vBStock\n // incentive DIFFERS from core — the case the treasury-cut math must size against (effective, not core).\n uint256 public effectiveIncentiveMantissa;\n uint256 public flashPremiumMantissa; // fee on flash principal, 1e18-scaled (default 0)\n address public liquidatorContract; // pool-wide gate; 0 = permissionless (direct liquidateBorrow)\n uint256 public treasuryPercent; // redeem fee, 1e18-scaled (default 0)\n address public vaiController; // a debt equal to this is VAI (no underlying(); see MockVAIController)\n\n function setVaiController(address v) external {\n vaiController = v;\n }\n\n function setShortfall(uint256 s) external {\n shortfall = s;\n }\n\n function setLiquidityErr(uint256 e) external {\n liquidityErr = e;\n }\n\n function setEffectiveIncentive(uint256 m) external {\n effectiveIncentiveMantissa = m;\n }\n\n function setTreasuryPercent(uint256 p) external {\n treasuryPercent = p;\n }\n\n function setLiquidatorContract(address l) external {\n liquidatorContract = l;\n }\n\n function setFlashPremium(uint256 m) external {\n flashPremiumMantissa = m;\n }\n\n function getAccountLiquidity(address) external view returns (uint256, uint256, uint256) {\n return (liquidityErr, 0, shortfall);\n }\n\n /// @dev Mirrors the seize math: seizeTokens = repay * incentive (1:1 collateral rate here).\n function liquidateCalculateSeizeTokens(\n address,\n address,\n uint256 repayAmount\n ) external view returns (uint256, uint256) {\n return (0, (repayAmount * liquidationIncentiveMantissa) / 1e18);\n }\n\n /// @dev Borrower-aware 4-arg overload (the version vToken.liquidateBorrowFresh and the off-chain\n /// scripts call). Same math here — the mock has a single pool — so both overloads agree.\n function liquidateCalculateSeizeTokens(\n address,\n address,\n address,\n uint256 repayAmount\n ) external view returns (uint256, uint256) {\n return (0, (repayAmount * liquidationIncentiveMantissa) / 1e18);\n }\n\n /// @dev VAI's seize math (VAIController.liquidateVAIFresh calls this): VAI is priced at $1 and the\n /// incentive is the borrower-agnostic getLiquidationIncentive — hence the 2-arg shape.\n function liquidateVAICalculateSeizeTokens(address, uint256 repayAmount) external view returns (uint256, uint256) {\n return (0, (repayAmount * liquidationIncentiveMantissa) / 1e18);\n }\n\n /// @dev Read by the off-chain script to size the Venus Liquidator's bonus cut. Returns the\n /// borrower-aware effective incentive when set, else the core value — so a test can make the\n /// two DIVERGE and prove the cut is sized off the effective one (mirrors the real gate).\n function getEffectiveLiquidationIncentive(address, address) external view returns (uint256) {\n return effectiveIncentiveMantissa != 0 ? effectiveIncentiveMantissa : liquidationIncentiveMantissa;\n }\n\n /// @dev Borrower-agnostic incentive — the one VAI's seize math uses.\n function getLiquidationIncentive(address) external view returns (uint256) {\n return liquidationIncentiveMantissa;\n }\n\n /// @dev Flash lender: send principal from the (pre-funded) debt vToken, call back, pull repay.\n function executeFlashLoan(\n address payable /* onBehalf */,\n address payable receiver,\n address[] calldata vTokens,\n uint256[] calldata amounts,\n bytes calldata param\n ) external {\n uint256[] memory premiums = new uint256[](1);\n premiums[0] = (amounts[0] * flashPremiumMantissa) / 1e18;\n\n MockVTokenDebt(vTokens[0]).flashOut(receiver, amounts[0]);\n (bool ok, uint256[] memory repay) = IFlashReceiverLike(receiver).executeOperation(\n vTokens,\n amounts,\n premiums,\n msg.sender,\n msg.sender,\n param\n );\n require(ok, \"executeOperation failed\");\n MockVTokenDebt(vTokens[0]).flashPull(receiver, repay[0]);\n }\n}\n\n/// @dev Collateral vToken mock. Tracks vToken balances; redeem() burns vTokens\n/// and pays out the underlying bStock 1:1 (exchangeRate = 1 for the test).\ncontract MockVTokenCollateral {\n address public immutable underlying;\n address public immutable comptroller;\n mapping(address => uint256) public balanceOf;\n\n constructor(address underlying_, address comptroller_) {\n underlying = underlying_;\n comptroller = comptroller_;\n }\n\n /// Called by the debt vToken to credit seized collateral to the liquidator.\n function creditSeize(address to, uint256 vAmount) external {\n balanceOf[to] += vAmount;\n }\n\n uint256 public redeemError; // non-zero -> redeem returns this code (exercises RedeemFailed)\n\n function setRedeemError(uint256 e) external {\n redeemError = e;\n }\n\n function redeem(uint256 redeemTokens) external returns (uint256) {\n if (redeemError != 0) return redeemError;\n require(balanceOf[msg.sender] >= redeemTokens, \"insufficient vTokens\");\n balanceOf[msg.sender] -= redeemTokens;\n // 1:1 exchange rate for the test\n require(ERC20(underlying).transfer(msg.sender, redeemTokens), \"redeem transfer failed\");\n return 0;\n }\n\n /// @dev 1:1, matching `redeem` above. Read by the off-chain script to precompute the seize.\n function exchangeRateStored() external pure returns (uint256) {\n return 1e18;\n }\n}\n\n/// @dev Debt vToken mock. liquidateBorrow pulls `repayAmount` of the debt\n/// underlying from the caller and credits seized collateral (repay + 10%).\ncontract MockVTokenDebt {\n address public immutable underlying;\n address public immutable comptroller;\n uint256 public incentiveMantissa = 1.1e18;\n uint256 public liquidateError; // non-zero -> liquidateBorrow returns this code (exercises LiquidateBorrowFailed)\n bool public constant isFlashLoanEnabled = true;\n\n constructor(address underlying_, address comptroller_) {\n underlying = underlying_;\n comptroller = comptroller_;\n }\n\n function setLiquidateError(uint256 e) external {\n liquidateError = e;\n }\n\n function liquidateBorrow(\n address /* borrower */,\n uint256 repayAmount,\n address vTokenCollateral\n ) external returns (uint256) {\n if (liquidateError != 0) return liquidateError;\n require(ERC20(underlying).transferFrom(msg.sender, address(this), repayAmount), \"repay pull failed\");\n uint256 seizeV = (repayAmount * incentiveMantissa) / 1e18;\n MockVTokenCollateral(vTokenCollateral).creditSeize(msg.sender, seizeV);\n return 0;\n }\n\n /// @dev Flash helpers, driven by MockComptrollerLite. Pre-fund this contract with `underlying`.\n function flashOut(address to, uint256 amount) external {\n require(msg.sender == comptroller, \"only comptroller\");\n require(ERC20(underlying).transfer(to, amount), \"flash out failed\");\n }\n\n function flashPull(address from, uint256 amount) external {\n require(msg.sender == comptroller, \"only comptroller\");\n // `from` (the receiver) approved THIS vToken as spender, per the real flash interface.\n require(ERC20(underlying).transferFrom(from, address(this), amount), \"flash pull failed\");\n }\n}\n\n/// @dev Stand-in for the Native router. Pulls tokenIn from the caller and pays\n/// tokenOut at a configurable rate (1e18 = 1:1). Pre-fund it with tokenOut.\ncontract MockNativeRouter {\n uint256 public rate = 1e18; // tokenOut per tokenIn, 18-dec fixed point\n\n function setRate(uint256 r) external {\n rate = r;\n }\n\n function swap(address tokenIn, uint256 amountIn, address tokenOut, address to) external {\n require(ERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), \"in pull failed\");\n uint256 amountOut = (amountIn * rate) / 1e18;\n require(ERC20(tokenOut).transfer(to, amountOut), \"out transfer failed\");\n }\n\n /// @dev Pulls the caller's ENTIRE tokenIn balance (what the liquidator actually holds after\n /// redeem) and pays tokenOut at `rate`. Avoids fixed-amount rounding mismatches on forks.\n function swapAll(address tokenIn, address tokenOut, address to) external {\n uint256 amountIn = ERC20(tokenIn).balanceOf(msg.sender);\n require(ERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), \"in pull failed\");\n uint256 amountOut = (amountIn * rate) / 1e18;\n require(ERC20(tokenOut).transfer(to, amountOut), \"out transfer failed\");\n }\n}\n\n/// @dev Malicious router: during the swap hop it re-enters the liquidator (via a preconfigured call)\n/// before completing a normal swap. Used to prove the `nonReentrant` guard blocks re-entry while\n/// the outer liquidation still settles. Must be allowlisted AND set as an operator so the re-entry\n/// clears `onlyOperator` and actually reaches the reentrancy guard.\ncontract MockReentrantRouter {\n uint256 public rate = 1e18;\n address public target; // the liquidator to re-enter\n bytes public reentryCalldata; // encoded liquidate/flashLiquidate call\n bool public reentrySucceeded; // true iff the re-entry call returned success (guard FAILED)\n bool public reentryAttempted;\n\n function configure(address target_, bytes calldata reentryCalldata_) external {\n target = target_;\n reentryCalldata = reentryCalldata_;\n }\n\n function swap(address tokenIn, uint256 amountIn, address tokenOut, address to) external {\n reentryAttempted = true;\n // Attempt to re-enter; swallow the result so the OUTER swap/liquidation can still complete.\n (bool ok, ) = target.call(reentryCalldata);\n reentrySucceeded = ok;\n require(ERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), \"in pull failed\");\n require(ERC20(tokenOut).transfer(to, (amountIn * rate) / 1e18), \"out transfer failed\");\n }\n\n /// @dev Same re-entry attempt, but pulls the caller's ENTIRE tokenIn balance and pays out — so the\n /// OUTER liquidation actually settles (clearing minOut), letting the test observe that the\n /// re-entry was blocked while the surrounding call succeeded.\n function swapAll(address tokenIn, address tokenOut, address to) external {\n reentryAttempted = true;\n (bool ok, ) = target.call(reentryCalldata);\n reentrySucceeded = ok;\n uint256 amountIn = ERC20(tokenIn).balanceOf(msg.sender);\n require(ERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), \"in pull failed\");\n require(ERC20(tokenOut).transfer(to, (amountIn * rate) / 1e18), \"out transfer failed\");\n }\n\n function setRate(uint256 r) external {\n rate = r;\n }\n}\n\n/// @dev Stand-in for an aggregator (e.g. Liquid Mesh) whose settlement pulls the input token through a\n/// SEPARATE spender contract, distinct from the call target. On Liquid Mesh you approve the spender\n/// `0x8157…` but call the router `0x3d90…`. `MockSpender.pull` does the `transferFrom`, so the input\n/// allowance must sit on the SPENDER, not the router — exactly the case `routerSpender` handles.\ncontract MockSpender {\n /// @dev Pull `amountIn` of `tokenIn` from `from` to `to`. `from` must have approved THIS contract.\n function pull(address tokenIn, address from, address to, uint256 amountIn) external {\n require(ERC20(tokenIn).transferFrom(from, to, amountIn), \"spender pull failed\");\n }\n}\n\n/// @dev The call target that pairs with `MockSpender`: it pulls the caller's input token VIA the spender\n/// (so the caller must approve the spender, NOT this router) and pays the output at `rate`. Mirrors a\n/// Liquid Mesh swap: `router.call(swapCalldata)` where settlement pulls via a separate spender.\n/// Pre-fund it with `tokenOut`.\ncontract MockSplitRouter {\n address public immutable spender;\n uint256 public rate = 1e18; // tokenOut per tokenIn, 18-dec fixed point\n\n constructor(address spender_) {\n spender = spender_;\n }\n\n function setRate(uint256 r) external {\n rate = r;\n }\n\n function swap(address tokenIn, uint256 amountIn, address tokenOut, address to) external {\n // Pull via the spender — reverts with \"spender pull failed\" if the caller approved this router\n // instead of the spender (the SafeTransferFromFailed analog of Liquid Mesh's build-time sim).\n MockSpender(spender).pull(tokenIn, msg.sender, address(this), amountIn);\n require(ERC20(tokenOut).transfer(to, (amountIn * rate) / 1e18), \"out transfer failed\");\n }\n\n /// @dev Pulls the caller's ENTIRE tokenIn balance VIA THE SPENDER (what the liquidator holds after\n /// redeem) and pays tokenOut at `rate`. Mirrors MockNativeRouter.swapAll but through the split\n /// spender, so tests need not match the exact seized amount.\n function swapAll(address tokenIn, address tokenOut, address to) external {\n uint256 amountIn = ERC20(tokenIn).balanceOf(msg.sender);\n MockSpender(spender).pull(tokenIn, msg.sender, address(this), amountIn);\n require(ERC20(tokenOut).transfer(to, (amountIn * rate) / 1e18), \"out transfer failed\");\n }\n}\n\n/// @dev Peg Stability Module stand-in exposing the REAL PSM surface the off-chain script encodes\n/// against (`swapStableForVAI` / `previewSwapStableForVAI` / `isPaused` / `vaiMintCap` /\n/// `vaiMinted`) — unlike the generic router mocks, so the script's locally-built calldata is what\n/// executes here. `rateMantissa` mirrors the PSM's IN-direction pricing (min($1, oracle), minus\n/// feeIn): out = amountIn * rateMantissa / 1e18. Pays VAI by minting, like the real PSM.\ncontract MockPSM {\n address public immutable stableToken;\n MockMintableERC20 public immutable vai;\n\n uint256 public rateMantissa = 1e18;\n bool public isPaused;\n uint256 public vaiMintCap = type(uint256).max;\n uint256 public vaiMinted;\n\n constructor(address stableToken_, MockMintableERC20 vai_) {\n stableToken = stableToken_;\n vai = vai_;\n }\n\n function setRate(uint256 r) external {\n rateMantissa = r;\n }\n\n function setPaused(bool p) external {\n isPaused = p;\n }\n\n function setVaiMintCap(uint256 c) external {\n vaiMintCap = c;\n }\n\n function previewSwapStableForVAI(uint256 stableTknAmount) public view returns (uint256) {\n return (stableTknAmount * rateMantissa) / 1e18;\n }\n\n function swapStableForVAI(address receiver, uint256 stableTknAmount) external returns (uint256) {\n require(!isPaused, \"psm paused\");\n uint256 vaiToMint = previewSwapStableForVAI(stableTknAmount);\n require(vaiMinted + vaiToMint <= vaiMintCap, \"mint cap reached\");\n vaiMinted += vaiToMint;\n require(ERC20(stableToken).transferFrom(msg.sender, address(this), stableTknAmount), \"stable pull failed\");\n vai.mint(receiver, vaiToMint);\n return vaiToMint;\n }\n}\n\n/// @dev Stand-in for the pool-wide Venus Liquidator (`liquidatorContract`). Pulls the repay from the\n/// caller and credits the seized collateral (repay * incentive) to the caller, minus a treasury cut.\ncontract MockVenusLiquidator {\n uint256 public incentiveMantissa = 1.1e18;\n uint256 public treasuryCutMantissa; // cut of the seized collateral kept by the liquidator (default 0)\n uint256 public pullMantissa = 1e18; // fraction of repayAmount actually pulled (default 100%)\n address public vBnb; // native BNB market; a repay for this vToken must arrive as msg.value\n address public vaiController; // VAI \"market\"; the repay is the VAI ERC20, resolved via getVAIAddress()\n\n function setTreasuryCut(uint256 m) external {\n treasuryCutMantissa = m;\n }\n\n /// @dev Simulate a partial repay pull (e.g. a close-factor cap): the gate pulls less than the\n /// caller approved, so a standing allowance would linger unless the caller resets it.\n function setPullMantissa(uint256 m) external {\n pullMantissa = m;\n }\n\n /// @dev Mirrors the real gate: a vBNB repay is native BNB (msg.value), not an ERC20 transferFrom.\n function setVBnb(address v) external {\n vBnb = v;\n }\n\n /// @dev Mirrors `Liquidator._liquidateVAI`: a VAI repay is pulled as the VAI ERC20 (resolved via\n /// `getVAIAddress()`, since the VAIController has no `underlying()`), then burned by the real\n /// VAIController. Here we just keep it — only the pull is observable to the liquidator.\n function setVaiController(address v) external {\n vaiController = v;\n }\n\n /// @dev Mirrors the real Venus Liquidator getter the off-chain script reads to precompute the cut.\n function treasuryPercentMantissa() external view returns (uint256) {\n return treasuryCutMantissa;\n }\n\n function liquidateBorrow(\n address vToken,\n address /* borrower */,\n uint256 repayAmount,\n address vTokenCollateral\n ) external payable {\n if (vToken == vBnb) {\n // Native BNB repay: require exact msg.value (mirrors Liquidator.sol) and keep the BNB.\n require(msg.value == repayAmount, \"bad value\");\n } else {\n // VAI has no `underlying()` — resolve it via `getVAIAddress()`, like `Liquidator._liquidateVAI`.\n address underlying = vToken == vaiController\n ? MockVAIController(vToken).getVAIAddress()\n : MockVTokenDebt(vToken).underlying();\n uint256 pulled = (repayAmount * pullMantissa) / 1e18;\n require(ERC20(underlying).transferFrom(msg.sender, address(this), pulled), \"repay pull failed\");\n }\n uint256 seizeV = (repayAmount * incentiveMantissa) / 1e18;\n uint256 toCaller = seizeV - (seizeV * treasuryCutMantissa) / 1e18;\n MockVTokenCollateral(vTokenCollateral).creditSeize(msg.sender, toCaller);\n }\n}\n\n/// @dev Comptroller stand-in that drives the flash callback with deliberately wrong arguments, so the\n/// receiver's mid-flight guards can be exercised. `mode` selects which invariant to violate; the\n/// contract under test is deployed with this as its immutable comptroller. `getAccountLiquidity`\n/// always reports a shortfall so the pre-flight `_check` passes and the callback is reached.\ncontract MockMaliciousFlashComptroller {\n enum Mode {\n BadInitiator, // report an initiator != the receiver\n WrongAsset // flash a vToken != params.vDebt\n }\n\n Mode public mode;\n address public liquidatorContract; // unset: the receiver would liquidate directly\n address public vaiController; // unset: `flashLiquidate`'s VAI check reads this and never matches vDebt\n\n function setMode(Mode mode_) external {\n mode = mode_;\n }\n\n function getAccountLiquidity(address) external pure returns (uint256, uint256, uint256) {\n return (0, 0, 1);\n }\n\n function executeFlashLoan(\n address payable /* onBehalf */,\n address payable receiver,\n address[] calldata vTokens,\n uint256[] calldata amounts,\n bytes calldata param\n ) external {\n uint256[] memory premiums = new uint256[](1);\n if (mode == Mode.BadInitiator) {\n IFlashReceiverLike(receiver).executeOperation(\n vTokens,\n amounts,\n premiums,\n address(uint160(0xbad)), // initiator != receiver\n msg.sender,\n param\n );\n } else {\n address[] memory wrongAsset = new address[](1);\n wrongAsset[0] = address(uint160(0xdead)); // != params.vDebt\n IFlashReceiverLike(receiver).executeOperation(wrongAsset, amounts, premiums, receiver, msg.sender, param);\n }\n }\n}\n" + }, + "contracts/Tokens/VAI/VAIControllerInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\nimport { VTokenInterface } from \"../VTokens/VTokenInterfaces.sol\";\n\ninterface VAIControllerInterface {\n function mintVAI(uint256 mintVAIAmount) external returns (uint256);\n\n function repayVAI(uint256 amount) external returns (uint256, uint256);\n\n function repayVAIBehalf(address borrower, uint256 amount) external returns (uint256, uint256);\n\n function liquidateVAI(\n address borrower,\n uint256 repayAmount,\n VTokenInterface vTokenCollateral\n ) external returns (uint256, uint256);\n\n function getMintableVAI(address minter) external view returns (uint256, uint256);\n\n function getVAIAddress() external view returns (address);\n\n function getVAIRepayAmount(address account) external view returns (uint256);\n}\n" + }, + "contracts/Tokens/VTokens/VToken.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\nimport { IAccessControlManagerV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol\";\nimport { IProtocolShareReserve } from \"../../external/IProtocolShareReserve.sol\";\nimport { ComptrollerInterface, IComptroller } from \"../../Comptroller/ComptrollerInterface.sol\";\nimport { TokenErrorReporter } from \"../../Utils/ErrorReporter.sol\";\nimport { Exponential } from \"../../Utils/Exponential.sol\";\nimport { InterestRateModelV8 } from \"../../InterestRateModels/InterestRateModelV8.sol\";\nimport { VTokenInterface } from \"./VTokenInterfaces.sol\";\nimport { MANTISSA_ONE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\n\n/**\n * @title Venus's vToken Contract\n * @notice Abstract base for vTokens\n * @author Venus\n */\nabstract contract VToken is VTokenInterface, Exponential, TokenErrorReporter {\n struct MintLocalVars {\n MathError mathErr;\n uint exchangeRateMantissa;\n uint mintTokens;\n uint totalSupplyNew;\n uint accountTokensNew;\n uint actualMintAmount;\n }\n\n struct RedeemLocalVars {\n MathError mathErr;\n uint exchangeRateMantissa;\n uint redeemTokens;\n uint redeemAmount;\n uint totalSupplyNew;\n uint accountTokensNew;\n }\n\n struct BorrowLocalVars {\n MathError mathErr;\n uint accountBorrows;\n uint accountBorrowsNew;\n uint totalBorrowsNew;\n }\n\n struct RepayBorrowLocalVars {\n Error err;\n MathError mathErr;\n uint repayAmount;\n uint borrowerIndex;\n uint accountBorrows;\n uint accountBorrowsNew;\n uint totalBorrowsNew;\n uint actualRepayAmount;\n }\n\n /*** Reentrancy Guard ***/\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n */\n modifier nonReentrant() {\n require(_notEntered, \"re-entered\");\n _notEntered = false;\n _;\n _notEntered = true; // get a gas-refund post-Istanbul\n }\n\n /**\n * @notice Transfer `amount` tokens from `msg.sender` to `dst`\n * @param dst The address of the destination account\n * @param amount The number of tokens to transfer\n * @return Whether or not the transfer succeeded\n */\n // @custom:event Emits Transfer event\n function transfer(address dst, uint256 amount) external override nonReentrant returns (bool) {\n return transferTokens(msg.sender, msg.sender, dst, amount) == uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Transfer `amount` tokens from `src` to `dst`\n * @param src The address of the source account\n * @param dst The address of the destination account\n * @param amount The number of tokens to transfer\n * @return Whether or not the transfer succeeded\n */\n // @custom:event Emits Transfer event\n function transferFrom(address src, address dst, uint256 amount) external override nonReentrant returns (bool) {\n return transferTokens(msg.sender, src, dst, amount) == uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Approve `spender` to transfer up to `amount` from `src`\n * @dev This will overwrite the approval amount for `spender`\n * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)\n * @param spender The address of the account which may transfer tokens\n * @param amount The number of tokens that are approved (type(uint256).max means infinite)\n * @return Whether or not the approval succeeded\n */\n // @custom:event Emits Approval event on successful approve\n function approve(address spender, uint256 amount) external override returns (bool) {\n transferAllowances[msg.sender][spender] = amount;\n emit Approval(msg.sender, spender, amount);\n return true;\n }\n\n /**\n * @notice Get the underlying balance of the `owner`\n * @dev This also accrues interest in a transaction\n * @param owner The address of the account to query\n * @return The amount of underlying owned by `owner`\n */\n function balanceOfUnderlying(address owner) external override returns (uint) {\n Exp memory exchangeRate = Exp({ mantissa: exchangeRateCurrent() });\n (MathError mErr, uint balance) = mulScalarTruncate(exchangeRate, accountTokens[owner]);\n ensureNoMathError(mErr);\n return balance;\n }\n\n /**\n * @notice Returns the current total borrows plus accrued interest\n * @return The total borrows with interest\n */\n function totalBorrowsCurrent() external override nonReentrant returns (uint) {\n require(accrueInterest() == uint(Error.NO_ERROR), \"accrue interest failed\");\n return totalBorrows;\n }\n\n /**\n * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex\n * @param account The address whose balance should be calculated after updating borrowIndex\n * @return The calculated balance\n */\n function borrowBalanceCurrent(address account) external override nonReentrant returns (uint) {\n require(accrueInterest() == uint(Error.NO_ERROR), \"accrue interest failed\");\n return borrowBalanceStored(account);\n }\n\n /**\n * @notice Transfers collateral tokens (this market) to the liquidator.\n * @dev Will fail unless called by another vToken during the process of liquidation.\n * Its absolutely critical to use msg.sender as the borrowed vToken and not a parameter.\n * @param liquidator The account receiving seized collateral\n * @param borrower The account having collateral seized\n * @param seizeTokens The number of vTokens to seize\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n // @custom:event Emits Transfer event\n function seize(\n address liquidator,\n address borrower,\n uint seizeTokens\n ) external override nonReentrant returns (uint) {\n return seizeInternal(msg.sender, liquidator, borrower, seizeTokens);\n }\n\n /**\n * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.\n * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.\n * @param newPendingAdmin New pending admin.\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n // @custom:event Emits NewPendingAdmin event with old and new admin addresses\n function _setPendingAdmin(address payable newPendingAdmin) external override returns (uint) {\n // Check caller = admin\n ensureAdmin(msg.sender);\n\n // Save current value, if any, for inclusion in log\n address oldPendingAdmin = pendingAdmin;\n\n // Store pendingAdmin with value newPendingAdmin\n pendingAdmin = newPendingAdmin;\n\n // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin)\n emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin\n * @dev Admin function for pending admin to accept role and update admin\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n // @custom:event Emits NewAdmin event on successful acceptance\n // @custom:event Emits NewPendingAdmin event with null new pending admin\n function _acceptAdmin() external override returns (uint) {\n // Check caller is pendingAdmin\n if (msg.sender != pendingAdmin) {\n return fail(Error.UNAUTHORIZED, FailureInfo.ACCEPT_ADMIN_PENDING_ADMIN_CHECK);\n }\n\n // Save current values for inclusion in log\n address oldAdmin = admin;\n address oldPendingAdmin = pendingAdmin;\n\n // Store admin with value pendingAdmin\n admin = pendingAdmin;\n\n // Clear the pending value\n pendingAdmin = payable(address(0));\n\n emit NewAdmin(oldAdmin, admin);\n emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice accrues interest and sets a new reserve factor for the protocol using `_setReserveFactorFresh`\n * @dev Governor function to accrue interest and set a new reserve factor\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n // @custom:event Emits NewReserveFactor event\n function _setReserveFactor(uint newReserveFactorMantissa_) external override nonReentrant returns (uint) {\n ensureAllowed(\"_setReserveFactor(uint256)\");\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reserve factor change failed.\n return fail(Error(error), FailureInfo.SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED);\n }\n\n // _setReserveFactorFresh emits reserve-factor-specific logs on errors, so we don't need to.\n return _setReserveFactorFresh(newReserveFactorMantissa_);\n }\n\n /**\n * @notice Sets the address of the access control manager of this contract\n * @dev Admin function to set the access control address\n * @param newAccessControlManagerAddress New address for the access control\n * @return uint 0=success, otherwise will revert\n */\n function setAccessControlManager(address newAccessControlManagerAddress) external returns (uint) {\n // Check caller is admin\n ensureAdmin(msg.sender);\n\n ensureNonZeroAddress(newAccessControlManagerAddress);\n\n emit NewAccessControlManager(accessControlManager, newAccessControlManagerAddress);\n accessControlManager = newAccessControlManagerAddress;\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Accrues interest and reduces reserves by transferring to protocol share reserve\n * @param reduceAmount_ Amount of reduction to reserves\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n // @custom:event Emits ReservesReduced event\n function _reduceReserves(uint reduceAmount_) external virtual override nonReentrant returns (uint) {\n ensureAllowed(\"_reduceReserves(uint256)\");\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed.\n return fail(Error(error), FailureInfo.REDUCE_RESERVES_ACCRUE_INTEREST_FAILED);\n }\n\n // If reserves were reduced in accrueInterest\n if (reduceReservesBlockNumber == block.number) return (uint(Error.NO_ERROR));\n // _reduceReservesFresh emits reserve-reduction-specific logs on errors, so we don't need to.\n return _reduceReservesFresh(reduceAmount_);\n }\n\n /**\n * @notice Get the current allowance from `owner` for `spender`\n * @param owner The address of the account which owns the tokens to be spent\n * @param spender The address of the account which may transfer tokens\n * @return The number of tokens allowed to be spent (type(uint256).max means infinite)\n */\n function allowance(address owner, address spender) external view override returns (uint256) {\n return transferAllowances[owner][spender];\n }\n\n /**\n * @notice Get the token balance of the `owner`\n * @param owner The address of the account to query\n * @return The number of tokens owned by `owner`\n */\n function balanceOf(address owner) external view override returns (uint256) {\n return accountTokens[owner];\n }\n\n /**\n * @notice Get a snapshot of the account's balances, and the cached exchange rate\n * @dev This is used by comptroller to more efficiently perform liquidity checks.\n * @param account Address of the account to snapshot\n * @return (possible error, token balance, borrow balance, exchange rate mantissa)\n */\n function getAccountSnapshot(address account) external view override returns (uint, uint, uint, uint) {\n uint borrowBalance;\n uint exchangeRateMantissa;\n\n MathError mErr;\n\n (mErr, borrowBalance) = borrowBalanceStoredInternal(account);\n if (mErr != MathError.NO_ERROR) {\n return (uint(Error.MATH_ERROR), 0, 0, 0);\n }\n\n (mErr, exchangeRateMantissa) = exchangeRateStoredInternal();\n if (mErr != MathError.NO_ERROR) {\n return (uint(Error.MATH_ERROR), 0, 0, 0);\n }\n\n return (uint(Error.NO_ERROR), accountTokens[account], borrowBalance, exchangeRateMantissa);\n }\n\n /**\n * @notice Returns the current per-block supply interest rate for this vToken\n * @dev The calculation includes `flashLoanAmount` in the cash balance to account for active flash loans.\n * @return The supply interest rate per block, scaled by 1e18\n */\n function supplyRatePerBlock() external view override returns (uint) {\n return\n interestRateModel.getSupplyRate(\n _getCashPriorWithFlashLoan(),\n totalBorrows,\n totalReserves,\n reserveFactorMantissa\n );\n }\n\n /**\n * @notice Returns the current per-block borrow interest rate for this vToken\n * @dev The calculation includes `flashLoanAmount` in the cash balance to account for active flash loans.\n * @return The borrow interest rate per block, scaled by 1e18\n */\n function borrowRatePerBlock() external view override returns (uint) {\n return interestRateModel.getBorrowRate(_getCashPriorWithFlashLoan(), totalBorrows, totalReserves);\n }\n\n /**\n * @notice Get cash balance of this vToken in the underlying asset\n * @return The quantity of underlying asset owned by this contract\n */\n function getCash() external view override returns (uint) {\n return getCashPrior();\n }\n\n /**\n * @notice Governance function to set new threshold of block difference after which funds will be sent to the protocol share reserve\n * @param newReduceReservesBlockDelta_ block difference value\n */\n function setReduceReservesBlockDelta(uint256 newReduceReservesBlockDelta_) external {\n require(newReduceReservesBlockDelta_ > 0, \"Invalid Input\");\n ensureAllowed(\"setReduceReservesBlockDelta(uint256)\");\n emit NewReduceReservesBlockDelta(reduceReservesBlockDelta, newReduceReservesBlockDelta_);\n reduceReservesBlockDelta = newReduceReservesBlockDelta_;\n }\n\n /**\n * @notice Sets protocol share reserve contract address\n * @param protcolShareReserve_ The address of protocol share reserve contract\n */\n function setProtocolShareReserve(address payable protcolShareReserve_) external {\n // Check caller is admin\n ensureAdmin(msg.sender);\n ensureNonZeroAddress(protcolShareReserve_);\n emit NewProtocolShareReserve(protocolShareReserve, protcolShareReserve_);\n protocolShareReserve = protcolShareReserve_;\n }\n\n /**\n * @notice Transfers the underlying asset to the specified address for flash loan purposes.\n * @dev Can only be called by the Comptroller contract. This function performs the actual transfer of the underlying\n * asset by calling the `doTransferOut` internal function.\n * - The caller must be the Comptroller contract.\n * - Sets the flashLoanAmount to track the borrowed amount during the flash loan process.\n * @param to The address to which the underlying asset is to be transferred.\n * @param amount The amount of the underlying asset to transfer.\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\n * @custom:error FlashLoanAlreadyActive is thrown if there is already an active flash loan.\n * @custom:error InsufficientCash is thrown when the vToken does not have enough cash to lend\n * @custom:event Emits TransferOutUnderlyingFlashLoan event on successful transfer of amount to receiver\n */\n function transferOutUnderlyingFlashLoan(address payable to, uint256 amount) external nonReentrant {\n if (msg.sender != address(comptroller)) {\n revert InvalidComptroller();\n }\n\n if (flashLoanAmount > 0) {\n revert FlashLoanAlreadyActive();\n }\n\n if (getCashPrior() < amount) {\n revert InsufficientCash();\n }\n flashLoanAmount = amount;\n doTransferOut(to, amount);\n emit TransferOutUnderlyingFlashLoan(underlying, to, amount);\n }\n\n /**\n * @notice Transfers the underlying asset from the specified address during flash loan repayment.\n * @dev Can only be called by the Comptroller contract. This function performs the actual transfer of the underlying\n * asset by calling the `doTransferIn` internal function and handles protocol fee distribution.\n * - The caller must be the Comptroller contract.\n * - Transfers the protocol fee to the protocol share reserve.\n * - Resets the flashLoanAmount to 0 to complete the flash loan cycle.\n * @param from The address from which the underlying asset is to be transferred.\n * @param repaymentAmount The amount of the underlying asset being repaid by the receiver.\n * @param totalFee The total fee amount for the flash loan.\n * @param protocolFee The protocol fee amount to be transferred to the protocol share reserve.\n * @return actualAmountTransferred The actual amount transferred in from the receiver.\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\n * @custom:error InsufficientRepayment is thrown when the repayment amount is insufficient to cover the total fee\n * @custom:event Emits TransferInUnderlyingFlashLoan event on successful transfer of amount from the receiver to the vToken\n */\n function transferInUnderlyingFlashLoan(\n address payable from,\n uint256 repaymentAmount,\n uint256 totalFee,\n uint256 protocolFee\n ) external nonReentrant returns (uint256) {\n if (msg.sender != address(comptroller)) {\n revert InvalidComptroller();\n }\n\n uint256 actualAmountTransferred = doTransferIn(from, repaymentAmount);\n\n if (actualAmountTransferred < totalFee) {\n revert InsufficientRepayment(actualAmountTransferred, totalFee);\n }\n\n // Transfer protocol fee to protocol share reserve\n doTransferOut(protocolShareReserve, protocolFee);\n\n IProtocolShareReserve(protocolShareReserve).updateAssetsState(\n address(comptroller),\n underlying,\n IProtocolShareReserve.IncomeType.FLASHLOAN\n );\n\n // Reset flashLoanAmount to complete the flash loan cycle\n flashLoanAmount = 0;\n\n emit TransferInUnderlyingFlashLoan(underlying, from, actualAmountTransferred, totalFee, protocolFee);\n return actualAmountTransferred;\n }\n\n /**\n * @notice Sets flash loan status for the market\n * @param enabled True to enable flash loans, false to disable\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n * @custom:access Only Governance\n * @custom:event Emits FlashLoanStatusChanged event on success\n */\n function setFlashLoanEnabled(bool enabled) external returns (uint256) {\n ensureAllowed(\"setFlashLoanEnabled(bool)\");\n\n if (isFlashLoanEnabled != enabled) {\n emit FlashLoanStatusChanged(isFlashLoanEnabled, enabled);\n isFlashLoanEnabled = enabled;\n }\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Update flashLoan fee mantissa\n * @param flashLoanFeeMantissa_ FlashLoan fee, scaled by 1e18\n * @param flashLoanProtocolShare_ FlashLoan protocol fee share, transferred to protocol share reserve\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n * @custom:access Only Governance\n * @custom:error FlashLoanFeeTooHigh is thrown when flash loan fee exceeds maximum allowed\n * @custom:error FlashLoanProtocolShareTooHigh is thrown when flash loan fee protocol share exceeds maximum allowed\n * @custom:event Emits FlashLoanFeeUpdated event on success\n */\n function setFlashLoanFeeMantissa(\n uint256 flashLoanFeeMantissa_,\n uint256 flashLoanProtocolShare_\n ) external returns (uint256) {\n ensureAllowed(\"setFlashLoanFeeMantissa(uint256,uint256)\");\n\n if (flashLoanFeeMantissa_ > MANTISSA_ONE) {\n revert FlashLoanFeeTooHigh(flashLoanFeeMantissa_, MANTISSA_ONE);\n }\n\n if (flashLoanProtocolShare_ > MANTISSA_ONE) {\n revert FlashLoanProtocolShareTooHigh(flashLoanProtocolShare_, MANTISSA_ONE);\n }\n\n // Only proceed if values are changing\n if (\n flashLoanFeeMantissa != flashLoanFeeMantissa_ || flashLoanProtocolShareMantissa != flashLoanProtocolShare_\n ) {\n emit FlashLoanFeeUpdated(\n flashLoanFeeMantissa,\n flashLoanFeeMantissa_,\n flashLoanProtocolShareMantissa,\n flashLoanProtocolShare_\n );\n\n flashLoanFeeMantissa = flashLoanFeeMantissa_;\n flashLoanProtocolShareMantissa = flashLoanProtocolShare_;\n }\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Initialize the money market\n * @param comptroller_ The address of the Comptroller\n * @param interestRateModel_ The address of the interest rate model\n * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18\n * @param name_ EIP-20 name of this token\n * @param symbol_ EIP-20 symbol of this token\n * @param decimals_ EIP-20 decimal precision of this token\n */\n function initialize(\n ComptrollerInterface comptroller_,\n InterestRateModelV8 interestRateModel_,\n uint initialExchangeRateMantissa_,\n string memory name_,\n string memory symbol_,\n uint8 decimals_\n ) public {\n ensureAdmin(msg.sender);\n require(accrualBlockNumber == 0 && borrowIndex == 0, \"market may only be initialized once\");\n\n // Set initial exchange rate\n initialExchangeRateMantissa = initialExchangeRateMantissa_;\n require(initialExchangeRateMantissa > 0, \"initial exchange rate must be greater than zero.\");\n\n // Set the comptroller\n uint err = _setComptroller(comptroller_);\n require(err == uint(Error.NO_ERROR), \"setting comptroller failed\");\n\n // Initialize block number and borrow index (block number mocks depend on comptroller being set)\n accrualBlockNumber = block.number;\n borrowIndex = mantissaOne;\n\n // Set the interest rate model (depends on block number / borrow index)\n err = _setInterestRateModelFresh(interestRateModel_);\n require(err == uint(Error.NO_ERROR), \"setting interest rate model failed\");\n\n name = name_;\n symbol = symbol_;\n decimals = decimals_;\n\n // The counter starts true to prevent changing it from zero to non-zero (i.e. smaller cost/refund)\n _notEntered = true;\n }\n\n /**\n * @notice Accrue interest then return the up-to-date exchange rate\n * @return Calculated exchange rate scaled by 1e18\n */\n function exchangeRateCurrent() public override nonReentrant returns (uint) {\n require(accrueInterest() == uint(Error.NO_ERROR), \"accrue interest failed\");\n return exchangeRateStored();\n }\n\n /**\n * @notice Applies accrued interest to total borrows and reserves\n * @dev This calculates interest accrued from the last checkpointed block\n * up to the current block and writes new checkpoint to storage and\n * reduce spread reserves to protocol share reserve\n * if currentBlock - reduceReservesBlockNumber >= blockDelta\n */\n // @custom:event Emits AccrueInterest event\n function accrueInterest() public virtual override returns (uint) {\n /* Remember the initial block number */\n uint currentBlockNumber = block.number;\n uint accrualBlockNumberPrior = accrualBlockNumber;\n\n /* Short-circuit accumulating 0 interest */\n if (accrualBlockNumberPrior == currentBlockNumber) {\n return uint(Error.NO_ERROR);\n }\n\n /* Read the previous values out of storage */\n uint cashPrior = _getCashPriorWithFlashLoan();\n uint borrowsPrior = totalBorrows;\n uint reservesPrior = totalReserves;\n uint borrowIndexPrior = borrowIndex;\n\n /* Calculate the current borrow interest rate */\n uint borrowRateMantissa = interestRateModel.getBorrowRate(cashPrior, borrowsPrior, reservesPrior);\n require(borrowRateMantissa <= borrowRateMaxMantissa, \"borrow rate is absurdly high\");\n\n /* Calculate the number of blocks elapsed since the last accrual */\n (MathError mathErr, uint blockDelta) = subUInt(currentBlockNumber, accrualBlockNumberPrior);\n ensureNoMathError(mathErr);\n\n /*\n * Calculate the interest accumulated into borrows and reserves and the new index:\n * simpleInterestFactor = borrowRate * blockDelta\n * interestAccumulated = simpleInterestFactor * totalBorrows\n * totalBorrowsNew = interestAccumulated + totalBorrows\n * totalReservesNew = interestAccumulated * reserveFactor + totalReserves\n * borrowIndexNew = simpleInterestFactor * borrowIndex + borrowIndex\n */\n\n Exp memory simpleInterestFactor;\n uint interestAccumulated;\n uint totalBorrowsNew;\n uint totalReservesNew;\n uint borrowIndexNew;\n\n (mathErr, simpleInterestFactor) = mulScalar(Exp({ mantissa: borrowRateMantissa }), blockDelta);\n if (mathErr != MathError.NO_ERROR) {\n return\n failOpaque(\n Error.MATH_ERROR,\n FailureInfo.ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED,\n uint(mathErr)\n );\n }\n\n (mathErr, interestAccumulated) = mulScalarTruncate(simpleInterestFactor, borrowsPrior);\n if (mathErr != MathError.NO_ERROR) {\n return\n failOpaque(\n Error.MATH_ERROR,\n FailureInfo.ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED,\n uint(mathErr)\n );\n }\n\n (mathErr, totalBorrowsNew) = addUInt(interestAccumulated, borrowsPrior);\n if (mathErr != MathError.NO_ERROR) {\n return\n failOpaque(\n Error.MATH_ERROR,\n FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED,\n uint(mathErr)\n );\n }\n\n (mathErr, totalReservesNew) = mulScalarTruncateAddUInt(\n Exp({ mantissa: reserveFactorMantissa }),\n interestAccumulated,\n reservesPrior\n );\n if (mathErr != MathError.NO_ERROR) {\n return\n failOpaque(\n Error.MATH_ERROR,\n FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED,\n uint(mathErr)\n );\n }\n\n (mathErr, borrowIndexNew) = mulScalarTruncateAddUInt(simpleInterestFactor, borrowIndexPrior, borrowIndexPrior);\n if (mathErr != MathError.NO_ERROR) {\n return\n failOpaque(\n Error.MATH_ERROR,\n FailureInfo.ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED,\n uint(mathErr)\n );\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /* We write the previously calculated values into storage */\n accrualBlockNumber = currentBlockNumber;\n borrowIndex = borrowIndexNew;\n totalBorrows = totalBorrowsNew;\n totalReserves = totalReservesNew;\n\n (mathErr, blockDelta) = subUInt(currentBlockNumber, reduceReservesBlockNumber);\n ensureNoMathError(mathErr);\n if (blockDelta >= reduceReservesBlockDelta) {\n reduceReservesBlockNumber = currentBlockNumber;\n uint actualCash = getCashPrior();\n if (actualCash < totalReservesNew) {\n _reduceReservesFresh(actualCash);\n } else {\n _reduceReservesFresh(totalReservesNew);\n }\n }\n\n /* We emit an AccrueInterest event */\n emit AccrueInterest(cashPrior, interestAccumulated, borrowIndexNew, totalBorrowsNew);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Sets a new comptroller for the market\n * @dev Admin function to set a new comptroller\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n // @custom:event Emits NewComptroller event\n function _setComptroller(ComptrollerInterface newComptroller) public override returns (uint) {\n // Check caller is admin\n ensureAdmin(msg.sender);\n\n // Ensure invoke comptroller.isComptroller() returns true\n require(newComptroller.isComptroller(), \"marker method returned false\");\n\n // Emit NewComptroller(oldComptroller, newComptroller)\n emit NewComptroller(comptroller, newComptroller);\n\n // Set market's comptroller to newComptroller\n comptroller = newComptroller;\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Accrues interest and updates the interest rate model using _setInterestRateModelFresh\n * @dev Governance function to accrue interest and update the interest rate model\n * @param newInterestRateModel_ The new interest rate model to use\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function _setInterestRateModel(InterestRateModelV8 newInterestRateModel_) public override returns (uint) {\n ensureAllowed(\"_setInterestRateModel(address)\");\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted change of interest rate model failed\n return fail(Error(error), FailureInfo.SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED);\n }\n\n // _setInterestRateModelFresh emits interest-rate-model-update-specific logs on errors, so we don't need to.\n return _setInterestRateModelFresh(newInterestRateModel_);\n }\n\n /**\n * @notice Calculates the exchange rate from the underlying to the VToken\n * @dev This function does not accrue interest before calculating the exchange rate\n * @return Calculated exchange rate scaled by 1e18\n */\n function exchangeRateStored() public view override returns (uint) {\n (MathError err, uint result) = exchangeRateStoredInternal();\n ensureNoMathError(err);\n return result;\n }\n\n /**\n * @notice Return the borrow balance of account based on stored data\n * @param account The address whose balance should be calculated\n * @return The calculated balance\n */\n function borrowBalanceStored(address account) public view override returns (uint) {\n (MathError err, uint result) = borrowBalanceStoredInternal(account);\n ensureNoMathError(err);\n return result;\n }\n\n /**\n * @notice Opens a debt position for the borrower as part of flash loan repayment\n * @dev This function is specifically called during flash loan operations when the repayment\n * is insufficient to cover the full borrowed amount plus fees. It creates a debt position\n * for the unpaid balance. The function checks if the borrow is allowed, accrues interest,\n * and updates the borrower's balance. It also emits a Borrow event and calls the\n * comptroller's borrowVerify function. It reverts if the borrow is not allowed or\n * if the market's block number is not current.\n * @param borrower The address of the borrower who will have the debt position created\n * @param borrowAmount The amount of underlying asset that becomes debt (unpaid flash loan balance)\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n * @custom:error InvalidComptroller is thrown if the caller is not the Comptroller.\n */\n function flashLoanDebtPosition(address borrower, uint borrowAmount) external nonReentrant returns (uint256) {\n // Reverts if the caller is not the comptroller\n if (msg.sender != address(comptroller)) {\n revert InvalidComptroller();\n }\n\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors.\n return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED);\n }\n\n // borrowFresh emits borrow-specific logs on errors, so we don't need to\n return borrowFresh(borrower, payable(address(0)), borrowAmount, false);\n }\n\n /**\n * @notice Calculates the total fee and protocol fee for a flash loan..\n * @param amount The amount of the flash loan.\n * @return totalFee The total fee for the flash loan.\n * @return protocolFee The portion of the total fee allocated to the protocol.\n */\n function calculateFlashLoanFee(uint256 amount) public view returns (uint256, uint256) {\n MathError mErr;\n uint256 totalFee;\n uint256 protocolFee;\n\n (mErr, totalFee) = mulScalarTruncate(Exp({ mantissa: amount }), flashLoanFeeMantissa);\n ensureNoMathError(mErr);\n\n (mErr, protocolFee) = mulScalarTruncate(Exp({ mantissa: totalFee }), flashLoanProtocolShareMantissa);\n ensureNoMathError(mErr);\n\n return (totalFee, protocolFee);\n }\n\n /**\n * @notice Transfers `tokens` tokens from `src` to `dst` by `spender`\n * @dev Called by both `transfer` and `transferFrom` internally\n * @param spender The address of the account performing the transfer\n * @param src The address of the source account\n * @param dst The address of the destination account\n * @param tokens The number of tokens to transfer\n * @return Whether or not the transfer succeeded\n */\n function transferTokens(address spender, address src, address dst, uint tokens) internal returns (uint) {\n /* Fail if transfer not allowed */\n uint allowed = comptroller.transferAllowed(address(this), src, dst, tokens);\n if (allowed != 0) {\n return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.TRANSFER_COMPTROLLER_REJECTION, allowed);\n }\n\n /* Do not allow self-transfers */\n if (src == dst) {\n return fail(Error.BAD_INPUT, FailureInfo.TRANSFER_NOT_ALLOWED);\n }\n\n /* Get the allowance, infinite for the account owner */\n uint startingAllowance = 0;\n if (spender == src) {\n startingAllowance = type(uint256).max;\n } else {\n startingAllowance = transferAllowances[src][spender];\n }\n\n /* Do the calculations, checking for {under,over}flow */\n MathError mathErr;\n uint allowanceNew;\n uint srvTokensNew;\n uint dstTokensNew;\n\n (mathErr, allowanceNew) = subUInt(startingAllowance, tokens);\n if (mathErr != MathError.NO_ERROR) {\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ALLOWED);\n }\n\n (mathErr, srvTokensNew) = subUInt(accountTokens[src], tokens);\n if (mathErr != MathError.NO_ERROR) {\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ENOUGH);\n }\n\n (mathErr, dstTokensNew) = addUInt(accountTokens[dst], tokens);\n if (mathErr != MathError.NO_ERROR) {\n return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_TOO_MUCH);\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n accountTokens[src] = srvTokensNew;\n accountTokens[dst] = dstTokensNew;\n\n /* Eat some of the allowance (if necessary) */\n if (startingAllowance != type(uint256).max) {\n transferAllowances[src][spender] = allowanceNew;\n }\n\n /* We emit a Transfer event */\n emit Transfer(src, dst, tokens);\n\n comptroller.transferVerify(address(this), src, dst, tokens);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Sender supplies assets into the market and receives vTokens in exchange\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\n * @param mintAmount The amount of the underlying asset to supply\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\n */\n function mintInternal(uint mintAmount) internal nonReentrant returns (uint, uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted mint failed\n return (fail(Error(error), FailureInfo.MINT_ACCRUE_INTEREST_FAILED), 0);\n }\n\n // mintFresh emits the actual Mint event if successful and logs on errors, so we don't need to\n return mintFresh(msg.sender, mintAmount);\n }\n\n /**\n * @notice User supplies assets into the market and receives vTokens in exchange\n * @dev Assumes interest has already been accrued up to the current block\n * @param minter The address of the account which is supplying the assets\n * @param mintAmount The amount of the underlying asset to supply\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\n */\n function mintFresh(address minter, uint mintAmount) internal returns (uint, uint) {\n /* Fail if mint not allowed */\n uint allowed = comptroller.mintAllowed(address(this), minter, mintAmount);\n if (allowed != 0) {\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed), 0);\n }\n\n /* Verify market's block number equals current block number */\n if (accrualBlockNumber != block.number) {\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.MINT_FRESHNESS_CHECK), 0);\n }\n\n MintLocalVars memory vars;\n\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\n if (vars.mathErr != MathError.NO_ERROR) {\n return (failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)), 0);\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /*\n * We call `doTransferIn` for the minter and the mintAmount.\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\n * `doTransferIn` reverts if anything goes wrong, since we can't be sure if\n * side-effects occurred. The function returns the amount actually transferred,\n * in case of a fee. On success, the vToken holds an additional `actualMintAmount`\n * of cash.\n */\n vars.actualMintAmount = doTransferIn(minter, mintAmount);\n\n /*\n * We get the current exchange rate and calculate the number of vTokens to be minted:\n * mintTokens = actualMintAmount / exchangeRate\n */\n\n (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(\n vars.actualMintAmount,\n Exp({ mantissa: vars.exchangeRateMantissa })\n );\n ensureNoMathError(vars.mathErr);\n\n /*\n * We calculate the new total supply of vTokens and minter token balance, checking for overflow:\n * totalSupplyNew = totalSupply + mintTokens\n * accountTokensNew = accountTokens[minter] + mintTokens\n */\n (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens);\n ensureNoMathError(vars.mathErr);\n (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[minter], vars.mintTokens);\n ensureNoMathError(vars.mathErr);\n\n /* We write previously calculated values into storage */\n totalSupply = vars.totalSupplyNew;\n accountTokens[minter] = vars.accountTokensNew;\n\n /* We emit a Mint event, and a Transfer event */\n emit Mint(minter, vars.actualMintAmount, vars.mintTokens, vars.accountTokensNew);\n emit Transfer(address(this), minter, vars.mintTokens);\n\n /* We call the defense and prime accrue interest hook */\n comptroller.mintVerify(address(this), minter, vars.actualMintAmount, vars.mintTokens);\n\n return (uint(Error.NO_ERROR), vars.actualMintAmount);\n }\n\n /**\n * @notice Sender supplies assets into the market and receiver receives vTokens in exchange\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\n * @param receiver The address of the account which is receiving the vTokens\n * @param mintAmount The amount of the underlying asset to supply\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\n */\n function mintBehalfInternal(address receiver, uint mintAmount) internal nonReentrant returns (uint, uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted mintBehalf failed\n return (fail(Error(error), FailureInfo.MINT_ACCRUE_INTEREST_FAILED), 0);\n }\n\n // mintBehalfFresh emits the actual Mint event if successful and logs on errors, so we don't need to\n return mintBehalfFresh(msg.sender, receiver, mintAmount);\n }\n\n /**\n * @notice Payer supplies assets into the market and receiver receives vTokens in exchange\n * @dev Assumes interest has already been accrued up to the current block\n * @param payer The address of the account which is paying the underlying token\n * @param receiver The address of the account which is receiving vToken\n * @param mintAmount The amount of the underlying asset to supply\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount.\n */\n function mintBehalfFresh(address payer, address receiver, uint mintAmount) internal returns (uint, uint) {\n ensureNonZeroAddress(receiver);\n /* Fail if mint not allowed */\n uint allowed = comptroller.mintAllowed(address(this), receiver, mintAmount);\n if (allowed != 0) {\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed), 0);\n }\n\n /* Verify market's block number equals current block number */\n if (accrualBlockNumber != block.number) {\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.MINT_FRESHNESS_CHECK), 0);\n }\n\n MintLocalVars memory vars;\n\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\n if (vars.mathErr != MathError.NO_ERROR) {\n return (failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)), 0);\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /*\n * We call `doTransferIn` for the payer and the mintAmount.\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\n * `doTransferIn` reverts if anything goes wrong, since we can't be sure if\n * side-effects occurred. The function returns the amount actually transferred,\n * in case of a fee. On success, the vToken holds an additional `actualMintAmount`\n * of cash.\n */\n vars.actualMintAmount = doTransferIn(payer, mintAmount);\n\n /*\n * We get the current exchange rate and calculate the number of vTokens to be minted:\n * mintTokens = actualMintAmount / exchangeRate\n */\n\n (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(\n vars.actualMintAmount,\n Exp({ mantissa: vars.exchangeRateMantissa })\n );\n ensureNoMathError(vars.mathErr);\n\n /*\n * We calculate the new total supply of vTokens and receiver token balance, checking for overflow:\n * totalSupplyNew = totalSupply + mintTokens\n * accountTokensNew = accountTokens[receiver] + mintTokens\n */\n (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens);\n ensureNoMathError(vars.mathErr);\n\n (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[receiver], vars.mintTokens);\n ensureNoMathError(vars.mathErr);\n\n /* We write previously calculated values into storage */\n totalSupply = vars.totalSupplyNew;\n accountTokens[receiver] = vars.accountTokensNew;\n\n /* We emit a MintBehalf event, and a Transfer event */\n emit MintBehalf(payer, receiver, vars.actualMintAmount, vars.mintTokens, vars.accountTokensNew);\n emit Transfer(address(this), receiver, vars.mintTokens);\n\n /* We call the defense and prime accrue interest hook */\n comptroller.mintVerify(address(this), receiver, vars.actualMintAmount, vars.mintTokens);\n\n return (uint(Error.NO_ERROR), vars.actualMintAmount);\n }\n\n /**\n * @notice Redeemer redeems vTokens in exchange for the underlying assets, transferred to the receiver. Redeemer and receiver can be the same\n * address, or different addresses if the receiver was previously approved by the redeemer as a valid delegate (see MarketFacet.updateDelegate)\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\n * @param redeemer The address of the account which is redeeming the tokens\n * @param receiver The receiver of the tokens\n * @param redeemTokens The number of vTokens to redeem into underlying\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function redeemInternal(\n address redeemer,\n address payable receiver,\n uint redeemTokens\n ) internal nonReentrant returns (uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed\n return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED);\n }\n\n // redeemFresh emits redeem-specific logs on errors, so we don't need to\n return redeemFresh(redeemer, receiver, redeemTokens, 0);\n }\n\n /**\n * @notice Sender redeems underlying assets on behalf of some other address. This function is only available\n * for senders, explicitly marked as delegates of the supplier using `comptroller.updateDelegate`\n * @dev Accrues interest whether or not the operation succeeds, unless reverted\n * @param redeemer The address of the account which is redeeming the tokens\n * @param receiver The receiver of the tokens, if called by a delegate\n * @param redeemAmount The amount of underlying to receive from redeeming vTokens\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function redeemUnderlyingInternal(\n address redeemer,\n address payable receiver,\n uint redeemAmount\n ) internal nonReentrant returns (uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed\n return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED);\n }\n\n // redeemFresh emits redeem-specific logs on errors, so we don't need to\n return redeemFresh(redeemer, receiver, 0, redeemAmount);\n }\n\n /**\n * @notice Redeemer redeems vTokens in exchange for the underlying assets, transferred to the receiver. Redeemer and receiver can be the same\n * address, or different addresses if the receiver was previously approved by the redeemer as a valid delegate (see MarketFacet.updateDelegate)\n * @dev Assumes interest has already been accrued up to the current block\n * @param redeemer The address of the account which is redeeming the tokens\n * @param receiver The receiver of the tokens\n * @param redeemTokensIn The number of vTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be non-zero)\n * @param redeemAmountIn The number of underlying tokens to receive from redeeming vTokens (only one of redeemTokensIn or redeemAmountIn may be non-zero)\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n // solhint-disable-next-line code-complexity\n function redeemFresh(\n address redeemer,\n address payable receiver,\n uint redeemTokensIn,\n uint redeemAmountIn\n ) internal returns (uint) {\n require(redeemTokensIn == 0 || redeemAmountIn == 0, \"one of redeemTokensIn or redeemAmountIn must be zero\");\n\n RedeemLocalVars memory vars;\n\n /* exchangeRate = invoke Exchange Rate Stored() */\n (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();\n ensureNoMathError(vars.mathErr);\n\n /* If redeemTokensIn > 0: */\n if (redeemTokensIn > 0) {\n /*\n * We calculate the exchange rate and the amount of underlying to be redeemed:\n * redeemTokens = redeemTokensIn\n * redeemAmount = redeemTokensIn x exchangeRateCurrent\n */\n vars.redeemTokens = redeemTokensIn;\n\n (vars.mathErr, vars.redeemAmount) = mulScalarTruncate(\n Exp({ mantissa: vars.exchangeRateMantissa }),\n redeemTokensIn\n );\n ensureNoMathError(vars.mathErr);\n } else {\n /*\n * We get the current exchange rate and calculate the amount to be redeemed:\n * redeemTokens = redeemAmountIn / exchangeRate\n * redeemAmount = redeemAmountIn\n */\n\n (vars.mathErr, vars.redeemTokens) = divScalarByExpTruncate(\n redeemAmountIn,\n Exp({ mantissa: vars.exchangeRateMantissa })\n );\n ensureNoMathError(vars.mathErr);\n\n vars.redeemAmount = redeemAmountIn;\n }\n\n /* Fail if redeem not allowed */\n uint allowed = comptroller.redeemAllowed(address(this), redeemer, vars.redeemTokens);\n if (allowed != 0) {\n revert(\"math error\");\n }\n\n /* Verify market's block number equals current block number */\n if (accrualBlockNumber != block.number) {\n revert(\"math error\");\n }\n\n /*\n * We calculate the new total supply and redeemer balance, checking for underflow:\n * totalSupplyNew = totalSupply - redeemTokens\n * accountTokensNew = accountTokens[redeemer] - redeemTokens\n */\n (vars.mathErr, vars.totalSupplyNew) = subUInt(totalSupply, vars.redeemTokens);\n ensureNoMathError(vars.mathErr);\n\n (vars.mathErr, vars.accountTokensNew) = subUInt(accountTokens[redeemer], vars.redeemTokens);\n ensureNoMathError(vars.mathErr);\n\n /* Fail gracefully if protocol has insufficient cash */\n if (getCashPrior() < vars.redeemAmount) {\n revert(\"math error\");\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /* We write previously calculated values into storage */\n totalSupply = vars.totalSupplyNew;\n accountTokens[redeemer] = vars.accountTokensNew;\n\n /*\n * We invoke doTransferOut for the receiver and the redeemAmount.\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\n * On success, the vToken has redeemAmount less of cash.\n * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\n */\n\n uint feeAmount;\n uint remainedAmount;\n if (IComptroller(address(comptroller)).treasuryPercent() != 0) {\n (vars.mathErr, feeAmount) = mulUInt(\n vars.redeemAmount,\n IComptroller(address(comptroller)).treasuryPercent()\n );\n ensureNoMathError(vars.mathErr);\n\n (vars.mathErr, feeAmount) = divUInt(feeAmount, MANTISSA_ONE);\n ensureNoMathError(vars.mathErr);\n\n (vars.mathErr, remainedAmount) = subUInt(vars.redeemAmount, feeAmount);\n ensureNoMathError(vars.mathErr);\n\n address payable treasuryAddress = payable(IComptroller(address(comptroller)).treasuryAddress());\n doTransferOut(treasuryAddress, feeAmount);\n\n emit RedeemFee(redeemer, feeAmount, vars.redeemTokens);\n } else {\n remainedAmount = vars.redeemAmount;\n }\n\n doTransferOut(receiver, remainedAmount);\n\n /* We emit a Transfer event, and a Redeem event */\n emit Transfer(redeemer, address(this), vars.redeemTokens);\n emit Redeem(redeemer, remainedAmount, vars.redeemTokens, vars.accountTokensNew);\n\n /* We call the defense and prime accrue interest hook */\n comptroller.redeemVerify(address(this), redeemer, vars.redeemAmount, vars.redeemTokens);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Receiver gets the borrow on behalf of the borrower address\n * @param borrower The borrower, on behalf of whom to borrow\n * @param receiver The account that would receive the funds (can be the same as the borrower)\n * @param borrowAmount The amount of the underlying asset to borrow\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function borrowInternal(\n address borrower,\n address payable receiver,\n uint borrowAmount\n ) internal nonReentrant returns (uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\n return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED);\n }\n\n // borrowFresh emits borrow-specific logs on errors, so we don't need to\n return borrowFresh(borrower, receiver, borrowAmount, true);\n }\n\n /**\n * @notice Receiver gets the borrow on behalf of the borrower address, controls whether to do the transfer\n * @dev Before calling this function, ensure that the interest has been accrued\n * @param borrower The borrower, on behalf of whom to borrow\n * @param receiver The account that would receive the funds (can be the same as the borrower)\n * @param borrowAmount The amount of the underlying asset to borrow\n * @param shouldTransfer Whether to call doTransferOut for the receiver\n * @return uint Returns 0 on success, otherwise revert (see ErrorReporter.sol for details).\n */\n function borrowFresh(\n address borrower,\n address payable receiver,\n uint borrowAmount,\n bool shouldTransfer\n ) internal returns (uint) {\n /* Revert if borrow not allowed */\n uint allowed = comptroller.borrowAllowed(address(this), borrower, borrowAmount);\n\n if (allowed != 0) {\n revert(\"math error\");\n }\n\n /* Verify market's block number equals current block number */\n if (accrualBlockNumber != block.number) {\n revert(\"math error\");\n }\n\n /* Revert if protocol has insufficient underlying cash */\n if (shouldTransfer && getCashPrior() < borrowAmount) {\n revert(\"math error\");\n }\n\n BorrowLocalVars memory vars;\n\n /*\n * We calculate the new borrower and total borrow balances, failing on overflow:\n * accountBorrowsNew = accountBorrows + borrowAmount\n * totalBorrowsNew = totalBorrows + borrowAmount\n */\n (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower);\n ensureNoMathError(vars.mathErr);\n\n (vars.mathErr, vars.accountBorrowsNew) = addUInt(vars.accountBorrows, borrowAmount);\n ensureNoMathError(vars.mathErr);\n\n (vars.mathErr, vars.totalBorrowsNew) = addUInt(totalBorrows, borrowAmount);\n ensureNoMathError(vars.mathErr);\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /* We write the previously calculated values into storage */\n accountBorrows[borrower].principal = vars.accountBorrowsNew;\n accountBorrows[borrower].interestIndex = borrowIndex;\n totalBorrows = vars.totalBorrowsNew;\n\n if (shouldTransfer) {\n /*\n * We invoke doTransferOut for the receiver and the borrowAmount.\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\n * On success, the vToken borrowAmount less of cash.\n * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\n */\n doTransferOut(receiver, borrowAmount);\n }\n\n /* We emit a Borrow event */\n emit Borrow(borrower, borrowAmount, vars.accountBorrowsNew, vars.totalBorrowsNew);\n\n /* We call the defense and prime accrue interest hook */\n comptroller.borrowVerify(address(this), borrower, borrowAmount);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Sender repays their own borrow\n * @param repayAmount The amount to repay\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\n */\n function repayBorrowInternal(uint repayAmount) internal nonReentrant returns (uint, uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\n return (fail(Error(error), FailureInfo.REPAY_BORROW_ACCRUE_INTEREST_FAILED), 0);\n }\n\n // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to\n return repayBorrowFresh(msg.sender, msg.sender, repayAmount);\n }\n\n /**\n * @notice Sender repays a borrow belonging to another borrowing account\n * @param borrower The account with the debt being payed off\n * @param repayAmount The amount to repay\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\n */\n function repayBorrowBehalfInternal(address borrower, uint repayAmount) internal nonReentrant returns (uint, uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed\n return (fail(Error(error), FailureInfo.REPAY_BEHALF_ACCRUE_INTEREST_FAILED), 0);\n }\n\n // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to\n return repayBorrowFresh(msg.sender, borrower, repayAmount);\n }\n\n /**\n * @notice Borrows are repaid by another user (possibly the borrower).\n * @param payer The account paying off the borrow\n * @param borrower The account with the debt being payed off\n * @param repayAmount The amount of undelrying tokens being returned\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\n */\n function repayBorrowFresh(address payer, address borrower, uint repayAmount) internal returns (uint, uint) {\n /* Fail if repayBorrow not allowed */\n uint allowed = comptroller.repayBorrowAllowed(address(this), payer, borrower, repayAmount);\n if (allowed != 0) {\n return (\n failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REPAY_BORROW_COMPTROLLER_REJECTION, allowed),\n 0\n );\n }\n\n /* Verify market's block number equals current block number */\n if (accrualBlockNumber != block.number) {\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.REPAY_BORROW_FRESHNESS_CHECK), 0);\n }\n\n RepayBorrowLocalVars memory vars;\n\n /* We remember the original borrowerIndex for verification purposes */\n vars.borrowerIndex = accountBorrows[borrower].interestIndex;\n\n /* We fetch the amount the borrower owes, with accumulated interest */\n (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower);\n if (vars.mathErr != MathError.NO_ERROR) {\n return (\n failOpaque(\n Error.MATH_ERROR,\n FailureInfo.REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\n uint(vars.mathErr)\n ),\n 0\n );\n }\n\n // caps the repayAmount to the actual owed amount\n vars.repayAmount = repayAmount >= vars.accountBorrows ? vars.accountBorrows : repayAmount;\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /*\n * We call doTransferIn for the payer and the repayAmount\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\n * On success, the vToken holds an additional repayAmount of cash.\n * doTransferIn reverts if anything goes wrong, since we can't be sure if side effects occurred.\n * it returns the amount actually transferred, in case of a fee.\n */\n vars.actualRepayAmount = doTransferIn(payer, vars.repayAmount);\n\n /*\n * We calculate the new borrower and total borrow balances, failing on underflow:\n * accountBorrowsNew = accountBorrows - actualRepayAmount\n * totalBorrowsNew = totalBorrows - actualRepayAmount\n */\n (vars.mathErr, vars.accountBorrowsNew) = subUInt(vars.accountBorrows, vars.actualRepayAmount);\n ensureNoMathError(vars.mathErr);\n\n (vars.mathErr, vars.totalBorrowsNew) = subUInt(totalBorrows, vars.actualRepayAmount);\n ensureNoMathError(vars.mathErr);\n\n /* We write the previously calculated values into storage */\n accountBorrows[borrower].principal = vars.accountBorrowsNew;\n accountBorrows[borrower].interestIndex = borrowIndex;\n totalBorrows = vars.totalBorrowsNew;\n\n /* We emit a RepayBorrow event */\n emit RepayBorrow(payer, borrower, vars.actualRepayAmount, vars.accountBorrowsNew, vars.totalBorrowsNew);\n\n /* We call the defense and prime accrue interest hook */\n comptroller.repayBorrowVerify(address(this), payer, borrower, vars.actualRepayAmount, vars.borrowerIndex);\n\n return (uint(Error.NO_ERROR), vars.actualRepayAmount);\n }\n\n /**\n * @notice The sender liquidates the borrowers collateral.\n * The collateral seized is transferred to the liquidator.\n * @param borrower The borrower of this vToken to be liquidated\n * @param vTokenCollateral The market in which to seize collateral from the borrower\n * @param repayAmount The amount of the underlying borrowed asset to repay\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\n */\n function liquidateBorrowInternal(\n address borrower,\n uint repayAmount,\n VTokenInterface vTokenCollateral\n ) internal nonReentrant returns (uint, uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed\n return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED), 0);\n }\n\n error = vTokenCollateral.accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed\n return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED), 0);\n }\n\n // liquidateBorrowFresh emits borrow-specific logs on errors, so we don't need to\n return liquidateBorrowFresh(msg.sender, borrower, repayAmount, vTokenCollateral);\n }\n\n /**\n * @notice The liquidator liquidates the borrowers collateral.\n * The collateral seized is transferred to the liquidator.\n * @param borrower The borrower of this vToken to be liquidated\n * @param liquidator The address repaying the borrow and seizing collateral\n * @param vTokenCollateral The market in which to seize collateral from the borrower\n * @param repayAmount The amount of the underlying borrowed asset to repay\n * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.\n */\n // solhint-disable-next-line code-complexity\n function liquidateBorrowFresh(\n address liquidator,\n address borrower,\n uint repayAmount,\n VTokenInterface vTokenCollateral\n ) internal returns (uint, uint) {\n /* Fail if liquidate not allowed */\n uint allowed = comptroller.liquidateBorrowAllowed(\n address(this),\n address(vTokenCollateral),\n liquidator,\n borrower,\n repayAmount\n );\n if (allowed != 0) {\n return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_COMPTROLLER_REJECTION, allowed), 0);\n }\n\n /* Verify market's block number equals current block number */\n if (accrualBlockNumber != block.number) {\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_FRESHNESS_CHECK), 0);\n }\n\n /* Verify vTokenCollateral market's block number equals current block number */\n if (vTokenCollateral.accrualBlockNumber() != block.number) {\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_COLLATERAL_FRESHNESS_CHECK), 0);\n }\n\n /* Fail if borrower = liquidator */\n if (borrower == liquidator) {\n return (fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_LIQUIDATOR_IS_BORROWER), 0);\n }\n\n /* Fail if repayAmount = 0 */\n if (repayAmount == 0) {\n return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_ZERO), 0);\n }\n\n /* Fail if repayAmount = type(uint256).max */\n if (repayAmount == type(uint256).max) {\n return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX), 0);\n }\n\n /* Fail if repayBorrow fails */\n (uint repayBorrowError, uint actualRepayAmount) = repayBorrowFresh(liquidator, borrower, repayAmount);\n if (repayBorrowError != uint(Error.NO_ERROR)) {\n return (fail(Error(repayBorrowError), FailureInfo.LIQUIDATE_REPAY_BORROW_FRESH_FAILED), 0);\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /* We calculate the number of collateral tokens that will be seized */\n (uint amountSeizeError, uint seizeTokens) = comptroller.liquidateCalculateSeizeTokens(\n borrower,\n address(this),\n address(vTokenCollateral),\n actualRepayAmount\n );\n\n require(amountSeizeError == uint(Error.NO_ERROR), \"LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED\");\n\n /* Revert if borrower collateral token balance < seizeTokens */\n require(vTokenCollateral.balanceOf(borrower) >= seizeTokens, \"LIQUIDATE_SEIZE_TOO_MUCH\");\n\n // If this is also the collateral, run seizeInternal to avoid re-entrancy, otherwise make an external call\n uint seizeError;\n if (address(vTokenCollateral) == address(this)) {\n seizeError = seizeInternal(address(this), liquidator, borrower, seizeTokens);\n } else {\n seizeError = vTokenCollateral.seize(liquidator, borrower, seizeTokens);\n }\n\n /* Revert if seize tokens fails (since we cannot be sure of side effects) */\n require(seizeError == uint(Error.NO_ERROR), \"token seizure failed\");\n\n /* We emit a LiquidateBorrow event */\n emit LiquidateBorrow(liquidator, borrower, actualRepayAmount, address(vTokenCollateral), seizeTokens);\n\n /* We call the defense and prime accrue interest hook */\n comptroller.liquidateBorrowVerify(\n address(this),\n address(vTokenCollateral),\n liquidator,\n borrower,\n actualRepayAmount,\n seizeTokens\n );\n\n return (uint(Error.NO_ERROR), actualRepayAmount);\n }\n\n /**\n * @notice Transfers collateral tokens (this market) to the liquidator.\n * @dev Called only during an in-kind liquidation, or by liquidateBorrow during the liquidation of another vToken.\n * Its absolutely critical to use msg.sender as the seizer vToken and not a parameter.\n * @param seizerToken The contract seizing the collateral (i.e. borrowed vToken)\n * @param liquidator The account receiving seized collateral\n * @param borrower The account having collateral seized\n * @param seizeTokens The number of vTokens to seize\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function seizeInternal(\n address seizerToken,\n address liquidator,\n address borrower,\n uint seizeTokens\n ) internal returns (uint) {\n /* Fail if seize not allowed */\n uint allowed = comptroller.seizeAllowed(address(this), seizerToken, liquidator, borrower, seizeTokens);\n if (allowed != 0) {\n return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_SEIZE_COMPTROLLER_REJECTION, allowed);\n }\n\n /* Fail if borrower = liquidator */\n if (borrower == liquidator) {\n return fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER);\n }\n\n MathError mathErr;\n uint borrowerTokensNew;\n uint liquidatorTokensNew;\n\n /*\n * We calculate the new borrower and liquidator token balances, failing on underflow/overflow:\n * borrowerTokensNew = accountTokens[borrower] - seizeTokens\n * liquidatorTokensNew = accountTokens[liquidator] + seizeTokens\n */\n (mathErr, borrowerTokensNew) = subUInt(accountTokens[borrower], seizeTokens);\n if (mathErr != MathError.NO_ERROR) {\n return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED, uint(mathErr));\n }\n\n (mathErr, liquidatorTokensNew) = addUInt(accountTokens[liquidator], seizeTokens);\n if (mathErr != MathError.NO_ERROR) {\n return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED, uint(mathErr));\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /* We write the previously calculated values into storage */\n accountTokens[borrower] = borrowerTokensNew;\n accountTokens[liquidator] = liquidatorTokensNew;\n\n /* Emit a Transfer event */\n emit Transfer(borrower, liquidator, seizeTokens);\n\n /* We call the defense and prime accrue interest hook */\n comptroller.seizeVerify(address(this), seizerToken, liquidator, borrower, seizeTokens);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Sets a new reserve factor for the protocol (requires fresh interest accrual)\n * @dev Governance function to set a new reserve factor\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function _setReserveFactorFresh(uint newReserveFactorMantissa) internal returns (uint) {\n // Verify market's block number equals current block number\n if (accrualBlockNumber != block.number) {\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_RESERVE_FACTOR_FRESH_CHECK);\n }\n\n // Check newReserveFactor ≤ maxReserveFactor\n if (newReserveFactorMantissa > reserveFactorMaxMantissa) {\n return fail(Error.BAD_INPUT, FailureInfo.SET_RESERVE_FACTOR_BOUNDS_CHECK);\n }\n\n uint oldReserveFactorMantissa = reserveFactorMantissa;\n reserveFactorMantissa = newReserveFactorMantissa;\n\n emit NewReserveFactor(oldReserveFactorMantissa, newReserveFactorMantissa);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice Accrues interest and adds reserves by transferring from `msg.sender`\n * @param addAmount Amount of addition to reserves\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function _addReservesInternal(uint addAmount) internal nonReentrant returns (uint) {\n uint error = accrueInterest();\n if (error != uint(Error.NO_ERROR)) {\n // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed.\n return fail(Error(error), FailureInfo.ADD_RESERVES_ACCRUE_INTEREST_FAILED);\n }\n\n // _addReservesFresh emits reserve-addition-specific logs on errors, so we don't need to.\n (error, ) = _addReservesFresh(addAmount);\n return error;\n }\n\n /**\n * @notice Add reserves by transferring from caller\n * @dev Requires fresh interest accrual\n * @param addAmount Amount of addition to reserves\n * @return (uint, uint) An error code (0=success, otherwise a failure (see ErrorReporter.sol for details)) and the actual amount added, net token fees\n */\n function _addReservesFresh(uint addAmount) internal returns (uint, uint) {\n // totalReserves + actualAddAmount\n uint totalReservesNew;\n uint actualAddAmount;\n\n // We fail gracefully unless market's block number equals current block number\n if (accrualBlockNumber != block.number) {\n return (fail(Error.MARKET_NOT_FRESH, FailureInfo.ADD_RESERVES_FRESH_CHECK), actualAddAmount);\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n /*\n * We call doTransferIn for the caller and the addAmount\n * Note: The vToken must handle variations between BEP-20 and BNB underlying.\n * On success, the vToken holds an additional addAmount of cash.\n * doTransferIn reverts if anything goes wrong, since we can't be sure if side effects occurred.\n * it returns the amount actually transferred, in case of a fee.\n */\n\n actualAddAmount = doTransferIn(msg.sender, addAmount);\n\n totalReservesNew = totalReserves + actualAddAmount;\n\n /* Revert on overflow */\n require(totalReservesNew >= totalReserves, \"add reserves unexpected overflow\");\n\n // Store reserves[n+1] = reserves[n] + actualAddAmount\n totalReserves = totalReservesNew;\n\n /* Emit NewReserves(admin, actualAddAmount, reserves[n+1]) */\n emit ReservesAdded(msg.sender, actualAddAmount, totalReservesNew);\n\n /* Return (NO_ERROR, actualAddAmount) */\n return (uint(Error.NO_ERROR), actualAddAmount);\n }\n\n /**\n * @notice Reduces reserves by transferring to protocol share reserve contract\n * @dev Requires fresh interest accrual\n * @param reduceAmount Amount of reduction to reserves\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function _reduceReservesFresh(uint reduceAmount) internal virtual returns (uint) {\n if (reduceAmount == 0) {\n return uint(Error.NO_ERROR);\n }\n\n // We fail gracefully unless market's block number equals current block number\n if (accrualBlockNumber != block.number) {\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDUCE_RESERVES_FRESH_CHECK);\n }\n\n // Fail gracefully if protocol has insufficient underlying cash\n if (getCashPrior() < reduceAmount) {\n return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDUCE_RESERVES_CASH_NOT_AVAILABLE);\n }\n\n // Check reduceAmount ≤ reserves[n] (totalReserves)\n if (reduceAmount > totalReserves) {\n return fail(Error.BAD_INPUT, FailureInfo.REDUCE_RESERVES_VALIDATION);\n }\n\n /////////////////////////\n // EFFECTS & INTERACTIONS\n // (No safe failures beyond this point)\n\n // Store reserves[n+1] = reserves[n] - reduceAmount\n totalReserves = totalReserves - reduceAmount;\n\n // doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.\n doTransferOut(protocolShareReserve, reduceAmount);\n\n IProtocolShareReserve(protocolShareReserve).updateAssetsState(\n address(comptroller),\n underlying,\n IProtocolShareReserve.IncomeType.SPREAD\n );\n\n emit ReservesReduced(protocolShareReserve, reduceAmount, totalReserves);\n\n return uint(Error.NO_ERROR);\n }\n\n /**\n * @notice updates the interest rate model (requires fresh interest accrual)\n * @dev Governance function to update the interest rate model\n * @param newInterestRateModel the new interest rate model to use\n * @return uint Returns 0 on success, otherwise returns a failure code (see ErrorReporter.sol for details).\n */\n function _setInterestRateModelFresh(InterestRateModelV8 newInterestRateModel) internal returns (uint) {\n // We fail gracefully unless market's block number equals current block number\n if (accrualBlockNumber != block.number) {\n return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_INTEREST_RATE_MODEL_FRESH_CHECK);\n }\n\n // Ensure invoke newInterestRateModel.isInterestRateModel() returns true\n require(newInterestRateModel.isInterestRateModel(), \"marker method returned false\");\n\n // Emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel)\n emit NewMarketInterestRateModel(interestRateModel, newInterestRateModel);\n\n // Set the interest rate model to newInterestRateModel\n interestRateModel = newInterestRateModel;\n\n return uint(Error.NO_ERROR);\n }\n\n /*** Safe Token ***/\n\n /**\n * @dev Performs a transfer in, reverting upon failure. Returns the amount actually transferred to the protocol, in case of a fee.\n * This may revert due to insufficient balance or insufficient allowance.\n */\n function doTransferIn(address from, uint amount) internal virtual returns (uint);\n\n /**\n * @dev Performs a transfer out, ideally returning an explanatory error code upon failure rather than reverting.\n * If caller has not called checked protocol's balance, may revert due to insufficient cash held in the contract.\n * If caller has checked protocol's balance, and verified it is >= amount, this should not revert in normal conditions.\n */\n function doTransferOut(address payable to, uint amount) internal virtual;\n\n /**\n * @notice Return the borrow balance of account based on stored data\n * @param account The address whose balance should be calculated\n * @return Tuple of error code and the calculated balance or 0 if error code is non-zero\n */\n function borrowBalanceStoredInternal(address account) internal view returns (MathError, uint) {\n /* Note: we do not assert that the market is up to date */\n MathError mathErr;\n uint principalTimesIndex;\n uint result;\n\n /* Get borrowBalance and borrowIndex */\n BorrowSnapshot storage borrowSnapshot = accountBorrows[account];\n\n /* If borrowBalance = 0 then borrowIndex is likely also 0.\n * Rather than failing the calculation with a division by 0, we immediately return 0 in this case.\n */\n if (borrowSnapshot.principal == 0) {\n return (MathError.NO_ERROR, 0);\n }\n\n /* Calculate new borrow balance using the interest index:\n * recentBorrowBalance = borrower.borrowBalance * market.borrowIndex / borrower.borrowIndex\n */\n (mathErr, principalTimesIndex) = mulUInt(borrowSnapshot.principal, borrowIndex);\n if (mathErr != MathError.NO_ERROR) {\n return (mathErr, 0);\n }\n\n (mathErr, result) = divUInt(principalTimesIndex, borrowSnapshot.interestIndex);\n if (mathErr != MathError.NO_ERROR) {\n return (mathErr, 0);\n }\n\n return (MathError.NO_ERROR, result);\n }\n\n /**\n * @notice Calculates the exchange rate from the underlying to the vToken\n * @dev This function does not accrue interest before calculating the exchange rate\n * @return Tuple of error code and calculated exchange rate scaled by 1e18\n */\n function exchangeRateStoredInternal() internal view virtual returns (MathError, uint) {\n uint _totalSupply = totalSupply;\n if (_totalSupply == 0) {\n /*\n * If there are no tokens minted:\n * exchangeRate = initialExchangeRate\n */\n return (MathError.NO_ERROR, initialExchangeRateMantissa);\n } else {\n /*\n * Otherwise:\n * exchangeRate = (totalCash + totalBorrows + flashLoanAmount - totalReserves) / totalSupply\n */\n uint totalCash = _getCashPriorWithFlashLoan();\n uint cashPlusBorrowsMinusReserves;\n Exp memory exchangeRate;\n MathError mathErr;\n\n (mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(totalCash, totalBorrows, totalReserves);\n if (mathErr != MathError.NO_ERROR) {\n return (mathErr, 0);\n }\n\n (mathErr, exchangeRate) = getExp(cashPlusBorrowsMinusReserves, _totalSupply);\n if (mathErr != MathError.NO_ERROR) {\n return (mathErr, 0);\n }\n\n return (MathError.NO_ERROR, exchangeRate.mantissa);\n }\n }\n\n /**\n * @notice Gets balance of this contract including active flash loans\n * @return The quantity of underlying owned by this contract plus active flash loan amount\n */\n function _getCashPriorWithFlashLoan() internal view returns (uint) {\n return getCashPrior() + flashLoanAmount;\n }\n\n function ensureAllowed(string memory functionSig) private view {\n require(\n IAccessControlManagerV8(accessControlManager).isAllowedToCall(msg.sender, functionSig),\n \"access denied\"\n );\n }\n\n function ensureAdmin(address caller_) private view {\n require(caller_ == admin, \"Unauthorized\");\n }\n\n function ensureNoMathError(MathError mErr) private pure {\n require(mErr == MathError.NO_ERROR, \"math error\");\n }\n\n function ensureNonZeroAddress(address address_) private pure {\n require(address_ != address(0), \"zero address\");\n }\n\n /*** Safe Token ***/\n\n /**\n * @notice Gets balance of this contract in terms of the underlying\n * @dev This excludes the value of the current message, if any\n * @return The quantity of underlying owned by this contract\n */\n function getCashPrior() internal view virtual returns (uint);\n}\n" + }, + "contracts/Tokens/VTokens/VTokenInterfaces.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\npragma solidity 0.8.25;\n\nimport { ComptrollerInterface } from \"../../Comptroller/ComptrollerInterface.sol\";\nimport { InterestRateModelV8 } from \"../../InterestRateModels/InterestRateModelV8.sol\";\n\ncontract VTokenStorageBase {\n /**\n * @notice Container for borrow balance information\n * @member principal Total balance (with accrued interest), after applying the most recent balance-changing action\n * @member interestIndex Global borrowIndex as of the most recent balance-changing action\n */\n struct BorrowSnapshot {\n uint principal;\n uint interestIndex;\n }\n\n /**\n * @dev Guard variable for re-entrancy checks\n */\n bool internal _notEntered;\n\n /**\n * @notice EIP-20 token name for this token\n */\n string public name;\n\n /**\n * @notice EIP-20 token symbol for this token\n */\n string public symbol;\n\n /**\n * @notice EIP-20 token decimals for this token\n */\n uint8 public decimals;\n\n /**\n * @notice Maximum borrow rate that can ever be applied (.0005% / block)\n */\n\n uint internal constant borrowRateMaxMantissa = 0.0005e16;\n\n /**\n * @notice Maximum fraction of interest that can be set aside for reserves\n */\n uint internal constant reserveFactorMaxMantissa = 1e18;\n\n /**\n * @notice Administrator for this contract\n */\n address payable public admin;\n\n /**\n * @notice Pending administrator for this contract\n */\n address payable public pendingAdmin;\n\n /**\n * @notice Contract which oversees inter-vToken operations\n */\n ComptrollerInterface public comptroller;\n\n /**\n * @notice Model which tells what the current interest rate should be\n */\n InterestRateModelV8 public interestRateModel;\n\n /**\n * @notice Initial exchange rate used when minting the first VTokens (used when totalSupply = 0)\n */\n uint internal initialExchangeRateMantissa;\n\n /**\n * @notice Fraction of interest currently set aside for reserves\n */\n uint public reserveFactorMantissa;\n\n /**\n * @notice Block number that interest was last accrued at\n */\n uint public accrualBlockNumber;\n\n /**\n * @notice Accumulator of the total earned interest rate since the opening of the market\n */\n uint public borrowIndex;\n\n /**\n * @notice Total amount of outstanding borrows of the underlying in this market\n */\n uint public totalBorrows;\n\n /**\n * @notice Total amount of reserves of the underlying held in this market\n */\n uint public totalReserves;\n\n /**\n * @notice Total number of tokens in circulation\n */\n uint public totalSupply;\n\n /**\n * @notice Official record of token balances for each account\n */\n mapping(address => uint) internal accountTokens;\n\n /**\n * @notice Approved token transfer amounts on behalf of others\n */\n mapping(address => mapping(address => uint)) internal transferAllowances;\n\n /**\n * @notice Mapping of account addresses to outstanding borrow balances\n */\n mapping(address => BorrowSnapshot) internal accountBorrows;\n\n /**\n * @notice Underlying asset for this VToken\n */\n address public underlying;\n\n /**\n * @notice Implementation address for this contract\n */\n address public implementation;\n\n /**\n * @notice delta block after which reserves will be reduced\n */\n uint public reduceReservesBlockDelta;\n\n /**\n * @notice last block number at which reserves were reduced\n */\n uint public reduceReservesBlockNumber;\n\n /**\n * @notice address of protocol share reserve contract\n */\n address payable public protocolShareReserve;\n\n /**\n * @notice address of accessControlManager\n */\n\n address public accessControlManager;\n}\n\ncontract VTokenStorage is VTokenStorageBase {\n /**\n * @notice flashLoan is enabled for this market or not\n */\n bool public isFlashLoanEnabled;\n\n /**\n * @notice total fee percentage collected on flashLoan (scaled by 1e18)\n */\n uint256 public flashLoanFeeMantissa;\n\n /**\n * @notice fee percentage of flashLoan that goes to protocol (scaled by 1e18)\n */\n uint256 public flashLoanProtocolShareMantissa;\n\n /**\n * @notice Amount of flashLoan taken by the receiver\n * @dev This is used to track the amount of flashLoan taken to correctly calculate the exchange rate\n * during the flashLoan process. It is added to the total cash when calculating the exchange rate.\n */\n uint256 public flashLoanAmount;\n\n /**\n * @notice Tracked internal cash balance, immune to direct token transfers (donation attacks)\n * @dev Updated only via doTransferIn/doTransferOut. Must be initialized via sweepTokenAndSync() after upgrade.\n */\n uint256 public internalCash;\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[45] private __gap;\n}\n\nabstract contract VTokenInterface is VTokenStorage {\n /**\n * @notice Indicator that this is a vToken contract (for inspection)\n */\n bool public constant isVToken = true;\n\n /*** Market Events ***/\n\n /**\n * @notice Event emitted when interest is accrued\n */\n event AccrueInterest(uint cashPrior, uint interestAccumulated, uint borrowIndex, uint totalBorrows);\n\n /**\n * @notice Event emitted when tokens are minted\n */\n event Mint(address minter, uint mintAmount, uint mintTokens, uint256 totalSupply);\n\n /**\n * @notice Event emitted when tokens are minted behalf by payer to receiver\n */\n event MintBehalf(address payer, address receiver, uint mintAmount, uint mintTokens, uint256 totalSupply);\n\n /**\n * @notice Event emitted when tokens are redeemed\n */\n event Redeem(address redeemer, uint redeemAmount, uint redeemTokens, uint256 totalSupply);\n\n /**\n * @notice Event emitted when tokens are redeemed and fee is transferred\n */\n event RedeemFee(address redeemer, uint feeAmount, uint redeemTokens);\n\n /**\n * @notice Event emitted when underlying is borrowed\n */\n event Borrow(address borrower, uint borrowAmount, uint accountBorrows, uint totalBorrows);\n\n /**\n * @notice Event emitted when a borrow is repaid\n */\n event RepayBorrow(address payer, address borrower, uint repayAmount, uint accountBorrows, uint totalBorrows);\n\n /**\n * @notice Event emitted when a borrow is liquidated\n */\n event LiquidateBorrow(\n address liquidator,\n address borrower,\n uint repayAmount,\n address vTokenCollateral,\n uint seizeTokens\n );\n\n /*** Admin Events ***/\n\n /**\n * @notice Event emitted when pendingAdmin is changed\n */\n event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);\n\n /**\n * @notice Event emitted when pendingAdmin is accepted, which means admin has been updated\n */\n event NewAdmin(address oldAdmin, address newAdmin);\n\n /**\n * @notice Event emitted when comptroller is changed\n */\n event NewComptroller(ComptrollerInterface oldComptroller, ComptrollerInterface newComptroller);\n\n /**\n * @notice Event emitted when interestRateModel is changed\n */\n event NewMarketInterestRateModel(\n InterestRateModelV8 oldInterestRateModel,\n InterestRateModelV8 newInterestRateModel\n );\n\n /**\n * @notice Event emitted when the reserve factor is changed\n */\n event NewReserveFactor(uint oldReserveFactorMantissa, uint newReserveFactorMantissa);\n\n /**\n * @notice Event emitted when the reserves are added\n */\n event ReservesAdded(address benefactor, uint addAmount, uint newTotalReserves);\n\n /**\n * @notice Event emitted when the reserves are reduced\n */\n event ReservesReduced(address protocolShareReserve, uint reduceAmount, uint newTotalReserves);\n\n /**\n * @notice EIP20 Transfer event\n */\n event Transfer(address indexed from, address indexed to, uint amount);\n\n /**\n * @notice EIP20 Approval event\n */\n event Approval(address indexed owner, address indexed spender, uint amount);\n\n /**\n * @notice Event emitted when block delta for reduce reserves get updated\n */\n event NewReduceReservesBlockDelta(uint256 oldReduceReservesBlockDelta, uint256 newReduceReservesBlockDelta);\n\n /**\n * @notice Event emitted when address of ProtocolShareReserve contract get updated\n */\n event NewProtocolShareReserve(address indexed oldProtocolShareReserve, address indexed newProtocolShareReserve);\n\n /**\n * @notice Emitted when access control address is changed by admin\n */\n event NewAccessControlManager(address oldAccessControlAddress, address newAccessControlAddress);\n\n /**\n * @notice Event emitted when flashLoanEnabled status is changed\n */\n event FlashLoanStatusChanged(bool previousStatus, bool newStatus);\n\n /**\n * @notice Event emitted when asset is transferred to receiver\n */\n event TransferOutUnderlyingFlashLoan(address asset, address receiver, uint256 amount);\n\n /**\n * @notice Event emitted when asset is transferred from sender and verified\n */\n event TransferInUnderlyingFlashLoan(\n address indexed asset,\n address indexed sender,\n uint256 amount,\n uint256 totalFee,\n uint256 protocolFee\n );\n\n /**\n * @notice Event emitted when internalCash is synced with actual token balance\n */\n event CashSynced(uint256 oldInternalCash, uint256 newInternalCash);\n\n /**\n * @notice Event emitted when excess tokens are swept by admin\n */\n event TokenSwept(address indexed recipient, uint256 amount);\n\n /**\n * @notice Event emitted when flashLoan fee mantissa is updated\n */\n event FlashLoanFeeUpdated(\n uint256 oldFlashLoanFeeMantissa,\n uint256 newFlashLoanFeeMantissa,\n uint256 oldFlashLoanProtocolShare,\n uint256 newFlashLoanProtocolShare\n );\n\n // @notice Thrown when comptroller is not valid\n error InvalidComptroller();\n\n // @notice Thrown when there is already an active flashLoan\n error FlashLoanAlreadyActive();\n\n /// @notice Thrown when flash loan fee exceeds maximum allowed\n error FlashLoanFeeTooHigh(uint256 fee, uint256 maxFee);\n\n /// @notice Thrown when flash loan fee protocol share exceeds maximum allowed\n error FlashLoanProtocolShareTooHigh(uint256 fee, uint256 maxFee);\n\n // @notice Thrown when the vToken does not have enough cash to lend\n error InsufficientCash();\n\n /// @notice Thrown when the repayment amount is insufficient to cover the total fee\n error InsufficientRepayment(uint256 actualAmount, uint256 requiredTotalFee);\n\n /*** User Interface ***/\n\n function transfer(address dst, uint amount) external virtual returns (bool);\n\n function transferFrom(address src, address dst, uint amount) external virtual returns (bool);\n\n function approve(address spender, uint amount) external virtual returns (bool);\n\n function balanceOfUnderlying(address owner) external virtual returns (uint);\n\n function totalBorrowsCurrent() external virtual returns (uint);\n\n function borrowBalanceCurrent(address account) external virtual returns (uint);\n\n function seize(address liquidator, address borrower, uint seizeTokens) external virtual returns (uint);\n\n /*** Admin Function ***/\n function _setPendingAdmin(address payable newPendingAdmin) external virtual returns (uint);\n\n /*** Admin Function ***/\n function _acceptAdmin() external virtual returns (uint);\n\n /*** Admin Function ***/\n function _setReserveFactor(uint newReserveFactorMantissa) external virtual returns (uint);\n\n /*** Admin Function ***/\n function _reduceReserves(uint reduceAmount) external virtual returns (uint);\n\n function balanceOf(address owner) external view virtual returns (uint);\n\n function allowance(address owner, address spender) external view virtual returns (uint);\n\n function getAccountSnapshot(address account) external view virtual returns (uint, uint, uint, uint);\n\n function borrowRatePerBlock() external view virtual returns (uint);\n\n function supplyRatePerBlock() external view virtual returns (uint);\n\n function getCash() external view virtual returns (uint);\n\n function exchangeRateCurrent() public virtual returns (uint);\n\n function accrueInterest() public virtual returns (uint);\n\n /*** Admin Function ***/\n function _setComptroller(ComptrollerInterface newComptroller) public virtual returns (uint);\n\n /*** Admin Function ***/\n function _setInterestRateModel(InterestRateModelV8 newInterestRateModel) public virtual returns (uint);\n\n function borrowBalanceStored(address account) public view virtual returns (uint);\n\n function exchangeRateStored() public view virtual returns (uint);\n}\n\ninterface VBep20Interface {\n /*** User Interface ***/\n\n function mint(uint mintAmount) external returns (uint);\n\n function mintBehalf(address receiver, uint mintAmount) external returns (uint);\n\n function redeem(uint redeemTokens) external returns (uint);\n\n function redeemUnderlying(uint redeemAmount) external returns (uint);\n\n function borrow(uint borrowAmount) external returns (uint);\n\n function repayBorrow(uint repayAmount) external returns (uint);\n\n function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint);\n\n function liquidateBorrow(\n address borrower,\n uint repayAmount,\n VTokenInterface vTokenCollateral\n ) external returns (uint);\n\n /*** Admin Functions ***/\n\n function _addReserves(uint addAmount) external returns (uint);\n}\n\ninterface VDelegatorInterface {\n /**\n * @notice Emitted when implementation is changed\n */\n event NewImplementation(address oldImplementation, address newImplementation);\n\n /**\n * @notice Called by the admin to update the implementation of the delegator\n * @param implementation_ The address of the new implementation for delegation\n * @param allowResign Flag to indicate whether to call _resignImplementation on the old implementation\n * @param becomeImplementationData The encoded bytes data to be passed to _becomeImplementation\n */\n function _setImplementation(\n address implementation_,\n bool allowResign,\n bytes memory becomeImplementationData\n ) external;\n}\n\ninterface VDelegateInterface {\n /**\n * @notice Called by the delegator on a delegate to initialize it for duty\n * @dev Should revert if any issues arise which make it unfit for delegation\n * @param data The encoded bytes data for any initialization\n */\n function _becomeImplementation(bytes memory data) external;\n\n /**\n * @notice Called by the delegator on a delegate to forfeit its responsibility\n */\n function _resignImplementation() external;\n}\n" + }, + "contracts/Utils/CarefulMath.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n/**\n * @title Careful Math\n * @author Venus\n * @notice Derived from OpenZeppelin's SafeMath library\n * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol\n */\ncontract CarefulMath {\n /**\n * @dev Possible error codes that we can return\n */\n enum MathError {\n NO_ERROR,\n DIVISION_BY_ZERO,\n INTEGER_OVERFLOW,\n INTEGER_UNDERFLOW\n }\n\n /**\n * @dev Multiplies two numbers, returns an error on overflow.\n */\n function mulUInt(uint a, uint b) internal pure returns (MathError, uint) {\n if (a == 0) {\n return (MathError.NO_ERROR, 0);\n }\n\n uint c;\n unchecked {\n c = a * b;\n }\n\n if (c / a != b) {\n return (MathError.INTEGER_OVERFLOW, 0);\n } else {\n return (MathError.NO_ERROR, c);\n }\n }\n\n /**\n * @dev Integer division of two numbers, truncating the quotient.\n */\n function divUInt(uint a, uint b) internal pure returns (MathError, uint) {\n if (b == 0) {\n return (MathError.DIVISION_BY_ZERO, 0);\n }\n\n return (MathError.NO_ERROR, a / b);\n }\n\n /**\n * @dev Subtracts two numbers, returns an error on overflow (i.e. if subtrahend is greater than minuend).\n */\n function subUInt(uint a, uint b) internal pure returns (MathError, uint) {\n if (b <= a) {\n unchecked {\n return (MathError.NO_ERROR, a - b);\n }\n } else {\n return (MathError.INTEGER_UNDERFLOW, 0);\n }\n }\n\n /**\n * @dev Adds two numbers, returns an error on overflow.\n */\n function addUInt(uint a, uint b) internal pure returns (MathError, uint) {\n uint c;\n unchecked {\n c = a + b;\n }\n\n if (c >= a) {\n return (MathError.NO_ERROR, c);\n } else {\n return (MathError.INTEGER_OVERFLOW, 0);\n }\n }\n\n /**\n * @dev add a and b and then subtract c\n */\n function addThenSubUInt(uint a, uint b, uint c) internal pure returns (MathError, uint) {\n (MathError err0, uint sum) = addUInt(a, b);\n\n if (err0 != MathError.NO_ERROR) {\n return (err0, 0);\n }\n\n return subUInt(sum, c);\n }\n}\n" + }, + "contracts/Utils/ErrorReporter.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { WeightFunction } from \"../Comptroller/Diamond/interfaces/IFacetBase.sol\";\n\ncontract ComptrollerErrorReporter {\n /// @notice Thrown when You are already in the selected pool.\n error AlreadyInSelectedPool();\n\n /// @notice Thrown when One or more of your assets are not compatible with the selected pool.\n error IncompatibleBorrowedAssets();\n\n /// @notice Thrown when Switching to this pool would fail the liquidity check or lead to liquidation.\n error LiquidityCheckFailed(uint256 errorCode, uint256 shortfall);\n\n /// @notice Thrown when trying to call pool-specific methods on the Core Pool\n error InvalidOperationForCorePool();\n\n /// @notice Thrown when input array lengths do not match\n error ArrayLengthMismatch();\n\n /// @notice Thrown when market trying to add in a pool is not listed in the core pool\n error MarketNotListedInCorePool();\n\n /// @notice Thrown when market is not set in the _poolMarkets mapping\n error MarketConfigNotFound();\n\n /// @notice Thrown when borrowing is not allowed in the selected pool for a given market.\n error BorrowNotAllowedInPool();\n\n /// @notice Thrown when trying to remove a market that is not listed in the given pool.\n error PoolMarketNotFound(uint96 poolId, address vToken);\n\n /// @notice Thrown when a given pool ID does not exist\n error PoolDoesNotExist(uint96 poolId);\n\n /// @notice Thrown when the pool label is empty\n error EmptyPoolLabel();\n\n /// @notice Thrown when a vToken is already listed in the specified pool\n error MarketAlreadyListed(uint96 poolId, address vToken);\n\n /// @notice Thrown when an invalid weighting strategy is provided\n error InvalidWeightingStrategy(WeightFunction strategy);\n\n // @notice Thrown when no assets are requested for flash loan\n error NoAssetsRequested();\n\n // @notice Thrown when invalid flash loan parameters are provided\n error InvalidFlashLoanParams();\n\n // @notice Thrown when flash loan is not enabled on the vToken\n error FlashLoanNotEnabled();\n\n // @notice Thrown when the sender is not authorized to use flashloan onBehalfOf\n error SenderNotAuthorizedForFlashLoan(address sender);\n\n // @notice Thrown when the onBehalfOf didn't approve the contract that receives flashloan\n error NotAnApprovedDelegate();\n\n // @notice Thrown when executeOperation on the receiver contract fails\n error ExecuteFlashLoanFailed();\n\n // @notice Thrown when the requested amount is zero\n error InvalidAmount();\n\n // @notice Thrown when failing to create a debt position in mode 1\n error FailedToCreateDebtPosition();\n\n /// @notice Thrown when attempting to interact with an inactive pool\n error InactivePool(uint96 poolId);\n\n /// @notice Thrown when repayment amount is insufficient to cover the fee\n error NotEnoughRepayment(uint256 repaid, uint256 required);\n\n /// @notice Thrown when the vToken market is not listed\n error MarketNotListed(address vToken);\n\n /// @notice Thrown when flash loans are paused system-wide\n error FlashLoanPausedSystemWide();\n\n /// @notice Thrown when too many assets are requested in a single flash loan\n error TooManyAssetsRequested(uint256 requested, uint256 maximum);\n\n enum Error {\n NO_ERROR,\n UNAUTHORIZED,\n COMPTROLLER_MISMATCH,\n INSUFFICIENT_SHORTFALL,\n INSUFFICIENT_LIQUIDITY,\n INVALID_CLOSE_FACTOR,\n INVALID_COLLATERAL_FACTOR,\n INVALID_LIQUIDATION_INCENTIVE,\n MARKET_NOT_ENTERED, // no longer possible\n MARKET_NOT_LISTED,\n MARKET_ALREADY_LISTED,\n MATH_ERROR,\n NONZERO_BORROW_BALANCE,\n PRICE_ERROR,\n REJECTION,\n SNAPSHOT_ERROR,\n TOO_MANY_ASSETS,\n TOO_MUCH_REPAY,\n INSUFFICIENT_BALANCE_FOR_VAI,\n MARKET_NOT_COLLATERAL,\n INVALID_LIQUIDATION_THRESHOLD\n }\n\n enum FailureInfo {\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\n ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK,\n EXIT_MARKET_BALANCE_OWED,\n EXIT_MARKET_REJECTION,\n SET_CLOSE_FACTOR_OWNER_CHECK,\n SET_CLOSE_FACTOR_VALIDATION,\n SET_COLLATERAL_FACTOR_OWNER_CHECK,\n SET_COLLATERAL_FACTOR_NO_EXISTS,\n SET_COLLATERAL_FACTOR_VALIDATION,\n SET_COLLATERAL_FACTOR_WITHOUT_PRICE,\n SET_IMPLEMENTATION_OWNER_CHECK,\n SET_LIQUIDATION_INCENTIVE_OWNER_CHECK,\n SET_LIQUIDATION_INCENTIVE_VALIDATION,\n SET_MAX_ASSETS_OWNER_CHECK,\n SET_PENDING_ADMIN_OWNER_CHECK,\n SET_PENDING_IMPLEMENTATION_OWNER_CHECK,\n SET_PRICE_ORACLE_OWNER_CHECK,\n SUPPORT_MARKET_EXISTS,\n SUPPORT_MARKET_OWNER_CHECK,\n SET_PAUSE_GUARDIAN_OWNER_CHECK,\n SET_VAI_MINT_RATE_CHECK,\n SET_VAICONTROLLER_OWNER_CHECK,\n SET_MINTED_VAI_REJECTION,\n SET_TREASURY_OWNER_CHECK,\n UNLIST_MARKET_NOT_LISTED,\n SET_LIQUIDATION_THRESHOLD_VALIDATION,\n COLLATERAL_FACTOR_GREATER_THAN_LIQUIDATION_THRESHOLD\n }\n\n /**\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\n **/\n event Failure(uint error, uint info, uint detail);\n\n /**\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\n */\n function fail(Error err, FailureInfo info) internal returns (uint) {\n emit Failure(uint(err), uint(info), 0);\n\n return uint(err);\n }\n\n /**\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\n */\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\n emit Failure(uint(err), uint(info), opaqueError);\n\n return uint(err);\n }\n}\n\ncontract TokenErrorReporter {\n enum Error {\n NO_ERROR,\n UNAUTHORIZED,\n BAD_INPUT,\n COMPTROLLER_REJECTION,\n COMPTROLLER_CALCULATION_ERROR,\n INTEREST_RATE_MODEL_ERROR,\n INVALID_ACCOUNT_PAIR,\n INVALID_CLOSE_AMOUNT_REQUESTED,\n INVALID_COLLATERAL_FACTOR,\n MATH_ERROR,\n MARKET_NOT_FRESH,\n MARKET_NOT_LISTED,\n TOKEN_INSUFFICIENT_ALLOWANCE,\n TOKEN_INSUFFICIENT_BALANCE,\n TOKEN_INSUFFICIENT_CASH,\n TOKEN_TRANSFER_IN_FAILED,\n TOKEN_TRANSFER_OUT_FAILED,\n TOKEN_PRICE_ERROR\n }\n\n /*\n * Note: FailureInfo (but not Error) is kept in alphabetical order\n * This is because FailureInfo grows significantly faster, and\n * the order of Error has some meaning, while the order of FailureInfo\n * is entirely arbitrary.\n */\n enum FailureInfo {\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\n ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED,\n ACCRUE_INTEREST_BORROW_RATE_CALCULATION_FAILED,\n ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED,\n ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED,\n ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED,\n ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED,\n BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\n BORROW_ACCRUE_INTEREST_FAILED,\n BORROW_CASH_NOT_AVAILABLE,\n BORROW_FRESHNESS_CHECK,\n BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED,\n BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED,\n BORROW_MARKET_NOT_LISTED,\n BORROW_COMPTROLLER_REJECTION,\n LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED,\n LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED,\n LIQUIDATE_COLLATERAL_FRESHNESS_CHECK,\n LIQUIDATE_COMPTROLLER_REJECTION,\n LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED,\n LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX,\n LIQUIDATE_CLOSE_AMOUNT_IS_ZERO,\n LIQUIDATE_FRESHNESS_CHECK,\n LIQUIDATE_LIQUIDATOR_IS_BORROWER,\n LIQUIDATE_REPAY_BORROW_FRESH_FAILED,\n LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED,\n LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED,\n LIQUIDATE_SEIZE_COMPTROLLER_REJECTION,\n LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER,\n LIQUIDATE_SEIZE_TOO_MUCH,\n MINT_ACCRUE_INTEREST_FAILED,\n MINT_COMPTROLLER_REJECTION,\n MINT_EXCHANGE_CALCULATION_FAILED,\n MINT_EXCHANGE_RATE_READ_FAILED,\n MINT_FRESHNESS_CHECK,\n MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED,\n MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED,\n MINT_TRANSFER_IN_FAILED,\n MINT_TRANSFER_IN_NOT_POSSIBLE,\n REDEEM_ACCRUE_INTEREST_FAILED,\n REDEEM_COMPTROLLER_REJECTION,\n REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED,\n REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED,\n REDEEM_EXCHANGE_RATE_READ_FAILED,\n REDEEM_FRESHNESS_CHECK,\n REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED,\n REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED,\n REDEEM_TRANSFER_OUT_NOT_POSSIBLE,\n REDUCE_RESERVES_ACCRUE_INTEREST_FAILED,\n REDUCE_RESERVES_ADMIN_CHECK,\n REDUCE_RESERVES_CASH_NOT_AVAILABLE,\n REDUCE_RESERVES_FRESH_CHECK,\n REDUCE_RESERVES_VALIDATION,\n REPAY_BEHALF_ACCRUE_INTEREST_FAILED,\n REPAY_BORROW_ACCRUE_INTEREST_FAILED,\n REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED,\n REPAY_BORROW_COMPTROLLER_REJECTION,\n REPAY_BORROW_FRESHNESS_CHECK,\n REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED,\n REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED,\n REPAY_BORROW_TRANSFER_IN_NOT_POSSIBLE,\n SET_COLLATERAL_FACTOR_OWNER_CHECK,\n SET_COLLATERAL_FACTOR_VALIDATION,\n SET_COMPTROLLER_OWNER_CHECK,\n SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED,\n SET_INTEREST_RATE_MODEL_FRESH_CHECK,\n SET_INTEREST_RATE_MODEL_OWNER_CHECK,\n SET_MAX_ASSETS_OWNER_CHECK,\n SET_ORACLE_MARKET_NOT_LISTED,\n SET_PENDING_ADMIN_OWNER_CHECK,\n SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED,\n SET_RESERVE_FACTOR_ADMIN_CHECK,\n SET_RESERVE_FACTOR_FRESH_CHECK,\n SET_RESERVE_FACTOR_BOUNDS_CHECK,\n TRANSFER_COMPTROLLER_REJECTION,\n TRANSFER_NOT_ALLOWED,\n TRANSFER_NOT_ENOUGH,\n TRANSFER_TOO_MUCH,\n ADD_RESERVES_ACCRUE_INTEREST_FAILED,\n ADD_RESERVES_FRESH_CHECK,\n ADD_RESERVES_TRANSFER_IN_NOT_POSSIBLE,\n TOKEN_GET_UNDERLYING_PRICE_ERROR,\n REPAY_VAI_COMPTROLLER_REJECTION,\n REPAY_VAI_FRESHNESS_CHECK,\n VAI_MINT_EXCHANGE_CALCULATION_FAILED,\n SFT_MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED\n }\n\n /**\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\n **/\n event Failure(uint error, uint info, uint detail);\n\n /**\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\n */\n function fail(Error err, FailureInfo info) internal returns (uint) {\n emit Failure(uint(err), uint(info), 0);\n\n return uint(err);\n }\n\n /**\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\n */\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\n emit Failure(uint(err), uint(info), opaqueError);\n\n return uint(err);\n }\n}\n\ncontract VAIControllerErrorReporter {\n enum Error {\n NO_ERROR,\n UNAUTHORIZED, // The sender is not authorized to perform this action.\n REJECTION, // The action would violate the comptroller, vaicontroller policy.\n SNAPSHOT_ERROR, // The comptroller could not get the account borrows and exchange rate from the market.\n PRICE_ERROR, // The comptroller could not obtain a required price of an asset.\n MATH_ERROR, // A math calculation error occurred.\n INSUFFICIENT_BALANCE_FOR_VAI // Caller does not have sufficient balance to mint VAI.\n }\n\n enum FailureInfo {\n SET_PENDING_ADMIN_OWNER_CHECK,\n SET_PENDING_IMPLEMENTATION_OWNER_CHECK,\n SET_COMPTROLLER_OWNER_CHECK,\n ACCEPT_ADMIN_PENDING_ADMIN_CHECK,\n ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK,\n VAI_MINT_REJECTION,\n VAI_BURN_REJECTION,\n VAI_LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED,\n VAI_LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED,\n VAI_LIQUIDATE_COLLATERAL_FRESHNESS_CHECK,\n VAI_LIQUIDATE_COMPTROLLER_REJECTION,\n VAI_LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED,\n VAI_LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX,\n VAI_LIQUIDATE_CLOSE_AMOUNT_IS_ZERO,\n VAI_LIQUIDATE_FRESHNESS_CHECK,\n VAI_LIQUIDATE_LIQUIDATOR_IS_BORROWER,\n VAI_LIQUIDATE_REPAY_BORROW_FRESH_FAILED,\n VAI_LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED,\n VAI_LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED,\n VAI_LIQUIDATE_SEIZE_COMPTROLLER_REJECTION,\n VAI_LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER,\n VAI_LIQUIDATE_SEIZE_TOO_MUCH,\n MINT_FEE_CALCULATION_FAILED,\n SET_TREASURY_OWNER_CHECK\n }\n\n /**\n * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary\n * contract-specific code that enables us to report opaque error codes from upgradeable contracts.\n **/\n event Failure(uint error, uint info, uint detail);\n\n /**\n * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator\n */\n function fail(Error err, FailureInfo info) internal returns (uint) {\n emit Failure(uint(err), uint(info), 0);\n\n return uint(err);\n }\n\n /**\n * @dev use this when reporting an opaque error from an upgradeable collaborator contract\n */\n function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) {\n emit Failure(uint(err), uint(info), opaqueError);\n\n return uint(err);\n }\n}\n" + }, + "contracts/Utils/Exponential.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CarefulMath } from \"./CarefulMath.sol\";\nimport { ExponentialNoError } from \"./ExponentialNoError.sol\";\n\n/**\n * @title Exponential module for storing fixed-precision decimals\n * @author Venus\n * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places.\n * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is:\n * `Exp({mantissa: 5100000000000000000})`.\n */\ncontract Exponential is CarefulMath, ExponentialNoError {\n /**\n * @dev Creates an exponential from numerator and denominator values.\n * Note: Returns an error if (`num` * 10e18) > MAX_INT,\n * or if `denom` is zero.\n */\n function getExp(uint num, uint denom) internal pure returns (MathError, Exp memory) {\n (MathError err0, uint scaledNumerator) = mulUInt(num, expScale);\n if (err0 != MathError.NO_ERROR) {\n return (err0, Exp({ mantissa: 0 }));\n }\n\n (MathError err1, uint rational) = divUInt(scaledNumerator, denom);\n if (err1 != MathError.NO_ERROR) {\n return (err1, Exp({ mantissa: 0 }));\n }\n\n return (MathError.NO_ERROR, Exp({ mantissa: rational }));\n }\n\n /**\n * @dev Adds two exponentials, returning a new exponential.\n */\n function addExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\n (MathError error, uint result) = addUInt(a.mantissa, b.mantissa);\n\n return (error, Exp({ mantissa: result }));\n }\n\n /**\n * @dev Subtracts two exponentials, returning a new exponential.\n */\n function subExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\n (MathError error, uint result) = subUInt(a.mantissa, b.mantissa);\n\n return (error, Exp({ mantissa: result }));\n }\n\n /**\n * @dev Multiply an Exp by a scalar, returning a new Exp.\n */\n function mulScalar(Exp memory a, uint scalar) internal pure returns (MathError, Exp memory) {\n (MathError err0, uint scaledMantissa) = mulUInt(a.mantissa, scalar);\n if (err0 != MathError.NO_ERROR) {\n return (err0, Exp({ mantissa: 0 }));\n }\n\n return (MathError.NO_ERROR, Exp({ mantissa: scaledMantissa }));\n }\n\n /**\n * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer.\n */\n function mulScalarTruncate(Exp memory a, uint scalar) internal pure returns (MathError, uint) {\n (MathError err, Exp memory product) = mulScalar(a, scalar);\n if (err != MathError.NO_ERROR) {\n return (err, 0);\n }\n\n return (MathError.NO_ERROR, truncate(product));\n }\n\n /**\n * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer.\n */\n function mulScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) internal pure returns (MathError, uint) {\n (MathError err, Exp memory product) = mulScalar(a, scalar);\n if (err != MathError.NO_ERROR) {\n return (err, 0);\n }\n\n return addUInt(truncate(product), addend);\n }\n\n /**\n * @dev Divide an Exp by a scalar, returning a new Exp.\n */\n function divScalar(Exp memory a, uint scalar) internal pure returns (MathError, Exp memory) {\n (MathError err0, uint descaledMantissa) = divUInt(a.mantissa, scalar);\n if (err0 != MathError.NO_ERROR) {\n return (err0, Exp({ mantissa: 0 }));\n }\n\n return (MathError.NO_ERROR, Exp({ mantissa: descaledMantissa }));\n }\n\n /**\n * @dev Divide a scalar by an Exp, returning a new Exp.\n */\n function divScalarByExp(uint scalar, Exp memory divisor) internal pure returns (MathError, Exp memory) {\n /*\n We are doing this as:\n getExp(mulUInt(expScale, scalar), divisor.mantissa)\n\n How it works:\n Exp = a / b;\n Scalar = s;\n `s / (a / b)` = `b * s / a` and since for an Exp `a = mantissa, b = expScale`\n */\n (MathError err0, uint numerator) = mulUInt(expScale, scalar);\n if (err0 != MathError.NO_ERROR) {\n return (err0, Exp({ mantissa: 0 }));\n }\n return getExp(numerator, divisor.mantissa);\n }\n\n /**\n * @dev Divide a scalar by an Exp, then truncate to return an unsigned integer.\n */\n function divScalarByExpTruncate(uint scalar, Exp memory divisor) internal pure returns (MathError, uint) {\n (MathError err, Exp memory fraction) = divScalarByExp(scalar, divisor);\n if (err != MathError.NO_ERROR) {\n return (err, 0);\n }\n\n return (MathError.NO_ERROR, truncate(fraction));\n }\n\n /**\n * @dev Multiplies two exponentials, returning a new exponential.\n */\n function mulExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\n (MathError err0, uint doubleScaledProduct) = mulUInt(a.mantissa, b.mantissa);\n if (err0 != MathError.NO_ERROR) {\n return (err0, Exp({ mantissa: 0 }));\n }\n\n // We add half the scale before dividing so that we get rounding instead of truncation.\n // See \"Listing 6\" and text above it at https://accu.org/index.php/journals/1717\n // Without this change, a result like 6.6...e-19 will be truncated to 0 instead of being rounded to 1e-18.\n (MathError err1, uint doubleScaledProductWithHalfScale) = addUInt(halfExpScale, doubleScaledProduct);\n if (err1 != MathError.NO_ERROR) {\n return (err1, Exp({ mantissa: 0 }));\n }\n\n (MathError err2, uint product) = divUInt(doubleScaledProductWithHalfScale, expScale);\n // The only error `div` can return is MathError.DIVISION_BY_ZERO but we control `expScale` and it is not zero.\n assert(err2 == MathError.NO_ERROR);\n\n return (MathError.NO_ERROR, Exp({ mantissa: product }));\n }\n\n /**\n * @dev Multiplies two exponentials given their mantissas, returning a new exponential.\n */\n function mulExp(uint a, uint b) internal pure returns (MathError, Exp memory) {\n return mulExp(Exp({ mantissa: a }), Exp({ mantissa: b }));\n }\n\n /**\n * @dev Multiplies three exponentials, returning a new exponential.\n */\n function mulExp3(Exp memory a, Exp memory b, Exp memory c) internal pure returns (MathError, Exp memory) {\n (MathError err, Exp memory ab) = mulExp(a, b);\n if (err != MathError.NO_ERROR) {\n return (err, ab);\n }\n return mulExp(ab, c);\n }\n\n /**\n * @dev Divides two exponentials, returning a new exponential.\n * (a/scale) / (b/scale) = (a/scale) * (scale/b) = a/b,\n * which we can scale as an Exp by calling getExp(a.mantissa, b.mantissa)\n */\n function divExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {\n return getExp(a.mantissa, b.mantissa);\n }\n}\n" + }, + "contracts/Utils/ExponentialNoError.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n/**\n * @title Exponential module for storing fixed-precision decimals\n * @author Compound\n * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places.\n * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is:\n * `Exp({mantissa: 5100000000000000000})`.\n */\ncontract ExponentialNoError {\n uint internal constant expScale = 1e18;\n uint internal constant doubleScale = 1e36;\n uint internal constant halfExpScale = expScale / 2;\n uint internal constant mantissaOne = expScale;\n\n struct Exp {\n uint mantissa;\n }\n\n struct Double {\n uint mantissa;\n }\n\n /**\n * @dev Truncates the given exp to a whole number value.\n * For example, truncate(Exp{mantissa: 15 * expScale}) = 15\n */\n function truncate(Exp memory exp) internal pure returns (uint) {\n // Note: We are not using careful math here as we're performing a division that cannot fail\n return exp.mantissa / expScale;\n }\n\n /**\n * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer.\n */\n function mul_ScalarTruncate(Exp memory a, uint scalar) internal pure returns (uint) {\n Exp memory product = mul_(a, scalar);\n return truncate(product);\n }\n\n /**\n * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer.\n */\n function mul_ScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) internal pure returns (uint) {\n Exp memory product = mul_(a, scalar);\n return add_(truncate(product), addend);\n }\n\n /**\n * @dev Checks if first Exp is less than second Exp.\n */\n function lessThanExp(Exp memory left, Exp memory right) internal pure returns (bool) {\n return left.mantissa < right.mantissa;\n }\n\n /**\n * @dev Checks if left Exp <= right Exp.\n */\n function lessThanOrEqualExp(Exp memory left, Exp memory right) internal pure returns (bool) {\n return left.mantissa <= right.mantissa;\n }\n\n /**\n * @dev Checks if left Exp > right Exp.\n */\n function greaterThanExp(Exp memory left, Exp memory right) internal pure returns (bool) {\n return left.mantissa > right.mantissa;\n }\n\n /**\n * @dev returns true if Exp is exactly zero\n */\n function isZeroExp(Exp memory value) internal pure returns (bool) {\n return value.mantissa == 0;\n }\n\n function safe224(uint n, string memory errorMessage) internal pure returns (uint224) {\n require(n < 2 ** 224, errorMessage);\n return uint224(n);\n }\n\n function safe32(uint n, string memory errorMessage) internal pure returns (uint32) {\n require(n < 2 ** 32, errorMessage);\n return uint32(n);\n }\n\n function add_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\n return Exp({ mantissa: add_(a.mantissa, b.mantissa) });\n }\n\n function add_(Double memory a, Double memory b) internal pure returns (Double memory) {\n return Double({ mantissa: add_(a.mantissa, b.mantissa) });\n }\n\n function add_(uint a, uint b) internal pure returns (uint) {\n return add_(a, b, \"addition overflow\");\n }\n\n function add_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\n uint c = a + b;\n require(c >= a, errorMessage);\n return c;\n }\n\n function sub_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\n return Exp({ mantissa: sub_(a.mantissa, b.mantissa) });\n }\n\n function sub_(Double memory a, Double memory b) internal pure returns (Double memory) {\n return Double({ mantissa: sub_(a.mantissa, b.mantissa) });\n }\n\n function sub_(uint a, uint b) internal pure returns (uint) {\n return sub_(a, b, \"subtraction underflow\");\n }\n\n function sub_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\n require(b <= a, errorMessage);\n return a - b;\n }\n\n function mul_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\n return Exp({ mantissa: mul_(a.mantissa, b.mantissa) / expScale });\n }\n\n function mul_(Exp memory a, uint b) internal pure returns (Exp memory) {\n return Exp({ mantissa: mul_(a.mantissa, b) });\n }\n\n function mul_(uint a, Exp memory b) internal pure returns (uint) {\n return mul_(a, b.mantissa) / expScale;\n }\n\n function mul_(Double memory a, Double memory b) internal pure returns (Double memory) {\n return Double({ mantissa: mul_(a.mantissa, b.mantissa) / doubleScale });\n }\n\n function mul_(Double memory a, uint b) internal pure returns (Double memory) {\n return Double({ mantissa: mul_(a.mantissa, b) });\n }\n\n function mul_(uint a, Double memory b) internal pure returns (uint) {\n return mul_(a, b.mantissa) / doubleScale;\n }\n\n function mul_(uint a, uint b) internal pure returns (uint) {\n return mul_(a, b, \"multiplication overflow\");\n }\n\n function mul_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\n if (a == 0 || b == 0) {\n return 0;\n }\n uint c = a * b;\n require(c / a == b, errorMessage);\n return c;\n }\n\n function div_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {\n return Exp({ mantissa: div_(mul_(a.mantissa, expScale), b.mantissa) });\n }\n\n function div_(Exp memory a, uint b) internal pure returns (Exp memory) {\n return Exp({ mantissa: div_(a.mantissa, b) });\n }\n\n function div_(uint a, Exp memory b) internal pure returns (uint) {\n return div_(mul_(a, expScale), b.mantissa);\n }\n\n function div_(Double memory a, Double memory b) internal pure returns (Double memory) {\n return Double({ mantissa: div_(mul_(a.mantissa, doubleScale), b.mantissa) });\n }\n\n function div_(Double memory a, uint b) internal pure returns (Double memory) {\n return Double({ mantissa: div_(a.mantissa, b) });\n }\n\n function div_(uint a, Double memory b) internal pure returns (uint) {\n return div_(mul_(a, doubleScale), b.mantissa);\n }\n\n function div_(uint a, uint b) internal pure returns (uint) {\n return div_(a, b, \"divide by zero\");\n }\n\n function div_(uint a, uint b, string memory errorMessage) internal pure returns (uint) {\n require(b > 0, errorMessage);\n return a / b;\n }\n\n function fraction(uint a, uint b) internal pure returns (Double memory) {\n return Double({ mantissa: div_(mul_(a, doubleScale), b) });\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "cancun", + "outputSelection": { + "*": { + "*": [ + "storageLayout", + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "evm.gasEstimates" + ], + "": ["ast"] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} From 4ba70aa492bd58d2363c20b25f98f960fe7539f9 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 20 Jul 2026 19:24:29 +0530 Subject: [PATCH 77/78] docs(audits): add HashDit BStockLiquidator audit report --- .../160_BStockLiquidator_Hashdit_20260720.pdf | Bin 0 -> 289165 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 audits/160_BStockLiquidator_Hashdit_20260720.pdf diff --git a/audits/160_BStockLiquidator_Hashdit_20260720.pdf b/audits/160_BStockLiquidator_Hashdit_20260720.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0da3657ba6f8f9dfce4b949f5c17ea11887016de GIT binary patch literal 289165 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�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∋5VU9 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 z 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 JOI9uw|1U_@oGbtU literal 0 HcmV?d00001 From c6880065a52d8909eaf1bce2aea83cd775c8aa24 Mon Sep 17 00:00:00 2001 From: fred-venus <234124932+fred-venus@users.noreply.github.com> Date: Tue, 21 Jul 2026 03:15:02 +0000 Subject: [PATCH 78/78] feat: updating deployment files --- deployments/bscmainnet.json | 188 +++++++++++++++++++++++++- deployments/bscmainnet_addresses.json | 6 +- 2 files changed, 188 insertions(+), 6 deletions(-) diff --git a/deployments/bscmainnet.json b/deployments/bscmainnet.json index b3b0621a0..b016a451b 100644 --- a/deployments/bscmainnet.json +++ b/deployments/bscmainnet.json @@ -2123,7 +2123,7 @@ ] }, "BStockLiquidator": { - "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "abi": [ { "anonymous": false, @@ -2275,6 +2275,11 @@ "name": "DeadlineExpired", "type": "error" }, + { + "inputs": [], + "name": "FlashNotSupportedForVai", + "type": "error" + }, { "inputs": [ { @@ -2333,6 +2338,17 @@ "name": "RouterNotAllowed", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "SpenderNotContract", + "type": "error" + }, { "inputs": [], "name": "SwapFailed", @@ -2472,6 +2488,25 @@ "name": "OwnershipTransferred", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "PartialSwapLeftover", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -2491,6 +2526,25 @@ "name": "RouterSet", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "RouterSpenderSet", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -2834,6 +2888,25 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "routerSpender", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -2870,6 +2943,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "setRouterSpender", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -2987,7 +3078,7 @@ ] }, "BStockLiquidator_Implementation": { - "address": "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", + "address": "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", "abi": [ { "inputs": [ @@ -3042,6 +3133,11 @@ "name": "DeadlineExpired", "type": "error" }, + { + "inputs": [], + "name": "FlashNotSupportedForVai", + "type": "error" + }, { "inputs": [ { @@ -3100,6 +3196,17 @@ "name": "RouterNotAllowed", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "SpenderNotContract", + "type": "error" + }, { "inputs": [], "name": "SwapFailed", @@ -3239,6 +3346,25 @@ "name": "OwnershipTransferred", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "PartialSwapLeftover", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -3258,6 +3384,25 @@ "name": "RouterSet", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "RouterSpenderSet", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -3601,6 +3746,25 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "routerSpender", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -3637,6 +3801,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "setRouterSpender", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -3737,7 +3919,7 @@ ] }, "BStockLiquidator_Proxy": { - "address": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "abi": [ { "inputs": [ diff --git a/deployments/bscmainnet_addresses.json b/deployments/bscmainnet_addresses.json index 9d686cdcc..c4cf0aa71 100644 --- a/deployments/bscmainnet_addresses.json +++ b/deployments/bscmainnet_addresses.json @@ -6,9 +6,9 @@ "ADA": "0x3EE2200Efb3400fAbB9AacF31297cBdD1d435D47", "BCH": "0x8fF795a6F4D97E7887C79beA79aba5cc76444aDf", "BETH": "0x250632378E573c6Be1AC2f97Fcdf00515d0Aa91B", - "BStockLiquidator": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", - "BStockLiquidator_Implementation": "0x883C36BADe4BAbE393feF2FcfcFC24A5e8E1A3D5", - "BStockLiquidator_Proxy": "0xF03C90e6BF66b43411189Ad848F17723f8B4A3c1", + "BStockLiquidator": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "BStockLiquidator_Implementation": "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", + "BStockLiquidator_Proxy": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "BTCB": "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c", "BUSD": "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", "CAKE": "0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82",