PositionLensAaveV3Main

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
  }
}}

Tags:
DeFi, Swap, Liquidity, Factory, Oracle|addr:0x3f166eabf0a7a031a62d0e416fb9b387a1d58c54|verified:true|block:23636185|tx:0x58fc04bc6ad184608dcd0f1b2af55916728335f40f65ed1eb5230cf0c044c1c9|first_check:1761297401

Submitted on: 2025-10-24 11:16:44

Comments

Log in to comment.

No comments yet.