Description:
Decentralized Finance (DeFi) protocol contract providing Swap, Liquidity, Factory, Oracle functionality.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/strategy/liquidation/PositionLensAaveV3Main.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../../ownership/MultiOwnable.sol";
/// Minimal ERC20 decimals
interface IERC20Minimal { function decimals() external view returns (uint8); }
interface IAavePool {
function getUserAccountData(address user) external view returns (
uint256 totalCollateralBase,
uint256 totalDebtBase,
uint256 availableBorrowsBase,
uint256 currentLiquidationThreshold,
uint256 ltv,
uint256 healthFactor
);
/// @notice Returns the total flash loan premium (fee) in basis points
/// @dev Default is 5 (0.05%), but can be updated via governance
function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128);
}
interface IAaveProtocolDataProvider {
function getReserveConfigurationData(address asset) external view returns (
uint256 decimals,
uint256 ltv,
uint256 liquidationThreshold,
uint256 liquidationBonus,
uint256 reserveFactor,
bool usageAsCollateralEnabled,
bool borrowingEnabled,
bool stableBorrowRateEnabled,
bool isActive,
bool isFrozen
);
function getUserReserveData(address asset, address user) external view returns(
uint256 currentATokenBalance,
uint256 currentStableDebt,
uint256 currentVariableDebt,
uint256 principalStableDebt,
uint256 scaledVariableDebt,
uint256 stableBorrowRate,
uint256 liquidityRate,
uint40 stableRateLastUpdated,
bool usageAsCollateralEnabled
);
function getLiquidationProtocolFee(address asset) external view returns (uint256);
}
interface IAaveOracle {
function getAssetPrice(address asset) external view returns (uint256);
function BASE_CURRENCY_UNIT() external view returns (uint256);
}
contract PositionLensAaveV3Main is MultiOwnable {
IAavePool public immutable pool;
IAaveProtocolDataProvider public immutable dataProvider;
IAaveOracle public immutable oracle;
address public immutable WETH;
uint256 public immutable defaultGasUnits;
uint256 public immutable defaultSlippageBps;
struct Quote {
bool profitable;
uint256 debtToCover;
uint256 expectedSeize;
uint256 proceedsBase;
uint256 repayCostBase; // Includes flash loan fee
uint256 flashLoanFeeBase; // Flash loan fee (0.05% of debt)
uint256 gasCostBase;
int256 netProfitBase;
uint256 closeFactorBps;
}
/// @notice Minimum valid liquidation bonus (100% = no bonus)
uint256 private constant MIN_LIQUIDATION_BONUS = 10_000;
struct PairQuote {
address collateralAsset;
address debtAsset;
Quote q;
}
constructor(
address _pool,
address _dataProvider,
address _oracle,
address _weth,
uint256 _defaultGasUnits,
uint256 _defaultSlippageBps
) {
require(
_pool != address(0) &&
_dataProvider != address(0) &&
_oracle != address(0) &&
_weth != address(0),
"Invalid address"
);
pool = IAavePool(_pool);
dataProvider = IAaveProtocolDataProvider(_dataProvider);
oracle = IAaveOracle(_oracle);
WETH = _weth;
defaultGasUnits = _defaultGasUnits;
defaultSlippageBps = _defaultSlippageBps;
}
// ---------- Single ----------
function quote(
address user,
address collateralAsset,
address debtAsset,
uint256 gasPriceWei,
uint256 slippageBps,
uint256 gasUnits
) external view returns (Quote memory q) {
return _quoteSingle(user, collateralAsset, debtAsset, gasPriceWei, slippageBps, gasUnits);
}
// ---------- Multi: one-to-one ----------
function quotePairsFiltered(
address user,
address[] calldata collateralAssets,
address[] calldata debtAssets,
uint256 gasPriceWei,
uint256 slippageBps,
uint256 gasUnits,
int256 minProfitBase
) external view returns (PairQuote[] memory out) {
uint256 n = collateralAssets.length;
require(n == debtAssets.length, "len mismatch");
PairQuote[] memory tmp = new PairQuote[](n);
uint256 count;
for (uint256 i; i < n; ++i) {
Quote memory q = _quoteSingle(user, collateralAssets[i], debtAssets[i], gasPriceWei, slippageBps, gasUnits);
if (q.profitable && q.netProfitBase >= minProfitBase) {
tmp[count++] = PairQuote(collateralAssets[i], debtAssets[i], q);
}
}
out = new PairQuote[](count);
for (uint256 i; i < count; ++i) out[i] = tmp[i];
}
// ---------- Multi: full cartesian ----------
function quoteCartesianFiltered(
address user,
address[] calldata collaterals,
address[] calldata debts,
uint256 gasPriceWei,
uint256 slippageBps,
uint256 gasUnits,
int256 minProfitBase
) external view returns (PairQuote[] memory out) {
uint256 m = collaterals.length;
uint256 n = debts.length;
PairQuote[] memory tmp = new PairQuote[](m * n);
uint256 count;
for (uint256 i; i < m; ++i) {
address c = collaterals[i];
for (uint256 j; j < n; ++j) {
address d = debts[j];
Quote memory q = _quoteSingle(user, c, d, gasPriceWei, slippageBps, gasUnits);
if (q.profitable && q.netProfitBase >= minProfitBase) {
tmp[count++] = PairQuote(c, d, q);
}
}
}
out = new PairQuote[](count);
for (uint256 i; i < count; ++i) out[i] = tmp[i];
}
// ---------- Internal core logic ----------
/// @dev Calculates liquidation profitability for a single collateral/debt pair
/// @notice liquidationBonus from Aave is in basis points where 10500 = 105% (5% bonus)
/// @param user The address of the borrower to liquidate
/// @param collateralAsset The collateral asset to seize
/// @param debtAsset The debt asset to repay
/// @param gasPriceWei Gas price in wei for cost calculation
/// @param slippageBps Slippage tolerance in basis points (e.g., 50 = 0.5%)
/// @param gasUnits Estimated gas units for the liquidation transaction
/// @return q Quote struct with profitability calculation results
function _quoteSingle(
address user,
address collateralAsset,
address debtAsset,
uint256 gasPriceWei,
uint256 slippageBps,
uint256 gasUnits
) internal view returns (Quote memory q) {
if (slippageBps==0) slippageBps = defaultSlippageBps;
if (gasUnits==0) gasUnits = defaultGasUnits;
(, , , , , uint256 hf) = pool.getUserAccountData(user);
q.closeFactorBps = (hf < 95e16) ? 10_000 : 5_000;
(, uint256 stableDebt, uint256 variableDebt, , , , , , ) =
dataProvider.getUserReserveData(debtAsset, user);
uint256 totalDebt = stableDebt + variableDebt;
if (totalDebt == 0) {
// Return empty quote with all fields initialized to zero/false
return Quote({
profitable: false,
debtToCover: 0,
expectedSeize: 0,
proceedsBase: 0,
repayCostBase: 0,
flashLoanFeeBase: 0,
gasCostBase: 0,
netProfitBase: 0,
closeFactorBps: q.closeFactorBps
});
}
uint256 maxRepay = (totalDebt * q.closeFactorBps) / 10_000;
( , , , uint256 liqBonusBps, , , , , , ) =
dataProvider.getReserveConfigurationData(collateralAsset);
// Validate liquidation bonus format (must be >= 10000, e.g., 10500 for 5% bonus)
if (liqBonusBps < MIN_LIQUIDATION_BONUS) {
return Quote({
profitable: false,
debtToCover: 0,
expectedSeize: 0,
proceedsBase: 0,
repayCostBase: 0,
flashLoanFeeBase: 0,
gasCostBase: 0,
netProfitBase: 0,
closeFactorBps: q.closeFactorBps
});
}
uint256 pxDebt = oracle.getAssetPrice(debtAsset);
uint256 pxColl = oracle.getAssetPrice(collateralAsset);
if (pxDebt==0||pxColl==0) {
// Return empty quote if prices are invalid
return Quote({
profitable: false,
debtToCover: 0,
expectedSeize: 0,
proceedsBase: 0,
repayCostBase: 0,
flashLoanFeeBase: 0,
gasCostBase: 0,
netProfitBase: 0,
closeFactorBps: q.closeFactorBps
});
}
q.debtToCover = maxRepay;
uint8 dDec = IERC20Minimal(debtAsset).decimals();
uint8 cDec = IERC20Minimal(collateralAsset).decimals();
// Calculate base cost to repay debt (without flash loan fee)
uint256 debtValueBase = _mulDiv(q.debtToCover, pxDebt, _pow10(dDec));
// Get flash loan fee from Aave Pool (default 0.05% = 5 bps, but query for accuracy)
uint256 flashLoanFeeBps = uint256(pool.FLASHLOAN_PREMIUM_TOTAL());
// Calculate flash loan fee in base currency
// Fee is paid on the borrowed amount (debtToCover)
q.flashLoanFeeBase = _mulDiv(debtValueBase, flashLoanFeeBps, 10_000);
// Total repay cost includes debt + flash loan fee
// Note: In flash loan liquidation, we borrow debtToCover and must repay it + fee
q.repayCostBase = debtValueBase + q.flashLoanFeeBase;
// Calculate theoretical seizure amount with liquidation bonus
// IMPORTANT: Liquidation bonus is based on DEBT REPAID, not flash loan fee
// The flash loan fee is our cost, not part of the liquidation itself
uint256 repayValueInCollUnits = _mulDiv(debtValueBase, _pow10(cDec), pxColl);
uint256 seizeRaw = (repayValueInCollUnits * liqBonusBps) / 10_000;
// Check actual available collateral balance before fee calculation
(uint256 aTokenBal, , , , , , , , ) = dataProvider.getUserReserveData(collateralAsset, user);
// Cap the seizure to available balance early to ensure accurate fee calculation
uint256 seizeBeforeFee = seizeRaw > aTokenBal ? aTokenBal : seizeRaw;
// Calculate bonus and apply protocol fee only to the bonus portion
uint256 liqProtFeeBps = dataProvider.getLiquidationProtocolFee(collateralAsset);
uint256 bonusUnits = (seizeBeforeFee > repayValueInCollUnits) ? (seizeBeforeFee - repayValueInCollUnits) : 0;
uint256 protocolFee = _mulDiv(bonusUnits, liqProtFeeBps, 10_000);
uint256 bonusAfterFee = bonusUnits - protocolFee;
// Final seizure amount: principal repayment + bonus after protocol fee
q.expectedSeize = (seizeBeforeFee > repayValueInCollUnits)
? (repayValueInCollUnits + bonusAfterFee)
: seizeBeforeFee;
uint256 grossProceedsBase = _mulDiv(q.expectedSeize, pxColl, _pow10(cDec));
uint256 proceedsHaircut = _mulDiv(grossProceedsBase, slippageBps, 10_000);
q.proceedsBase = grossProceedsBase - proceedsHaircut;
uint256 pxEthBase = oracle.getAssetPrice(WETH);
q.gasCostBase = _mulDiv(gasUnits * gasPriceWei, pxEthBase, 1e18);
q.netProfitBase = int256(q.proceedsBase) - int256(q.repayCostBase) - int256(q.gasCostBase);
q.profitable = (q.netProfitBase > 0);
}
// ---------- utils ----------
function _pow10(uint8 n) internal pure returns (uint256) {
uint256 x=1; unchecked{for(uint8 i; i<n; ++i) x*=10;} return x;
}
function _mulDiv(uint256 a,uint256 b,uint256 d) internal pure returns(uint256){return (a*b)/d;}
}"
},
"contracts/ownership/MultiOwnable.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
/**
* @title MultiOwnable
* @author antonis@typesystem.xyz
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract MultiOwnable {
mapping(address => bool) public isOwner;
address[] public owners;
mapping(address => uint256) private ownerIndex;
modifier onlyOwner() {
require(isOwner[msg.sender], "Not an owner");
_;
}
constructor() {
isOwner[msg.sender] = true;
owners.push(msg.sender);
ownerIndex[msg.sender] = 0;
}
/**
* @dev Allows an owner to add a new owner.
* @param _newOwner The address of the new owner.
*/
function addOwner(address _newOwner) external onlyOwner {
require(_newOwner != address(0), "Invalid address");
require(!isOwner[_newOwner], "Address is already an owner");
isOwner[_newOwner] = true;
owners.push(_newOwner);
ownerIndex[_newOwner] = owners.length - 1;
}
/**
* @dev Allows an owner to remove another owner.
* @param _oldOwner The address of the owner to remove.
*/
function removeOwner(address _oldOwner) external onlyOwner {
require(isOwner[_oldOwner], "Address is not an owner");
require(owners.length > 1, "Cannot remove the last owner");
uint256 indexToRemove = ownerIndex[_oldOwner];
address lastOwner = owners[owners.length - 1];
// Swap the owner to be removed with the last owner in the array
owners[indexToRemove] = lastOwner;
ownerIndex[lastOwner] = indexToRemove;
// Remove the last element
owners.pop();
delete isOwner[_oldOwner];
delete ownerIndex[_oldOwner];
}
}
"
}
},
"settings": {
"remappings": [
"@uniswap/universal-router/=lib/universal-router/",
"@uniswap/v4-core/=lib/v4-core/",
"@uniswap/v4-periphery/=lib/v4-periphery/",
"@uniswap/permit2/=lib/permit2/",
"@uniswap/v3-core/=lib/v3-core/",
"@uniswap/v3-periphery/=lib/v3-periphery/",
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"@protocol-v3/contracts/=lib/protocol-v3/contracts/",
"solidity-bytes-utils/=lib/solidity-bytes-utils/contracts/",
"@openzeppelin/=lib/openzeppelin-contracts/",
"@ensdomains/=lib/v4-core/node_modules/@ensdomains/",
"@uniswap/v2-core/=lib/universal-router/node_modules/@uniswap/v2-core/",
"aave-address-book/=lib/aave-address-book/src/",
"aave-v3-core/=lib/aave-v3-core/",
"aave-v3-origin/=lib/aave-address-book/lib/aave-v3-origin/src/",
"ds-test/=lib/universal-router/lib/forge-std/lib/ds-test/src/",
"erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
"forge-gas-snapshot/=lib/permit2/lib/forge-gas-snapshot/src/",
"forge-std/=lib/universal-router/lib/forge-std/src/",
"halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
"hardhat/=lib/v4-core/node_modules/hardhat/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"permit2/=lib/permit2/",
"protocol-v3/=lib/protocol-v3/",
"solidity-utils/=lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/src/",
"solmate/=lib/universal-router/lib/solmate/",
"universal-router/=lib/universal-router/",
"v3-core/=lib/v3-core/",
"v3-periphery/=lib/universal-router/lib/v3-periphery/contracts/",
"v4-core/=lib/v4-core/src/",
"v4-periphery/=lib/v4-periphery/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "prague",
"viaIR": true
}
}}
Submitted on: 2025-10-24 11:16:44
Comments
Log in to comment.
No comments yet.