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/.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/audits/160_BStockLiquidator_Hashdit_20260720.pdf b/audits/160_BStockLiquidator_Hashdit_20260720.pdf new file mode 100644 index 000000000..0da3657ba Binary files /dev/null and b/audits/160_BStockLiquidator_Hashdit_20260720.pdf differ diff --git a/contracts/BStock/BStockLiquidator.sol b/contracts/BStock/BStockLiquidator.sol new file mode 100644 index 000000000..69030bb86 --- /dev/null +++ b/contracts/BStock/BStockLiquidator.sol @@ -0,0 +1,520 @@ +// 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"; +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, IVAIController } 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 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. + */ +contract BStockLiquidator is + Ownable2StepUpgradeable, + ReentrancyGuardUpgradeable, + IBStockLiquidator, + IFlashLoanReceiver +{ + using SafeERC20Upgradeable for IERC20Upgradeable; + + /// @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; + + /// @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; + + /// @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[49] private __gap; + + modifier onlyOperator() { + if (msg.sender != owner() && !isOperator[msg.sender]) revert NotOperator(); + _; + } + + /// @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_, 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(); + } + + /// @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); + } + + /// @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 // + // --------------------------------------------------------------------- // + + /// @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; + // 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 { + // 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. 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); + } + + /// @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); + } + + /// @inheritdoc IBStockLiquidator + /// @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(); + 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 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 {} + + // --------------------------------------------------------------------- // + // INVENTORY mode // + // --------------------------------------------------------------------- // + + /// @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) { + _validateRouters(params.router, 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( + params.borrower, + address(params.vBStock), + address(params.vDebt), + params.repayAmount, + seizedBStock, + debtOut, + false + ); + } + + // --------------------------------------------------------------------- // + // FLASH mode // + // --------------------------------------------------------------------- // + + /// @inheritdoc IBStockLiquidator + function flashLiquidate(LiquidationParams calldata params) external override onlyOperator nonReentrant { + _validateRouters(params.router, params.router2); + + 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); + vTokens[0] = (address(params.vDebt) == vBNB) ? vWBNB : IVToken(address(params.vDebt)); + uint256[] memory amounts = new uint256[](1); + amounts[0] = params.repayAmount; + + comptroller.executeFlashLoan( + payable(address(this)), + payable(address(this)), + vTokens, + amounts, + abi.encode(params) + ); + } + + /// @inheritdoc IFlashLoanReceiver + function executeOperation( + VToken[] calldata vTokens, + uint256[] calldata amounts, + uint256[] calldata premiums, + address initiator, + address /* onBehalf */, + bytes calldata param + ) 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. + if (initiator != address(this)) revert BadInitiator(initiator); + + LiquidationParams memory params = abi.decode(param, (LiquidationParams)); + // 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); + + // 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); + repayAmounts[0] = amounts[0] + premiums[0]; + if (debtOut < repayAmounts[0]) revert InsufficientOut(debtOut, 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, + address(params.vBStock), + address(params.vDebt), + params.repayAmount, + seizedBStock, + debtOut, + true + ); + return (true, repayAmounts); + } + + // --------------------------------------------------------------------- // + // Core // + // --------------------------------------------------------------------- // + + /// @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 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)); + // 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() + // only when the call reverted without returndata. + if (returndata.length != 0) { + assembly { + revert(add(returndata, 0x20), mload(returndata)) + } + } + revert SwapFailed(); + } + 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. + uint256 spent = balBefore - token.balanceOf(address(this)); + if (spent < amount) emit PartialSwapLeftover(address(token), amount - spent); + } + + /** + * @dev The atomic sequence shared by both funding modes: + * approve debt -> liquidateBorrow (seize vBStock) -> redeem (raw bStock) + * -> 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 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) { + // 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; + 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. + // 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(); + ensureNonzeroAddress(gate); + 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 + // (dust or a stray transfer) is excluded — we only sell what this redeem actually returned. + uint256 rawBefore = bStock.balanceOf(address(this)); + uint256 redeemErr = params.vBStock.redeem(seizedV); + if (redeemErr != 0) revert RedeemFailed(redeemErr); + seizedBStock = bStock.balanceOf(address(this)) - rawBefore; + + // 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)); + 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 new file mode 100644 index 000000000..518940c89 --- /dev/null +++ b/contracts/BStock/IBStockLiquidator.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.25; + +import { IVBep20 } from "../InterfacesV8.sol"; + +/// @title IBStockLiquidator +/// @author Venus +/// @notice External API, events, errors and parameter struct for {BStockLiquidator}. +/// @dev The flash-loan callback `executeOperation` is intentionally NOT part of this interface — it is +/// declared by {IFlashLoanReceiver} (owned by the Core flash-loan subsystem) and implemented directly +/// by {BStockLiquidator}, keeping this interface free of the concrete `VToken` dependency. +interface IBStockLiquidator { + /// @notice Parameters for a single liquidation. + /// @dev The swap can be one or two hops. When `router2 == address(0)` it is a single hop + /// (bStock -> debt) and `swapCalldata2` / `intermediateToken` are ignored — behavior is + /// identical to the single-hop version. When `router2` is set it is two hops + /// (bStock -> `intermediateToken` -> debt): hop 1 sells bStock via `router`, hop 2 converts + /// the intermediate to the debt asset via `router2`. `minOut` is always the FINAL debt-asset + /// floor across the whole chain. `deadline` is a unix-timestamp expiry: the call reverts once + /// `block.timestamp` passes it, so a stale tx cannot settle against an expired quote. + struct LiquidationParams { + address borrower; // account to liquidate + IVBep20 vDebt; // borrowed market to repay (e.g. vUSDT) + IVBep20 vBStock; // bStock collateral market to seize (e.g. vTSLAB) + uint256 repayAmount; // debt underlying to repay (its own decimals) + address router; // hop-1 RFQ router (Native firm-quote target, Liquid Mesh router, …) — must be allowlisted + bytes swapCalldata; // hop-1 calldata (off-chain-signed RFQ order): bStock -> intermediate (or -> debt if single-hop) + uint256 minOut; // minimum FINAL debt-asset amount the swap chain must yield, else revert + address router2; // hop-2 router (AMM/aggregator): intermediate -> debt; address(0) = single-hop + bytes swapCalldata2; // hop-2 calldata; the swap recipient inside it MUST be this contract + address intermediateToken; // token hop 1 outputs and hop 2 consumes (e.g. USDT); required when router2 set + uint256 deadline; // unix timestamp after which the call reverts; guards a stale tx sitting in the mempool + } + + /// @notice Emitted when an operator is allowlisted or removed. + event OperatorSet(address indexed operator, bool allowed); + + /// @notice Emitted when a swap router is allowlisted or removed. + event RouterSet(address indexed router, bool allowed); + + /// @notice Emitted when a router's token-approval target (spender) is set or cleared. + event RouterSpenderSet(address indexed router, address indexed spender); + + /// @notice Emitted on a successful liquidation. + /// @param borrower The liquidated account. + /// @param vBStock The seized bStock collateral market. + /// @param vDebt The repaid debt market. + /// @param repayAmount Debt underlying repaid. + /// @param seizedBStock Raw bStock redeemed and sold. + /// @param debtOut Debt-asset proceeds of the swap. + /// @param flash True if funded by a flash loan, false if from inventory. + event Liquidated( + address indexed borrower, + address indexed vBStock, + address indexed vDebt, + uint256 repayAmount, + uint256 seizedBStock, + uint256 debtOut, + bool flash + ); + + /// @notice Emitted when the owner withdraws a token. + event Swept(address indexed token, address indexed to, uint256 amount); + + /// @notice Emitted when the owner withdraws stuck native BNB. + event SweptNative(address indexed to, uint256 amount); + + /// @notice Emitted when a swap hop pulls less than the amount approved to the router, leaving a + /// residual of the input token in the contract (e.g. a partially-filled RFQ quote). The + /// residual is recoverable via `sweep`. + /// @param token The input token left over (bStock on hop 1, the intermediate on hop 2). + /// @param amount The residual amount not consumed by the swap. + event PartialSwapLeftover(address indexed token, uint256 amount); + + /// @notice Thrown when the caller is neither the owner nor an allowlisted operator. + error NotOperator(); + + /// @notice Thrown when the supplied swap router is not allowlisted. + error RouterNotAllowed(address router); + + /// @notice Thrown when a router spender being set is not a deployed contract. The spender receives + /// a live token approval during the swap, so an EOA spender is always a misconfiguration. + error SpenderNotContract(address spender); + + /// @notice Thrown when `vBStock.redeem` returns a non-zero error code. + error RedeemFailed(uint256 errCode); + + /// @notice Thrown when the low-level call to the router reverts. + error SwapFailed(); + + /// @notice Thrown when swap proceeds are below `minOut`. + error InsufficientOut(uint256 got, uint256 minOut); + + /// @notice Thrown when `minOut` is zero: a liquidation must set a non-zero debt-asset floor, + /// else it would silently accept any proceeds (including zero). + error ZeroMinOut(); + + /// @notice Thrown when a two-hop `intermediateToken` is zero, or equals the debt or bStock token. + error InvalidIntermediate(); + + /// @notice Thrown when `executeOperation` is called by something other than the Comptroller. + error OnlyComptroller(); + + /// @notice Thrown when the flash-loan initiator is not this contract. + error BadInitiator(address initiator); + + /// @notice Thrown when the flashed asset does not match `params.vDebt`. + error WrongFlashAsset(); + + /// @notice Thrown when `flashLiquidate` is called with a VAI debt. VAI is minted/burned by the + /// VAIController and has no vToken market to flash from — use `liquidate` (INVENTORY mode) + /// with pre-funded VAI instead. + error FlashNotSupportedForVai(); + + /// @notice Thrown when the call is submitted after `params.deadline`. + error DeadlineExpired(uint256 deadline, uint256 nowTs); + + /// @notice Thrown when a native BNB transfer (the `sweepNative` payout) fails. + error NativeTransferFailed(); + + /// @notice Allow or disallow an address to trigger liquidations. + /// @param operator Address to allowlist or remove. + /// @param allowed True to allow, false to remove. + function setOperator(address operator, bool allowed) external; + + /// @notice Allow or disallow a router as the swap target (e.g. the Native router). + /// @dev Removing a router also clears its `routerSpender` entry (emitting {RouterSpenderSet} with + /// `address(0)`), so a stale spender cannot silently reactivate on a later re-allowlist. + /// @param router Address to allowlist or remove. + /// @param allowed True to allow, false to remove. + function setRouter(address router, bool allowed) external; + + /// @notice Set the token-approval target (spender) for a router whose settlement contract that pulls + /// the input token differs from the call target (e.g. Liquid Mesh). When unset, the approval + /// defaults to the router itself (Native behaviour). Setting `spender = address(0)` clears it. + /// @dev Reverts with {RouterNotAllowed} unless `router` is currently allowlisted, and with + /// {SpenderNotContract} when a non-zero `spender` has no code. The spender is an approval + /// target only — it is never called; the low-level call always targets the allowlisted router. + /// @param router The allowlisted swap target (call target). + /// @param spender The contract that pulls the input token via `transferFrom` during settlement. + function setRouterSpender(address router, address spender) external; + + /// @notice Withdraw any token (profit, leftover inventory, stuck dust) to `to`. + /// @param token Token to withdraw. + /// @param to Recipient. + /// @param amount Amount to withdraw. + function sweep(address token, address to, uint256 amount) external; + + /// @notice Withdraw stuck native BNB (a stray transfer, or a gate refund) to `to`. + /// @param to Recipient. + /// @param amount Amount of native BNB to withdraw. + function sweepNative(address to, uint256 amount) external; + + /// @notice Liquidate using the contract's own debt-asset inventory. + /// @dev The contract must already hold >= `repayAmount` of `vDebt.underlying()`. + /// Profit (proceeds - repay) stays in the contract; withdraw it with `sweep`. + /// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final + /// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt). + /// @return debtOut Debt-asset proceeds realized by the swap chain. + function liquidate(LiquidationParams calldata params) external returns (uint256 debtOut); + + /// @notice Liquidate by flash-borrowing the repay amount from Venus, repaid (+ premium) in the same tx. + /// @dev Requires this contract to be `authorizedFlashLoan` in the Comptroller and `vDebt` flash-enabled. + /// Profit (proceeds - repay - premium) stays in the contract; withdraw it with `sweep`. + /// @param params Liquidation parameters (borrower, markets, repay, hop-1 router + calldata, final + /// minOut, `deadline`, and the optional hop-2 router/calldata/intermediate for non-USDT debt). + function flashLiquidate(LiquidationParams calldata params) external; +} 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/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 new file mode 100644 index 000000000..a7b596d86 --- /dev/null +++ b/contracts/test/BStockLiquidationMocks.sol @@ -0,0 +1,545 @@ +// 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 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; + 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. +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) + 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; + } + + function setLiquidityErr(uint256 e) external { + liquidityErr = e; + } + + function setEffectiveIncentive(uint256 m) external { + effectiveIncentiveMantissa = m; + } + + function setTreasuryPercent(uint256 p) external { + treasuryPercent = p; + } + + function setLiquidatorContract(address l) external { + liquidatorContract = l; + } + + function setFlashPremium(uint256 m) external { + flashPremiumMantissa = m; + } + + function getAccountLiquidity(address) external view returns (uint256, uint256, uint256) { + return (liquidityErr, 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 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 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. 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 effectiveIncentiveMantissa != 0 ? effectiveIncentiveMantissa : 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 */, + 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 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 +/// 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 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 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 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 { + 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 + 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; + } + + /// @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 gate: a vBNB repay is native BNB (msg.value), not an ERC20 transferFrom. + function setVBnb(address v) external { + 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; + } + + function liquidateBorrow( + address vToken, + address /* borrower */, + uint256 repayAmount, + address vTokenCollateral + ) external payable { + if (vToken == vBnb) { + // Native BNB repay: require exact msg.value (mirrors Liquidator.sol) and keep the BNB. + require(msg.value == repayAmount, "bad value"); + } else { + // 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"); + } + 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 + address public vaiController; // unset: `flashLiquidate`'s VAI check reads this and never matches vDebt + + 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/deploy/019-deploy-bstock-liquidator.ts b/deploy/019-deploy-bstock-liquidator.ts new file mode 100644 index 000000000..bc6a79a9b --- /dev/null +++ b/deploy/019-deploy-bstock-liquidator.ts @@ -0,0 +1,83 @@ +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(); + + // 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; + + // 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", { + contract: "BStockLiquidator", + from: deployer, + args: [comptrollerAddress, vBnbAddress, vWbnbAddress, wBnbAddress], + log: true, + autoMine: true, + proxy: { + owner: proxyAdmin, + proxyContract: "OpenZeppelinTransparentProxy", + execute: { + methodName: "initialize", + 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) + // (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, + // 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 +}; + +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.json b/deployments/bscmainnet.json index f8c14bf18..b016a451b 100644 --- a/deployments/bscmainnet.json +++ b/deployments/bscmainnet.json @@ -2122,6 +2122,1951 @@ } ] }, + "BStockLiquidator": { + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "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": [], + "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" + }, + { + "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": "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", + "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" + } + ] + }, + "BStockLiquidator_Proxy": { + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "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/.migrations.json b/deployments/bscmainnet/.migrations.json index 522d782a7..45d003e70 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": 1784553138 } diff --git a/deployments/bscmainnet/BStockLiquidator.json b/deployments/bscmainnet/BStockLiquidator.json new file mode 100644 index 000000000..bebc22c93 --- /dev/null +++ b/deployments/bscmainnet/BStockLiquidator.json @@ -0,0 +1,1082 @@ +{ + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "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": [], + "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" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + } + ], + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "receipt": { + "to": null, + "from": "0x8D43F1776B4d5eB69cDbE2e79899520754a0cd9A", + "contractAddress": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "transactionIndex": 81, + "gasUsed": "797497", + "logsBloom": "0x000000040000000000000000000000004020000000000000008000000000000000000000040000000000000000000800200000000000000000000000000000000000000000000000000000000000020000010000000000000000000000000000000000000200000000000000000008000000008000000000000000001000004000000000000000000000000000000000400000000000c0000000000000800020000000000000000000000000000400000000000000000000000000000000000000000020000000000000000000040000000001000400000000000800000020002000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58", + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "logs": [ + { + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x000000000000000000000000de9734dd7aead610dd41ffe9abc25c5ccf142487" + ], + "data": "0x", + "logIndex": 390, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" + }, + { + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000008d43f1776b4d5eb69cdbe2e79899520754a0cd9a" + ], + "data": "0x", + "logIndex": 391, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" + }, + { + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000008d43f1776b4d5eb69cdbe2e79899520754a0cd9a", + "0x00000000000000000000000083f426233b358a36953f6951161e76fb7c866a7a" + ], + "data": "0x", + "logIndex": 392, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" + }, + { + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logIndex": 393, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" + }, + { + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "topics": ["0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f"], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001bb765b741a5f3c2a338369dab539385534e3343", + "logIndex": 394, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" + } + ], + "blockNumber": 111096720, + "cumulativeGasUsed": "15406242", + "status": 1, + "byzantium": true + }, + "args": [ + "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", + "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": "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", + "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..d6a58cdd3 --- /dev/null +++ b/deployments/bscmainnet/BStockLiquidator_Implementation.json @@ -0,0 +1,1333 @@ +{ + "address": "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", + "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" + } + ], + "transactionHash": "0x86bdd6c0de5e57a8f23ec9a936d7d9dc36cd92615dec7bde78f76e37efa2923a", + "receipt": { + "to": null, + "from": "0x8D43F1776B4d5eB69cDbE2e79899520754a0cd9A", + "contractAddress": "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", + "transactionIndex": 74, + "gasUsed": "2644510", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x82b3c1c03990f9eca06aad80ed06e2c65f9d552d09ecf9470096cde3ee81d840", + "transactionHash": "0x86bdd6c0de5e57a8f23ec9a936d7d9dc36cd92615dec7bde78f76e37efa2923a", + "logs": [ + { + "transactionIndex": 74, + "blockNumber": 111096716, + "transactionHash": "0x86bdd6c0de5e57a8f23ec9a936d7d9dc36cd92615dec7bde78f76e37efa2923a", + "address": "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", + "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], + "data": "0x00000000000000000000000000000000000000000000000000000000000000ff", + "logIndex": 467, + "blockHash": "0x82b3c1c03990f9eca06aad80ed06e2c65f9d552d09ecf9470096cde3ee81d840" + } + ], + "blockNumber": 111096716, + "cumulativeGasUsed": "11995332", + "status": 1, + "byzantium": true + }, + "args": [ + "0xfD36E2c2a6789Db23113685031d7F16329158384", + "0xA07c5b74C9B40447a954e1466938b865b6BBea36", + "0x6bCa74586218dB34cdB402295796b79663d816e9", + "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" + ], + "numDeployments": 1, + "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": { + "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) — 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." + }, + "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 — 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 — 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 — 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`." + }, + "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` — 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": { + "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": 2834, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "isOperator", + "offset": 0, + "slot": "201", + "type": "t_mapping(t_address,t_bool)" + }, + { + "astId": 2839, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "isRouter", + "offset": 0, + "slot": "202", + "type": "t_mapping(t_address,t_bool)" + }, + { + "astId": 2844, + "contract": "contracts/BStock/BStockLiquidator.sol:BStockLiquidator", + "label": "routerSpender", + "offset": 0, + "slot": "203", + "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": { + "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_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", + "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..117f5a100 --- /dev/null +++ b/deployments/bscmainnet/BStockLiquidator_Proxy.json @@ -0,0 +1,271 @@ +{ + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "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": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "receipt": { + "to": null, + "from": "0x8D43F1776B4d5eB69cDbE2e79899520754a0cd9A", + "contractAddress": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "transactionIndex": 81, + "gasUsed": "797497", + "logsBloom": "0x000000040000000000000000000000004020000000000000008000000000000000000000040000000000000000000800200000000000000000000000000000000000000000000000000000000000020000010000000000000000000000000000000000000200000000000000000008000000008000000000000000001000004000000000000000000000000000000000400000000000c0000000000000800020000000000000000000000000000400000000000000000000000000000000000000000020000000000000000000040000000001000400000000000800000020002000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58", + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "logs": [ + { + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x000000000000000000000000de9734dd7aead610dd41ffe9abc25c5ccf142487" + ], + "data": "0x", + "logIndex": 390, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" + }, + { + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000008d43f1776b4d5eb69cdbe2e79899520754a0cd9a" + ], + "data": "0x", + "logIndex": 391, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" + }, + { + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000008d43f1776b4d5eb69cdbe2e79899520754a0cd9a", + "0x00000000000000000000000083f426233b358a36953f6951161e76fb7c866a7a" + ], + "data": "0x", + "logIndex": 392, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" + }, + { + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logIndex": 393, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" + }, + { + "transactionIndex": 81, + "blockNumber": 111096720, + "transactionHash": "0xb785569e5ac565c6a59bbe42c39118274411d16cd738a769a18a930e2eef3347", + "address": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "topics": ["0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f"], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001bb765b741a5f3c2a338369dab539385534e3343", + "logIndex": 394, + "blockHash": "0xb1b6261e7ec2a197c8b50486ea151e499f88f4ab5944e84f2c85abd0c383bb58" + } + ], + "blockNumber": 111096720, + "cumulativeGasUsed": "15406242", + "status": 1, + "byzantium": true + }, + "args": [ + "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", + "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/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 + } + } +} 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 + } + } +} diff --git a/deployments/bscmainnet_addresses.json b/deployments/bscmainnet_addresses.json index e7b98765b..c4cf0aa71 100644 --- a/deployments/bscmainnet_addresses.json +++ b/deployments/bscmainnet_addresses.json @@ -6,6 +6,9 @@ "ADA": "0x3EE2200Efb3400fAbB9AacF31297cBdD1d435D47", "BCH": "0x8fF795a6F4D97E7887C79beA79aba5cc76444aDf", "BETH": "0x250632378E573c6Be1AC2f97Fcdf00515d0Aa91B", + "BStockLiquidator": "0x5974Badab6911a78Ba15229045514C2C1bD42343", + "BStockLiquidator_Implementation": "0xde9734DD7AEad610Dd41FFE9Abc25C5cCf142487", + "BStockLiquidator_Proxy": "0x5974Badab6911a78Ba15229045514C2C1bD42343", "BTCB": "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c", "BUSD": "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", "CAKE": "0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82", 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/scripts/bstock/README.md b/scripts/bstock/README.md new file mode 100644 index 000000000..b55cf9f66 --- /dev/null +++ b/scripts/bstock/README.md @@ -0,0 +1,280 @@ +# 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 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. 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 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: + +- **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 + +- **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 + 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.) + **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). + +### 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. + +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. + +**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 + +```bash +# NATIVE_API_KEY read from .env +TOKEN=TSLAB AMOUNT=5 npx hardhat run scripts/bstock/native-smoke.ts +``` + +```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`. + +--- + +## 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 the hop-1 quote (bStock→USDT) from the best of Native / Liquid +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. + +**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 | +| `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) | + +_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 + +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 +``` + +Then: **Safe → Apps → Transaction Builder → Load** the JSON, review, sign, execute. + +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 | +| -------------- | --- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| `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 | + +> 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. + +--- + +## 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 new file mode 100644 index 000000000..c087a16f3 --- /dev/null +++ b/scripts/bstock/atomic-liquidate.ts @@ -0,0 +1,568 @@ +/** + * 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 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) 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) + * 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. + * + * 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" + * 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. + * 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 (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, + * 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 + * + * 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). + */ +import { BigNumber, Contract, Signer } from "ethers"; +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"; +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)"; +const LIQUIDATOR_ABI = [ + `function liquidate(${PARAMS_TUPLE}) returns (uint256)`, + `function flashLiquidate(${PARAMS_TUPLE})`, + "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,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 { + const v = process.env[name]; + if (required && !v) throw new Error(`Missing env ${name}`); + return v || ""; +} + +interface Hop1 { + source: string; + router: string; + calldata: string; + 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 +} + +/** + * 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. + 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, + calldata: built.calldata, + out: winner.quote.out, + floor: built.builtFloor, + 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 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 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 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. + 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")); + 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); + + // 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). + // 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; + 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); + 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)`); + + // 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? 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 + // 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 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(`${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(`${seizeFn} returned 0 seize for ${borrower}`); + const exchangeRate: BigNumber = await vBStock.exchangeRateStored(); + const ONE = BigNumber.from(10).pow(18); + + // 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) { + throw new Error( + "Venus Liquidator gate (comptroller.liquidatorContract) is unset — the contract routes every repay through it and reverts when unset", + ); + } + + // 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 + // 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)) { + // 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 (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); + 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 + // 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 = 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}`); + + // `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. 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 usdtOut = ethers.utils.getAddress(process.env.USDT_ADDR || BSC_USDT); + const twoHop = debt.address.toLowerCase() !== usdtOut.toLowerCase(); + + let router: string; + let swapCalldata: string; + 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; + // 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 + // 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"); + 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); + swapCalldata2 = data2; + intermediateToken = usdtOut; + } + } else { + // 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, + usdtOut, + humanAmount: seizedHumanQuote, + weiAmount: seizedForQuote, + slippage, + }); + 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 — 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. 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 = isVai + ? await getPsmSwap({ amountIn: midFloor, recipient: liquidator.address }, ethers.provider) + : await getAmmSwap( + { + tokenIn: usdtOut, + tokenOut: debt.address, + amountIn: midFloor.toString(), + recipient: liquidator.address, + slippage, + }, + ethers.provider, + ); + router2 = ethers.utils.getAddress(amm.router); + swapCalldata2 = amm.calldata; + intermediateToken = usdtOut; + 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} ` + + `(floor ${ethers.utils.formatUnits(midFloor, debtDec)}, TTL ${ttl}s, router ${router})`, + ); + } + } + + // 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 = 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, + vDebt: vDebt.address, + vBStock: vBStock.address, + repayAmount: repay, + router, + swapCalldata, + minOut, + router2, + swapCalldata2, + intermediateToken, + deadline, + }; + + // 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.`, + ); + } + } + + // 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 + // `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( + `hop-1 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}`); + 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/amm.ts b/scripts/bstock/lib/amm.ts new file mode 100644 index 000000000..9d269d612 --- /dev/null +++ b/scripts/bstock/lib/amm.ts @@ -0,0 +1,163 @@ +/** + * 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 + // 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 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 +} + +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. + * + * 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. 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)"); + 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/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 new file mode 100644 index 000000000..36c21a991 --- /dev/null +++ b/scripts/bstock/lib/liquidmesh.ts @@ -0,0 +1,232 @@ +/** + * 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"; + +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"; +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) +} + +/** 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; +} + +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"); + // 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" }); +} + +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 fetchWithTimeout(`${host()}${path}`, { headers: headers("GET", path) }, "Liquid Mesh /quote"); + const text = await res.text(); + let body: LmResponse; + try { + body = JSON.parse(text) as LmResponse; + } 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; +} + +/** + * 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 fetchWithTimeout( + `${host()}${path}`, + { method: "POST", headers: headers("POST", path, body), body }, + "Liquid Mesh /swap", + ); + const text = await res.text(); + let j: LmResponse; + try { + j = JSON.parse(text) as LmResponse; + } 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; + 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/lib/native.ts b/scripts/bstock/lib/native.ts new file mode 100644 index 000000000..831626635 --- /dev/null +++ b/scripts/bstock/lib/native.ts @@ -0,0 +1,140 @@ +/** + * 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. + */ +import { fetchWithTimeout } from "./http"; + +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 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) { + 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; +} + +export const BSC_USDT = "0x55d398326f99059fF775485246999027B3197955"; 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/scripts/bstock/lib/safe.ts b/scripts/bstock/lib/safe.ts new file mode 100644 index 000000000..cb030e8f1 --- /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 { BigNumber, BigNumberish, 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))`, 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: BigNumber.from(value).toString(), + 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/lib/sources.ts b/scripts/bstock/lib/sources.ts new file mode 100644 index 000000000..9af3581b5 --- /dev/null +++ b/scripts/bstock/lib/sources.ts @@ -0,0 +1,183 @@ +/** + * 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 + builtFloor: BigNumber; // the built order's own worst-case out — what the fill is actually bound by +} + +/** + * 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). + * + * `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; +} + +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, + }); + const out = BigNumber.from(q.amountOut); + return { + 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 + }), + }; + }, +}; + +// --------------------------------------------------------------------------- // +// 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, + 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 () => { + // 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, + tokenOut: a.usdtOut, + amountWei: a.weiAmount.toString(), + minOutWei: minFloor.toString(), + routePlans: quote.routePlans, + 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 + // 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. + 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 + }; + }, + }; + }, +}; + +// --------------------------------------------------------------------------- // +// 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()); + // 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) + 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/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/liquidation-flowchart.svg b/scripts/bstock/liquidation-flowchart.svg new file mode 100644 index 000000000..f787b4346 --- /dev/null +++ b/scripts/bstock/liquidation-flowchart.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + 2 · atomic-liquidate.ts + DRY_RUN=1 + + + + + + + + + live · auto + + + all sources dead + + + + + + + + no source fills + + + + + + + PSM live · or non-VAI + + + VAI: PSM paused / cap full + + + yes + + + no · can't fix fast + + + + + + + bStock borrower in shortfall + + + + 1 · native-smoke.ts · lm-smoke.ts + read-only, ~10s — which RFQ sources are live? + + + + Any RFQ source live? + native-smoke (Native) · lm-smoke (LM) + + + + 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) + + + + 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 + + + + VAI: PSM usable? + not paused & cap headroom ≥ pull + + + + Dry-run passes? + callStatic liquidate / flashLiquidate + + + + 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) + 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 + + + + legend + + script step + + decision + + hop-2 routing (VAI = PSM) + + sent atomically + + Safe multisig fallback + 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 new file mode 100644 index 000000000..7ca53635a --- /dev/null +++ b/scripts/bstock/native-smoke.ts @@ -0,0 +1,57 @@ +/** + * Smoke test for the Native Swap API integration (no chain interaction). + * + * 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 + * + * Options (env): + * NATIVE_API_KEY=... TOKEN=NVDAB AMOUNT=5 npx hardhat run scripts/bstock/native-smoke.ts + * + * Env: + * NATIVE_API_KEY (required) Native Swap API key + * 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, getFirmQuote, getOrderbook, quoteDeadline } from "./lib/native"; + +async function main() { + 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 (quote = USDT). + const ob = await getOrderbook("bsc"); + 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`); + + // 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 : ${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}`); + console.log(` orders : ${q.orders?.length}`); +} + +main() + .then(() => process.exit(0)) + .catch(e => { + console.error(e); + process.exit(1); + }); diff --git a/scripts/bstock/safe-fallback.ts b/scripts/bstock/safe-fallback.ts new file mode 100644 index 000000000..67b269eab --- /dev/null +++ b/scripts/bstock/safe-fallback.ts @@ -0,0 +1,313 @@ +/** + * bStock backstop liquidation — Safe multisig FALLBACK flow (no on-chain swap). + * + * 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. + * + * 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 + * + * 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, 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 \ + * 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) + * 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"; +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"; +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,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"; + +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 buildSafeFallbackBatch(provider: providers.Provider) { + 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); + + // 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; + 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); + + 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) --- + 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)})`); + + // 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(); + 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}`); + + // 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( + `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 ONE = BigNumber.from(10).pow(18); + // 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 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(`${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(`${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. + // 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)) { + // 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`. 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); + } + + // 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 = vRedeem.mul(exchangeRate).div(ONE).mul(ONE.sub(treasuryPercent)).div(ONE); + console.log( + `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) --- + 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 --- + // 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 seizeTxs = [ + call(vBStock.address, "redeem(uint256)", [vRedeem]), + 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, + 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()}; 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, vRedeem, 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)); + console.log(`\nwrote Safe batch (${txs.length} txs) -> ${out}`); + console.log("Import in Safe -> Apps -> Transaction Builder -> Load."); +} + +// 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/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 new file mode 100644 index 000000000..4cfacfafb --- /dev/null +++ b/tests/hardhat/BStockAtomicLiquidate.ts @@ -0,0 +1,869 @@ +import { setBalance } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { BigNumberish, 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 +// 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 +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", + "WBNB_ADDR", + "MOCK_NATIVE", + "MOCK_AMM", + "MOCK_OUT", + "MODE", + "DRY_RUN", + "SLIPPAGE", + "MIN_OUT_BUFFER", + "SEIZE_BUFFER", + "SETTLE_TTL_MARGIN", + "ALLOW_NO_SHORTFALL", + "NATIVE_API_KEY", + "SOURCE", + "LM_API_KEY", + "LM_PRIVATE_KEY_SEED", + "PSM_ADDR", +] 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; + let wbnb: Contract, vWBNB: Contract; + let venusLiq: Contract, vai: Contract, vaiController: Contract; + + 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"], + }); + } + + // 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(); + + // 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. + 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); + + // 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("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("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("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 + // 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 + 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 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 + }); + + // 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. + 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 + }); + + // 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 + }); + + // 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 + }); + + // 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("sizes the Liquidator treasury cut off the effective incentive, not core (VAI, defensive)", async () => { + await vai.mint(liq.address, REPAY); + // 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 + + // 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" }); + // 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 // + // ------------------------------------------------------------------------ // + // 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`; + // `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); + 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: opts.lmExpiry ?? 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=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 + 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("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() }); + 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 + // 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 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" }); + 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 + }); + + // 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 + }); + }); +}); diff --git a/tests/hardhat/BStockLiquidator.ts b/tests/hardhat/BStockLiquidator.ts new file mode 100644 index 000000000..c16bfd19e --- /dev/null +++ b/tests/hardhat/BStockLiquidator.ts @@ -0,0 +1,824 @@ +import { SnapshotRestorer, setBalance, takeSnapshot } 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, 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 +// 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 vai: Contract, vaiController: 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(); + + // 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); + + // 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); + + // 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, VBNB, vWBNB.address, wbnb.address], + 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]); + } + + // 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, + vDebt: vDebt.address, + vBStock: vBStock.address, + repayAmount: REPAY, + router: router.address, + swapCalldata: swapCalldata(SEIZED, liq.address), + minOut: MIN_OUT, + router2: ZERO, + swapCalldata2: "0x", + intermediateToken: ZERO, + deadline: ethers.constants.MaxUint256, // never expires unless a test overrides it + ...over, + }; + } + + // 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", () => { + 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, 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); + // 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 (the pool gate)", async () => { + await usdt.mint(liq.address, REPAY); + + await expect(liq.connect(owner).liquidate(params())) + .to.emit(liq, "Liquidated") + .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); + }); + + it("handles the Venus Liquidator treasury cut via delta accounting (sells only what was credited)", async () => { + await venusLiq.setTreasuryCut(U("0.1")); // liquidator keeps back 10% of the seized collateral + 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, vDebt.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, 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); + }); + }); + + // 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 + 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, 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)); + // 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)); + // 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 () => { + 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 + }); + }); + + // 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, 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 + expect(await bStock.balanceOf(liq.address)).to.equal(0); + // 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 () => { + 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, 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)); + 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, 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); + }); + + 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), + // 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.revertedWith("ERC20: insufficient allowance"); + 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 + }); + }); + + // 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 + // 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( + liq.connect(stranger).executeOperation([vDebt.address], [REPAY], [0], liq.address, liq.address, "0x"), + ).to.be.revertedWithCustomError(liq, "OnlyComptroller"); + }); + + 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("no longer pre-blocks a zero-shortfall borrower (supports forced liquidations)", async () => { + await comptroller.setShortfall(0); + await expect(liq.connect(owner).liquidate(params())).to.emit(liq, "Liquidated"); + }); + + 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("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("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())) + .to.be.revertedWithCustomError(liq, "RedeemFailed") + .withArgs(7); + }); + + 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.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", + ); + }); + }); + + 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, 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, VBNB, vWBNB.address, wbnb.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(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")); + 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", () => { + 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/BStockLiquidatorLiquidMesh.ts b/tests/hardhat/BStockLiquidatorLiquidMesh.ts new file mode 100644 index 000000000..98f2cb8b3 --- /dev/null +++ b/tests/hardhat/BStockLiquidatorLiquidMesh.ts @@ -0,0 +1,214 @@ +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 -> 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.revertedWith("ERC20: insufficient allowance"); + }); + + 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 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 () => { + 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.revertedWith("ERC20: insufficient allowance"); + }); +}); diff --git a/tests/hardhat/BStockSafeFallback.ts b/tests/hardhat/BStockSafeFallback.ts new file mode 100644 index 000000000..184dbf2f2 --- /dev/null +++ b/tests/hardhat/BStockSafeFallback.ts @@ -0,0 +1,282 @@ +// 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 { BigNumber, 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 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; + 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, + // 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, + }); + } + + beforeEach(async () => { + await deploy(); + // wipe any env leakage between tests + for (const k of [ + "SAFE", + "BORROWER", + "VBSTOCK", + "VDEBT", + "REPAY_AMOUNT", + "TARGET", + "ALLOW_PLACEHOLDER", + "SEIZE_BUFFER", + ]) { + delete process.env[k]; + } + }); + + it("routes the repay through the Venus Liquidator gate (T1)", async () => { + setEnv(); + const { txs, gate, vReceived, seizedRaw } = await buildSafeFallbackBatch(ethers.provider); + + 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("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 + }); + + // 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(); + 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 () => { + 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); + }); + + // 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); + }); + + 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/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/); + }); +}); diff --git a/tests/hardhat/Fork/BStockLiquidatorFork.ts b/tests/hardhat/Fork/BStockLiquidatorFork.ts new file mode 100644 index 000000000..048d1660a --- /dev/null +++ b/tests/hardhat/Fork/BStockLiquidatorFork.ts @@ -0,0 +1,1493 @@ +// ============================================================================================ +// 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 { expect } from "chai"; +import { BigNumber, Contract } from "ethers"; +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 { assertVaiGateClear } from "../../../scripts/bstock/lib/vai-gate"; +import { + A, + Action, + BStockMarket, + BalanceSlot, + ERC20_ABI, + ONE, + TOK, + VTOKEN_ABI, + ZERO, + asTimelockWith, + assertRolledBack, + assertSettledFor, + buildSingleHopMock, + buildTwoHopMockThenPcs, + deployFundedMockNative, + findBalanceSlot, + listBStockMarket, + makeUnderwaterBorrower, + mulberry32, + randBn, + setTokenBalance, +} from "./helpers/bstock"; +import { FORK_MAINNET, forking, initMainnetUser } from "./utils"; + +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"; + +// 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()", +]; +// 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); +const P_CRASH = parseUnits("50", 18); + +// 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"), + 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"), + FORCED_SCRIPT: ethers.utils.getAddress("0x00000000000000000000000000000000ca11000c"), + 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 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 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("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; + + // 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) + + 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 + }); + + /* ---------------------------------------------------------------- */ + /* 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); + }); + + 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, + 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); + + 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, + B.CAKE, + ); + 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, + B.BNB, + ); + 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); + }); + + // 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, + "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]); + // mintVAI returns an error CODE (legacy style) — probe first so a cap/pause surfaces loudly. + 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); + 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 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) + await liqVai.connect(owner).setRouter(PSM_USDT, true); // hop-2 router: the real PSM + return liqVai; + } + + // 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 xr: BigNumber = await mkt.vBStock.exchangeRateStored(); + 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(); + + 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); + + 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); + }); + + // 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. + 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, + ); + }); + + // 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 + // 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, + 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("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, + 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, + }); + + // 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); + }); + }); + + /* ---------------------------------------------------------------- */ + /* 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 rows: { sym: string; addr: string; status: string; reason: 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 snap = await ethers.provider.send("evm_snapshot", []); + try { + const reason = await sweepOne(owner, c, pcs, mkt, mock, liq, vDebt, fund); + if (reason === "OK") { + liquidated++; + rows.push({ sym, addr: vDebt, status: "LIQUIDATED", reason: "" }); + tally["liquidated"] = (tally["liquidated"] || 0) + 1; + } else { + // Normalize the reason head for the table + tally (e.g. "BorrowNotAllowedInPool"). + const clean = reason + .replace(/^borrow \(/, "") + .replace(/\)$/, "") + .split("(")[0] + .trim(); + 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. + 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("\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 + }); + }); + + /* ---------------------------------------------------------------- */ + /* 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 { + // (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); + 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"; + } + 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( + "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) : ""; + // 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); + 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, + borrower, + ); + 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 (${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); +} diff --git a/tests/hardhat/Fork/BStockMatrixFork.ts b/tests/hardhat/Fork/BStockMatrixFork.ts new file mode 100644 index 000000000..22d781831 --- /dev/null +++ b/tests/hardhat/Fork/BStockMatrixFork.ts @@ -0,0 +1,608 @@ +// ============================================================================================ +// 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 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"; +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 authorizedFlashLoan(address) view returns (bool)", + "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: 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; + 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). 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 + // 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 (~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)"], + ]); + const resilient = new ethers.Contract( + A.RESILIENT_ORACLE, + [ + "function setTokenConfig(tuple(address asset, address[3] oracles, bool[3] enableFlagsForOracles, bool cachingEnabled))", + ], + tl, + ); + 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 + // 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]) 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 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 () => { + 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 () => { + 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); +} diff --git a/tests/hardhat/Fork/helpers/bstock.ts b/tests/hardhat/Fork/helpers/bstock.ts new file mode 100644 index 000000000..0442170ba --- /dev/null +++ b/tests/hardhat/Fork/helpers/bstock.ts @@ -0,0 +1,430 @@ +// 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 { expect } from "chai"; +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,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)", +]; +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) */ +/* ------------------------------------------------------------------ */ + +// 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. +// +// 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 <= 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, loc: BalanceSlot) { + await setStorageAt(token, mappingSlot(account, loc), 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); + }, + }; +} + +/* ------------------------------------------------------------------ */ +/* 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, + borrower: string, +): Promise<{ swapCalldata: string; swapCalldata2: string; expectedOut: BigNumber }> { + const comptroller = new ethers.Contract(A.COMPTROLLER, COMPTROLLER_ABI, owner); + // 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 + 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"