Skip to content
Open
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
d2f5cd0
feat: add new vars in AccountLiquiditySnapshot
Debugger022 Jul 7, 2025
1f2d92b
feat: modify comptroller interface
Debugger022 Jul 7, 2025
a2ac784
feat: add dynamic close factor and liquidation incentive
Debugger022 Jul 7, 2025
39c970d
feat: add contract-sizer in hardhat config
Debugger022 Jul 8, 2025
5aac9d2
chore: update yarn.lock
Debugger022 Jul 8, 2025
083a1f7
feat: move liquidation logic to Liquidation library
Debugger022 Jul 8, 2025
8c964fa
feat: update comptroller to use Liquidation library functions
Debugger022 Jul 8, 2025
7c65f24
feat: add maximum liquidation incentive per asset
Debugger022 Jul 9, 2025
32bf187
feat: update liquidation library
Debugger022 Jul 9, 2025
2716fec
refactor: remove pool liquidation Incentive reference
Debugger022 Jul 9, 2025
e1f58da
feat: moved reward updates logic to Rewards library
Debugger022 Jul 10, 2025
0921fce
refactor: removed liquidation Incentive mapping
Debugger022 Jul 10, 2025
dec2414
refactor: reduced comptroller size
Debugger022 Jul 10, 2025
f2ef6fd
fix: minor fix
Debugger022 Jul 10, 2025
27cf44c
fix: adjust heal account tests for dynamic factors
Debugger022 Jul 10, 2025
47ba38e
fix: adjust liquidate account tests for dynamic factors
Debugger022 Jul 14, 2025
876e44e
refactor: add zero checks in snapshot calculations
Debugger022 Jul 14, 2025
5860efe
test: fix hooks and setters test
Debugger022 Jul 14, 2025
fc9c6a7
test: fix seize tokens test
Debugger022 Jul 14, 2025
691c424
refactor: moved rewards logic to internal functions
Debugger022 Jul 15, 2025
bebf45d
test: fix pool lens test
Debugger022 Jul 15, 2025
2811258
test: fix tests
Debugger022 Jul 15, 2025
8876c29
feat: getter for liquidation incentive per market
Debugger022 Jul 15, 2025
e6e906f
fix: fixed vTokens test
Debugger022 Jul 15, 2025
749dbb6
test: fix NativeToken gateway test
Debugger022 Jul 15, 2025
e8ea512
fix: Average liquidation incentive calculation
Debugger022 Jul 16, 2025
c4a5768
refactor: corrected addPool signature
Debugger022 Jul 16, 2025
fe24745
refactor: adjust computation in calculateIncentiveAdjustedDebt
Debugger022 Jul 16, 2025
bfd294f
test: fixed integration tests
Debugger022 Jul 16, 2025
232421a
fix: fix Pool lens test
Debugger022 Jul 17, 2025
fbde27d
fix: corrected averageLT calculation
Debugger022 Jul 17, 2025
438695d
feat: add Toxic liquidation check
Debugger022 Jul 17, 2025
58e2678
feat: update comptroller interface
Debugger022 Jul 17, 2025
2413962
test: refactored integration tests
Debugger022 Jul 17, 2025
c5564d9
fix: fixed references for averageLT
Debugger022 Jul 17, 2025
525f1cf
fix: fixed storage layout
Debugger022 Jul 22, 2025
c825a8e
feat: external Liquidaiton Manager contract instead of library
Debugger022 Jul 22, 2025
c907123
feat: Liquidation Manager interface
Debugger022 Jul 22, 2025
b6f0e5e
feat: update comptroller interface
Debugger022 Jul 22, 2025
d072b0a
feat: add liquidation manager setter and refactor dependencies
Debugger022 Jul 22, 2025
d86ccaa
refactor: add MarketListed internal function to reduce comptroller size
Debugger022 Jul 22, 2025
ebcd3e4
feat: add natspec comments for reward functions
Debugger022 Jul 22, 2025
8451373
fix: fix tests
Debugger022 Jul 23, 2025
d8bc0ce
refactor: move order processing back to comptroller
Debugger022 Jul 23, 2025
dbf5739
fix: integration test
Debugger022 Jul 23, 2025
3177bf8
test: fixed fork tests
Debugger022 Jul 24, 2025
ec585af
feat: liquidation Manager for common functionalities of core and IL
Debugger022 Jul 28, 2025
1dc088c
feat: IL specific liquidation manager
Debugger022 Jul 28, 2025
91c22c3
refactor: using ILLiquidation manager in comptroller
Debugger022 Jul 28, 2025
b7ad5ae
refactor: moved some logic to Liquidation Manager
Debugger022 Jul 28, 2025
e7285d6
test: refactor tests
Debugger022 Jul 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
398 changes: 233 additions & 165 deletions contracts/Comptroller.sol

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions contracts/ComptrollerInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ interface ComptrollerInterface {
/*** Liquidity/Liquidation Calculations ***/

function liquidateCalculateSeizeTokens(
address borrower,
address vTokenBorrowed,
address vTokenCollateral,
uint256 repayAmount
Expand All @@ -103,6 +104,12 @@ interface ComptrollerInterface {
function getAllMarkets() external view returns (VToken[] memory);

function actionPaused(address market, Action action) external view returns (bool);

function getDynamicLiquidationIncentive(address borrower, address market) external view returns (uint256);

function getMarketLiquidationIncentive(address vToken) external view returns (uint256);

function getOracle() external view returns (ResilientOracleInterface);
}

/**
Expand Down Expand Up @@ -132,4 +139,8 @@ interface ComptrollerViewInterface {
function supplyCaps(address) external view returns (uint256);

function approvedDelegates(address user, address delegate) external view returns (bool);

function getDynamicLiquidationIncentive(address borrower, address market) external view returns (uint256);

function getMarketLiquidationIncentive(address vToken) external view returns (uint256);
}
15 changes: 13 additions & 2 deletions contracts/ComptrollerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { VToken } from "./VToken.sol";
import { RewardsDistributor } from "./Rewards/RewardsDistributor.sol";
import { IPrime } from "@venusprotocol/venus-protocol/contracts/Tokens/Prime/Interfaces/IPrime.sol";
import { Action } from "./ComptrollerInterface.sol";
import { ILiquidationManager } from "./LiquidationManagerInterface.sol";

/**
* @title ComptrollerStorage
Expand All @@ -27,6 +28,10 @@ contract ComptrollerStorage {
uint256 effects;
uint256 liquidity;
uint256 shortfall;
uint256 averageLT; // Average liquidation threshold of all assets in the snapshot
uint256 healthFactor; // Health factor of the account, calculated as (weightedCollateral / borrows)
uint256 healthFactorThreshold; // Health factor threshold for liquidation, calculated as (averageLT * (1e18 + LiquidationIncentiveAvg) / 1e18)
uint256 liquidationIncentiveAvg; // Average liquidation incentive of all assets in the snapshot
}

struct RewardSpeeds {
Expand All @@ -48,6 +53,8 @@ contract ComptrollerStorage {
uint256 liquidationThresholdMantissa;
// Per-market mapping of "accounts in this asset"
mapping(address => bool) accountMembership;
// discount on collateral that a liquidator receives when liquidating a borrow in this market
uint256 maxLiquidationIncentiveMantissa;
}

/**
Expand All @@ -63,7 +70,7 @@ contract ComptrollerStorage {
/**
* @notice Multiplier representing the discount on collateral that a liquidator receives
*/
uint256 public liquidationIncentiveMantissa;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintain the attribute, to not break the storage layout. Simply rename it to "deprecatedLiquidationIncentiveMantissa"?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uint256 public deprecatedLiquidationIncentiveMantissa;

/**
* @notice Per-account mapping of "assets you are in"
Expand Down Expand Up @@ -118,10 +125,14 @@ contract ComptrollerStorage {
//mapping(address user => mapping (address delegate => bool approved)) public approvedDelegates;
mapping(address => mapping(address => bool)) public approvedDelegates;

/// @notice The liquidation manager contract that handles liquidation logic
// This is an interface to allow for different liquidation strategies
ILiquidationManager public liquidationManager;

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[47] private __gap;
uint256[46] private __gap;
}
8 changes: 3 additions & 5 deletions contracts/Lens/PoolLens.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { TimeManagerV8 } from "@venusprotocol/solidity-utilities/contracts/TimeM
* for all pools within the lending protocol can be acquired through the function `getAllPools()`. Additionally, the following records can be
* looked up for specific pools and markets:
- the vToken balance of a given user;
- the pool data (oracle address, associated vToken, liquidation incentive, etc) of a pool via its associated comptroller address;
- the pool data (oracle address, associated vToken etc) of a pool via its associated comptroller address;
- the vToken address in a pool for a given asset;
- a list of all pools that support an asset;
- the underlying asset price of a vToken;
Expand All @@ -41,7 +41,6 @@ contract PoolLens is ExponentialNoError, TimeManagerV8 {
string description;
address priceOracle;
uint256 closeFactor;
uint256 liquidationIncentive;
uint256 minLiquidatableCollateral;
VTokenMetadata[] vTokens;
}
Expand Down Expand Up @@ -270,7 +269,7 @@ contract PoolLens is ExponentialNoError, TimeManagerV8 {
*
* @param comptrollerAddress Address of the comptroller
*
* @return badDebtSummary A struct with comptroller address, total bad debut denominated in usd, and
* @return badDebtSummary A struct with comptroller address, total bad debt denominated in usd, and
* a break down of bad debt by market
*/
function getPoolBadDebt(address comptrollerAddress) external view returns (BadDebtSummary memory) {
Expand All @@ -287,7 +286,7 @@ contract PoolLens is ExponentialNoError, TimeManagerV8 {
badDebtSummary.comptroller = comptrollerAddress;
badDebtSummary.badDebts = badDebts;

// // Calculate the bad debt is USD per market
// Calculate the bad debt in USD per market
for (uint256 i; i < markets.length; ++i) {
BadDebt memory badDebt;
badDebt.vTokenAddress = address(markets[i]);
Expand Down Expand Up @@ -368,7 +367,6 @@ contract PoolLens is ExponentialNoError, TimeManagerV8 {
vTokens: vTokenMetadataItems,
priceOracle: address(comptrollerViewInstance.oracle()),
closeFactor: comptrollerViewInstance.closeFactorMantissa(),
liquidationIncentive: comptrollerViewInstance.liquidationIncentiveMantissa(),
minLiquidatableCollateral: comptrollerViewInstance.minLiquidatableCollateral()
});

Expand Down
160 changes: 160 additions & 0 deletions contracts/LiquidationManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.10;

import { VToken } from "./VToken.sol";
import { ComptrollerStorage } from "./ComptrollerStorage.sol";
import { ComptrollerInterface } from "./ComptrollerInterface.sol";
import { Comptroller } from "./Comptroller.sol";
import { ExponentialNoError } from "./ExponentialNoError.sol";
import { ILiquidationManager } from "./LiquidationManagerInterface.sol";
import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";

contract LiquidationManager is ILiquidationManager, ExponentialNoError {
/**
* @notice Calculates incentive-adjusted debt
*/
function calculateIncentiveAdjustedDebt(
address borrower,
VToken[] memory markets,
ComptrollerInterface comptroller
) external view returns (uint256 weightedBorrowSum) {
for (uint256 i; i < markets.length; ++i) {
VToken market = markets[i];
(, , uint256 borrowBalance, ) = market.getAccountSnapshot(borrower);
if (borrowBalance == 0) continue;

ResilientOracleInterface oracle = comptroller.getOracle();
uint256 borrowPrice = oracle.getUnderlyingPrice(address(market));
uint256 borrowValueUSD = mul_ScalarTruncate(Exp({ mantissa: borrowPrice }), borrowBalance);

uint256 marketIncentive = comptroller.getDynamicLiquidationIncentive(borrower, address(market));

weightedBorrowSum = ExponentialNoError.add_(
weightedBorrowSum,
ExponentialNoError.mul_ScalarTruncate(
ExponentialNoError.Exp({ mantissa: marketIncentive }),
borrowValueUSD
)
);
}
}

/**
* @notice Processes a single asset for a given account and updates the liquidity snapshot.
* @dev
* - Constructs AssetData for the asset and account.
* - Calculates and applies the asset's effect on the account's liquidity snapshot, including any modifications (redeem/borrow).
* @param asset The VToken asset to process.
* @param account The address of the account being evaluated.
* @param effects Parameters describing any modifications (redeem/borrow) to apply for this asset.
* @param assetWeight The risk weight of the asset.
* @param snapshot The current account liquidity snapshot to update.
* @return The updated AccountLiquiditySnapshot struct.
*/
function processAsset(
VToken asset,
address account,
EffectsParams memory effects,
uint256 assetWeight,
uint256 underlyingPrice,
ComptrollerStorage.AccountLiquiditySnapshot memory snapshot
) external view returns (ComptrollerStorage.AccountLiquiditySnapshot memory) {
(, uint256 vTokenBalance, uint256 borrowBalance, uint256 exchangeRateMantissa) = asset.getAccountSnapshot(
account
);

AssetData memory assetData = AssetData({
vTokenBalance: vTokenBalance,
borrowBalance: borrowBalance,
exchangeRateMantissa: exchangeRateMantissa,
underlyingPrice: underlyingPrice,
assetWeight: assetWeight,
vTokenAddress: address(asset)
});

return _calculateAssetValues(assetData, snapshot, effects);
}

/**
* @notice Finalizes the account liquidity snapshot by calculating weighted averages, health factors, and liquidity/shortfall.
* @dev
* - Computes the average weight.
* - Calculates the sum of borrows and effects.
* - Determines the health factor as the ratio of weighted collateral to total borrow plus effects.
* - Sets the health factor threshold using the weighted average and liquidation incentive.
* - Calculates liquidity and shortfall based on the comparison of weighted collateral and borrow plus effects.
* @param snapshot The account liquidity snapshot to be finalized.
* @return The finalized account liquidity snapshot with updated fields.
*/
function finalizeSnapshot(
ComptrollerStorage.AccountLiquiditySnapshot memory snapshot
) external pure returns (ComptrollerStorage.AccountLiquiditySnapshot memory) {
if (snapshot.totalCollateral > 0) {
snapshot.averageLT = div_(snapshot.averageLT, snapshot.totalCollateral);
}
uint256 borrowPlusEffects = snapshot.borrows + snapshot.effects;

if (borrowPlusEffects > 0) {
snapshot.healthFactor = div_(snapshot.weightedCollateral, borrowPlusEffects);
}
snapshot.healthFactorThreshold = div_(snapshot.averageLT * (1e18 + snapshot.liquidationIncentiveAvg), 1e18);

unchecked {
if (snapshot.weightedCollateral > borrowPlusEffects) {
snapshot.liquidity = snapshot.weightedCollateral - borrowPlusEffects;
snapshot.shortfall = 0;
} else {
snapshot.liquidity = 0;
snapshot.shortfall = borrowPlusEffects - snapshot.weightedCollateral;
}
}

return snapshot;
}

/**
* @notice Calculates and updates the liquidity snapshot values for a given asset.
* @dev Computes weighted collateral, total collateral, and borrow values using asset data and price information.
* If the asset is being modified (redeemed or borrowed), applies the effects to the snapshot as well.
* @param asset The asset data struct containing balances, prices, and weights.
* @param snapshot The current account liquidity snapshot to update.
* @param effectsParams Parameters describing any modifications (redeem/borrow) to apply for this asset.
* @return The updated AccountLiquiditySnapshot struct.
*/
function _calculateAssetValues(
AssetData memory asset,
ComptrollerStorage.AccountLiquiditySnapshot memory snapshot,
EffectsParams memory effectsParams
) internal pure returns (ComptrollerStorage.AccountLiquiditySnapshot memory) {
Exp memory oraclePrice = Exp({ mantissa: asset.underlyingPrice });
Exp memory vTokenPrice = mul_(Exp({ mantissa: asset.exchangeRateMantissa }), oraclePrice);
Exp memory weightedVTokenPrice = mul_(Exp({ mantissa: asset.assetWeight }), vTokenPrice);

// Core calculations
snapshot.weightedCollateral = mul_ScalarTruncateAddUInt(
weightedVTokenPrice,
asset.vTokenBalance,
snapshot.weightedCollateral
);
snapshot.totalCollateral = mul_ScalarTruncateAddUInt(
vTokenPrice,
asset.vTokenBalance,
snapshot.totalCollateral
);
snapshot.borrows = mul_ScalarTruncateAddUInt(oraclePrice, asset.borrowBalance, snapshot.borrows);
uint256 vTokenBalanceUSD = mul_ScalarTruncate(vTokenPrice, asset.vTokenBalance);
snapshot.averageLT += mul_(asset.assetWeight, vTokenBalanceUSD);

// Handle modified asset effects
if (address(asset.vTokenAddress) == address(effectsParams.vTokenModify)) {
snapshot.effects = mul_ScalarTruncateAddUInt(
weightedVTokenPrice,
effectsParams.redeemTokens,
snapshot.effects
);
snapshot.effects = mul_ScalarTruncateAddUInt(oraclePrice, effectsParams.borrowAmount, snapshot.effects);
}

return snapshot;
}
}
42 changes: 42 additions & 0 deletions contracts/LiquidationManagerInterface.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.10;

import { VToken } from "./VToken.sol";
import { ComptrollerStorage } from "./ComptrollerStorage.sol";
import { ComptrollerInterface } from "./ComptrollerInterface.sol";

interface ILiquidationManager {
struct AssetData {
uint256 vTokenBalance;
uint256 borrowBalance;
uint256 exchangeRateMantissa;
uint256 underlyingPrice;
uint256 assetWeight;
address vTokenAddress;
}

struct EffectsParams {
VToken vTokenModify;
uint256 redeemTokens;
uint256 borrowAmount;
}

function calculateIncentiveAdjustedDebt(
address borrower,
VToken[] memory markets,
ComptrollerInterface comptroller
) external view returns (uint256 weightedBorrowSum);

function processAsset(
VToken asset,
address account,
EffectsParams memory effects,
uint256 assetWeight,
uint256 underlyingPrice,
ComptrollerStorage.AccountLiquiditySnapshot memory snapshot
) external view returns (ComptrollerStorage.AccountLiquiditySnapshot memory);

function finalizeSnapshot(
ComptrollerStorage.AccountLiquiditySnapshot memory snapshot
) external pure returns (ComptrollerStorage.AccountLiquiditySnapshot memory);
}
5 changes: 1 addition & 4 deletions contracts/Pool/PoolRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ contract PoolRegistry is Ownable2StepUpgradeable, AccessControlledV8, PoolRegist
* @param name The name of the pool
* @param comptroller Pool's Comptroller contract
* @param closeFactor The pool's close factor (scaled by 1e18)
* @param liquidationIncentive The pool's liquidation incentive (scaled by 1e18)
* @param minLiquidatableCollateral Minimal collateral for regular (non-batch) liquidations flow
* @return index The index of the registered Venus pool
* @custom:error ZeroAddressNotAllowed is thrown when Comptroller address is zero
Expand All @@ -134,10 +133,9 @@ contract PoolRegistry is Ownable2StepUpgradeable, AccessControlledV8, PoolRegist
string calldata name,
Comptroller comptroller,
uint256 closeFactor,
uint256 liquidationIncentive,
uint256 minLiquidatableCollateral
) external virtual returns (uint256 index) {
_checkAccessAllowed("addPool(string,address,uint256,uint256,uint256)");
_checkAccessAllowed("addPool(string,address,uint256,uint256)");
// Input validation
ensureNonzeroAddress(address(comptroller));
ensureNonzeroAddress(address(comptroller.oracle()));
Expand All @@ -146,7 +144,6 @@ contract PoolRegistry is Ownable2StepUpgradeable, AccessControlledV8, PoolRegist

// Set Venus pool parameters
comptroller.setCloseFactor(closeFactor);
comptroller.setLiquidationIncentive(liquidationIncentive);
comptroller.setMinLiquidatableCollateral(minLiquidatableCollateral);

return poolId;
Expand Down
9 changes: 7 additions & 2 deletions contracts/VToken.sol

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid changes in the VToken contract, if it's doable. Because vBNB cannot be upgraded

Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,10 @@ contract VToken is
*/
function setProtocolSeizeShare(uint256 newProtocolSeizeShareMantissa_) external {
_checkAccessAllowed("setProtocolSeizeShare(uint256)");
uint256 liquidationIncentive = ComptrollerViewInterface(address(comptroller)).liquidationIncentiveMantissa();

uint256 liquidationIncentive = ComptrollerViewInterface(address(comptroller)).getMarketLiquidationIncentive(
address(this)
);
if (newProtocolSeizeShareMantissa_ + MANTISSA_ONE > liquidationIncentive) {
revert ProtocolSeizeShareTooBig();
}
Expand Down Expand Up @@ -1221,6 +1224,7 @@ contract VToken is

/* We calculate the number of collateral tokens that will be seized */
(uint256 amountSeizeError, uint256 seizeTokens) = comptroller.liquidateCalculateSeizeTokens(
borrower,
address(this),
address(vTokenCollateral),
actualRepayAmount
Expand Down Expand Up @@ -1274,8 +1278,9 @@ contract VToken is
* borrowerTokensNew = accountTokens[borrower] - seizeTokens
* liquidatorTokensNew = accountTokens[liquidator] + seizeTokens
*/

uint256 liquidationIncentiveMantissa = ComptrollerViewInterface(address(comptroller))
.liquidationIncentiveMantissa();
.getDynamicLiquidationIncentive(borrower, address(this));
uint256 numerator = mul_(seizeTokens, Exp({ mantissa: protocolSeizeShareMantissa }));
uint256 protocolSeizeTokens = div_(numerator, Exp({ mantissa: liquidationIncentiveMantissa }));
uint256 liquidatorSeizeTokens = seizeTokens - protocolSeizeTokens;
Expand Down
2 changes: 1 addition & 1 deletion contracts/WUSDMLiquidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ contract WUSDMLiquidator is Ownable2StepUpgradeable {
}

function _configureMarkets() internal {
(, uint256 wUSDMCollateralFactor, uint256 wUSDMLiquidationThreshold) = COMPTROLLER.markets(address(VWUSDM));
(, uint256 wUSDMCollateralFactor, uint256 wUSDMLiquidationThreshold, ) = COMPTROLLER.markets(address(VWUSDM));
_originalConfig = OriginalConfig({
minLiquidatableCollateral: COMPTROLLER.minLiquidatableCollateral(),
closeFactor: COMPTROLLER.closeFactorMantissa(),
Expand Down
Loading