Moolah

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/moolah/Moolah.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { AccessControlEnumerableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import { Id, IMoolahStaticTyping, IMoolahBase, MarketParams, Position, Market, Authorization, Signature } from "./interfaces/IMoolah.sol";
import { IMoolahLiquidateCallback, IMoolahRepayCallback, IMoolahSupplyCallback, IMoolahSupplyCollateralCallback, IMoolahFlashLoanCallback } from "./interfaces/IMoolahCallbacks.sol";
import { IIrm } from "./interfaces/IIrm.sol";
import { IERC20 } from "./interfaces/IERC20.sol";
import { IOracle } from "./interfaces/IOracle.sol";

import "./libraries/ConstantsLib.sol";
import { UtilsLib } from "./libraries/UtilsLib.sol";
import { EventsLib } from "./libraries/EventsLib.sol";
import { ErrorsLib } from "./libraries/ErrorsLib.sol";
import { MathLib, WAD } from "./libraries/MathLib.sol";
import { SharesMathLib } from "./libraries/SharesMathLib.sol";
import { MarketParamsLib } from "./libraries/MarketParamsLib.sol";
import { SafeTransferLib } from "./libraries/SafeTransferLib.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import { IProvider } from "../provider/interfaces/IProvider.sol";

/// @title Moolah
/// @author Lista DAO
/// @notice The Moolah contract.
contract Moolah is
  UUPSUpgradeable,
  AccessControlEnumerableUpgradeable,
  ReentrancyGuardUpgradeable,
  PausableUpgradeable,
  IMoolahStaticTyping
{
  using MathLib for uint128;
  using MathLib for uint256;
  using UtilsLib for uint256;
  using SharesMathLib for uint256;
  using SafeTransferLib for IERC20;
  using MarketParamsLib for MarketParams;
  using EnumerableSet for EnumerableSet.AddressSet;

  /* IMMUTABLES */

  /// @inheritdoc IMoolahBase
  bytes32 public immutable DOMAIN_SEPARATOR;

  /* STORAGE */

  /// @inheritdoc IMoolahBase
  address public feeRecipient;
  /// @inheritdoc IMoolahStaticTyping
  mapping(Id => mapping(address => Position)) public position;
  /// @inheritdoc IMoolahStaticTyping
  mapping(Id => Market) public market;
  /// @inheritdoc IMoolahBase
  mapping(address => bool) public isIrmEnabled;
  /// @inheritdoc IMoolahBase
  mapping(uint256 => bool) public isLltvEnabled;
  /// @inheritdoc IMoolahBase
  mapping(address => mapping(address => bool)) public isAuthorized;
  /// @inheritdoc IMoolahBase
  mapping(address => uint256) public nonce;
  /// @inheritdoc IMoolahStaticTyping
  mapping(Id => MarketParams) public idToMarketParams;
  /// marketId => liquidation whitelist addresses
  mapping(Id => EnumerableSet.AddressSet) private liquidationWhitelist;
  /// The minimum loan token position value, using the same precision as the oracle (8 decimals).
  uint256 public minLoanValue;
  /// @inheritdoc IMoolahBase
  mapping(Id => mapping(address => address)) public providers;

  /// if whitelist is set, only whitelisted addresses can supply, supply collateral, borrow
  mapping(Id => EnumerableSet.AddressSet) private whiteList;

  /// default market fee rate
  uint256 public defaultMarketFee;

  bytes32 public constant MANAGER = keccak256("MANAGER"); // manager role
  bytes32 public constant PAUSER = keccak256("PAUSER"); // pauser role
  bytes32 public constant OPERATOR = keccak256("OPERATOR"); // operator role

  /* CONSTRUCTOR */

  /// @custom:oz-upgrades-unsafe-allow constructor
  constructor() {
    _disableInitializers();
    DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
  }

  /// @param admin The new admin of the contract.
  /// @param manager The new manager of the contract.
  /// @param pauser The new pauser of the contract.
  function initialize(address admin, address manager, address pauser, uint256 _minLoanValue) public initializer {
    require(admin != address(0), ErrorsLib.ZERO_ADDRESS);
    require(manager != address(0), ErrorsLib.ZERO_ADDRESS);
    require(pauser != address(0), ErrorsLib.ZERO_ADDRESS);

    __Pausable_init();
    __AccessControl_init();
    __ReentrancyGuard_init();

    _grantRole(DEFAULT_ADMIN_ROLE, admin);
    _grantRole(MANAGER, manager);
    _grantRole(PAUSER, pauser);

    minLoanValue = _minLoanValue;
  }

  /* ONLY MANAGER FUNCTIONS */

  /// @inheritdoc IMoolahBase
  function enableIrm(address irm) external onlyRole(MANAGER) {
    require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET);

    isIrmEnabled[irm] = true;

    emit EventsLib.EnableIrm(irm);
  }

  /// @inheritdoc IMoolahBase
  function enableLltv(uint256 lltv) external onlyRole(MANAGER) {
    require(!isLltvEnabled[lltv], ErrorsLib.ALREADY_SET);
    require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED);

    isLltvEnabled[lltv] = true;

    emit EventsLib.EnableLltv(lltv);
  }

  /// @inheritdoc IMoolahBase
  function setFee(MarketParams memory marketParams, uint256 newFee) external onlyRole(MANAGER) {
    Id id = marketParams.id();
    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
    require(newFee != market[id].fee, ErrorsLib.ALREADY_SET);
    require(newFee <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);

    // Accrue interest using the previous fee set before changing it.
    _accrueInterest(marketParams, id);

    // Safe "unchecked" cast.
    market[id].fee = uint128(newFee);

    emit EventsLib.SetFee(id, newFee);
  }

  /// @inheritdoc IMoolahBase
  function setDefaultMarketFee(uint256 newFee) external onlyRole(MANAGER) {
    require(newFee != defaultMarketFee, ErrorsLib.ALREADY_SET);
    require(newFee <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);

    defaultMarketFee = newFee;

    emit EventsLib.SetDefaultMarketFee(newFee);
  }

  /// @inheritdoc IMoolahBase
  function setFeeRecipient(address newFeeRecipient) external onlyRole(MANAGER) {
    require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);

    feeRecipient = newFeeRecipient;

    emit EventsLib.SetFeeRecipient(newFeeRecipient);
  }

  /// @inheritdoc IMoolahBase
  function setMinLoanValue(uint256 _minLoanValue) external onlyRole(MANAGER) {
    require(_minLoanValue != minLoanValue, ErrorsLib.ALREADY_SET);
    minLoanValue = _minLoanValue;

    emit EventsLib.SetMinLoanValue(_minLoanValue);
  }

  /// @inheritdoc IMoolahBase
  function addLiquidationWhitelist(Id id, address account) public onlyRole(MANAGER) {
    require(!liquidationWhitelist[id].contains(account), ErrorsLib.ALREADY_SET);
    liquidationWhitelist[id].add(account);

    emit EventsLib.AddLiquidationWhitelist(id, account);
  }

  /// @inheritdoc IMoolahBase
  function removeLiquidationWhitelist(Id id, address account) public onlyRole(MANAGER) {
    require(liquidationWhitelist[id].contains(account), ErrorsLib.NOT_SET);
    liquidationWhitelist[id].remove(account);

    emit EventsLib.RemoveLiquidationWhitelist(id, account);
  }

  /// @inheritdoc IMoolahBase
  function batchToggleLiquidationWhitelist(
    Id[] memory ids,
    address[][] memory accounts,
    bool isAddition
  ) external onlyRole(MANAGER) {
    require(ids.length == accounts.length, ErrorsLib.INCONSISTENT_INPUT);
    // add/remove accounts from liquidation whitelist for each market
    for (uint256 i = 0; i < ids.length; ++i) {
      Id id = ids[i];
      address[] memory accountList = accounts[i];
      for (uint256 j = 0; j < accountList.length; ++j) {
        address account = accountList[j];
        // add to whitelist
        if (isAddition) {
          addLiquidationWhitelist(id, account);
        } else {
          // remove from whitelist
          removeLiquidationWhitelist(id, account);
        }
      }
    }
  }

  /// @inheritdoc IMoolahBase
  function addWhiteList(Id id, address account) external onlyRole(MANAGER) {
    require(!whiteList[id].contains(account), ErrorsLib.ALREADY_SET);
    whiteList[id].add(account);

    emit EventsLib.AddWhiteList(id, account);
  }

  /// @inheritdoc IMoolahBase
  function removeWhiteList(Id id, address account) external onlyRole(MANAGER) {
    require(whiteList[id].contains(account), ErrorsLib.NOT_SET);
    whiteList[id].remove(account);

    emit EventsLib.RemoveWhiteList(id, account);
  }

  function addProvider(Id id, address provider) external onlyRole(MANAGER) {
    address token = IProvider(provider).TOKEN();
    require(token != address(0), ErrorsLib.ZERO_ADDRESS);
    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
    require(provider != address(0), ErrorsLib.ZERO_ADDRESS);
    require(providers[id][token] == address(0), ErrorsLib.ALREADY_SET);

    providers[id][token] = provider;

    emit EventsLib.AddProvider(id, token, provider);
  }

  function removeProvider(Id id, address token) external onlyRole(MANAGER) {
    require(providers[id][token] != address(0), ErrorsLib.NOT_SET);

    address provider = providers[id][token];
    delete providers[id][token];

    emit EventsLib.RemoveProvider(id, token, provider);
  }

  /* MARKET CREATION */

  /// @inheritdoc IMoolahBase
  function createMarket(MarketParams memory marketParams) external {
    require(getRoleMemberCount(OPERATOR) == 0 || hasRole(OPERATOR, msg.sender), ErrorsLib.UNAUTHORIZED);
    Id id = marketParams.id();
    require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
    require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
    require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
    require(marketParams.oracle != address(0), ErrorsLib.ZERO_ADDRESS);
    require(marketParams.loanToken != address(0), ErrorsLib.ZERO_ADDRESS);
    require(marketParams.collateralToken != address(0), ErrorsLib.ZERO_ADDRESS);

    // Safe "unchecked" cast.
    market[id].lastUpdate = uint128(block.timestamp);
    market[id].fee = uint128(defaultMarketFee);
    idToMarketParams[id] = marketParams;
    IOracle(marketParams.oracle).peek(marketParams.loanToken);
    IOracle(marketParams.oracle).peek(marketParams.collateralToken);

    emit EventsLib.CreateMarket(id, marketParams);

    // Call to initialize the IRM in case it is stateful.
    if (marketParams.irm != address(0)) IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
  }

  /* SUPPLY MANAGEMENT */

  /// @inheritdoc IMoolahBase
  function supply(
    MarketParams memory marketParams,
    uint256 assets,
    uint256 shares,
    address onBehalf,
    bytes calldata data
  ) external whenNotPaused nonReentrant returns (uint256, uint256) {
    Id id = marketParams.id();
    require(isWhiteList(id, onBehalf), ErrorsLib.NOT_WHITELIST);
    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
    require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
    require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);

    _accrueInterest(marketParams, id);

    if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
    else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);

    position[id][onBehalf].supplyShares += shares;
    market[id].totalSupplyShares += shares.toUint128();
    market[id].totalSupplyAssets += assets.toUint128();

    emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);

    if (data.length > 0) IMoolahSupplyCallback(msg.sender).onMoolahSupply(assets, data);

    IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);

    require(_checkSupplyAssets(marketParams, onBehalf), ErrorsLib.REMAIN_SUPPLY_TOO_LOW);

    return (assets, shares);
  }

  /// @inheritdoc IMoolahBase
  function withdraw(
    MarketParams memory marketParams,
    uint256 assets,
    uint256 shares,
    address onBehalf,
    address receiver
  ) external whenNotPaused nonReentrant returns (uint256, uint256) {
    Id id = marketParams.id();
    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
    require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
    require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
    // No need to verify that onBehalf != address(0) thanks to the following authorization check.
    require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);

    _accrueInterest(marketParams, id);

    if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
    else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);

    position[id][onBehalf].supplyShares -= shares;
    market[id].totalSupplyShares -= shares.toUint128();
    market[id].totalSupplyAssets -= assets.toUint128();

    require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);

    emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);

    IERC20(marketParams.loanToken).safeTransfer(receiver, assets);

    return (assets, shares);
  }

  /* BORROW MANAGEMENT */

  /// @inheritdoc IMoolahBase
  function borrow(
    MarketParams memory marketParams,
    uint256 assets,
    uint256 shares,
    address onBehalf,
    address receiver
  ) external whenNotPaused nonReentrant returns (uint256, uint256) {
    Id id = marketParams.id();
    require(isWhiteList(id, onBehalf), ErrorsLib.NOT_WHITELIST);
    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
    require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
    require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
    // No need to verify that onBehalf != address(0) thanks to the following authorization check.
    address provider = providers[id][marketParams.loanToken];
    if (provider == msg.sender) {
      require(receiver == provider, ErrorsLib.NOT_PROVIDER);
    } else {
      require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
    }

    _accrueInterest(marketParams, id);

    if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
    else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);

    position[id][onBehalf].borrowShares += shares.toUint128();
    market[id].totalBorrowShares += shares.toUint128();
    market[id].totalBorrowAssets += assets.toUint128();

    require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
    require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);

    emit EventsLib.Borrow(id, msg.sender, onBehalf, receiver, assets, shares);

    IERC20(marketParams.loanToken).safeTransfer(receiver, assets);

    require(_checkBorrowAssets(marketParams, onBehalf), ErrorsLib.REMAIN_BORROW_TOO_LOW);

    return (assets, shares);
  }

  /// @inheritdoc IMoolahBase
  function repay(
    MarketParams memory marketParams,
    uint256 assets,
    uint256 shares,
    address onBehalf,
    bytes calldata data
  ) external whenNotPaused nonReentrant returns (uint256, uint256) {
    Id id = marketParams.id();
    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
    require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
    require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);

    _accrueInterest(marketParams, id);

    if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
    else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);

    position[id][onBehalf].borrowShares -= shares.toUint128();
    market[id].totalBorrowShares -= shares.toUint128();
    market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();

    // `assets` may be greater than `totalBorrowAssets` by 1.
    emit EventsLib.Repay(id, msg.sender, onBehalf, assets, shares);

    if (data.length > 0) IMoolahRepayCallback(msg.sender).onMoolahRepay(assets, data);

    IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);

    require(_checkBorrowAssets(marketParams, onBehalf), ErrorsLib.REMAIN_BORROW_TOO_LOW);

    return (assets, shares);
  }

  /* COLLATERAL MANAGEMENT */

  /// @inheritdoc IMoolahBase
  function supplyCollateral(
    MarketParams memory marketParams,
    uint256 assets,
    address onBehalf,
    bytes calldata data
  ) external whenNotPaused nonReentrant {
    Id id = marketParams.id();
    require(isWhiteList(id, onBehalf), ErrorsLib.NOT_WHITELIST);
    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
    require(assets != 0, ErrorsLib.ZERO_ASSETS);
    require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
    address provider = providers[id][marketParams.collateralToken];
    if (provider != address(0)) {
      require(msg.sender == provider, ErrorsLib.NOT_PROVIDER);
    }
    // Don't accrue interest because it's not required and it saves gas.

    position[id][onBehalf].collateral += assets.toUint128();

    emit EventsLib.SupplyCollateral(id, msg.sender, onBehalf, assets);

    if (data.length > 0) IMoolahSupplyCollateralCallback(msg.sender).onMoolahSupplyCollateral(assets, data);

    IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
  }

  /// @inheritdoc IMoolahBase
  function withdrawCollateral(
    MarketParams memory marketParams,
    uint256 assets,
    address onBehalf,
    address receiver
  ) external whenNotPaused nonReentrant {
    Id id = marketParams.id();
    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
    require(assets != 0, ErrorsLib.ZERO_ASSETS);
    require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
    // No need to verify that onBehalf != address(0) thanks to the following authorization check.
    address provider = providers[id][marketParams.collateralToken];
    if (provider != address(0)) {
      require(msg.sender == provider && receiver == provider, ErrorsLib.NOT_PROVIDER);
    } else {
      require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
    }

    _accrueInterest(marketParams, id);

    position[id][onBehalf].collateral -= assets.toUint128();

    require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);

    emit EventsLib.WithdrawCollateral(id, msg.sender, onBehalf, receiver, assets);

    IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
  }

  /* LIQUIDATION */

  /// @inheritdoc IMoolahBase
  function liquidate(
    MarketParams memory marketParams,
    address borrower,
    uint256 seizedAssets,
    uint256 repaidShares,
    bytes calldata data
  ) external whenNotPaused nonReentrant returns (uint256, uint256) {
    Id id = marketParams.id();
    require(_checkLiquidationWhiteList(id, msg.sender), ErrorsLib.NOT_LIQUIDATION_WHITELIST);
    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
    require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);

    _accrueInterest(marketParams, id);

    {
      uint256 collateralPrice = getPrice(marketParams);

      require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);

      // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
      uint256 liquidationIncentiveFactor = UtilsLib.min(
        MAX_LIQUIDATION_INCENTIVE_FACTOR,
        WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
      );

      if (seizedAssets > 0) {
        uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE);

        repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentiveFactor).toSharesUp(
          market[id].totalBorrowAssets,
          market[id].totalBorrowShares
        );
      } else {
        seizedAssets = repaidShares
          .toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares)
          .wMulDown(liquidationIncentiveFactor)
          .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
      }
    }
    uint256 repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);

    position[id][borrower].borrowShares -= repaidShares.toUint128();
    market[id].totalBorrowShares -= repaidShares.toUint128();
    market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();

    position[id][borrower].collateral -= seizedAssets.toUint128();

    uint256 badDebtShares;
    uint256 badDebtAssets;
    if (position[id][borrower].collateral == 0) {
      badDebtShares = position[id][borrower].borrowShares;
      badDebtAssets = UtilsLib.min(
        market[id].totalBorrowAssets,
        badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares)
      );

      market[id].totalBorrowAssets -= badDebtAssets.toUint128();
      market[id].totalSupplyAssets -= badDebtAssets.toUint128();
      market[id].totalBorrowShares -= badDebtShares.toUint128();
      position[id][borrower].borrowShares = 0;
    }

    // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
    emit EventsLib.Liquidate(
      id,
      msg.sender,
      borrower,
      repaidAssets,
      repaidShares,
      seizedAssets,
      badDebtAssets,
      badDebtShares
    );

    IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);

    {
      address provider = providers[id][marketParams.collateralToken];
      if (provider != address(0)) {
        IProvider(provider).liquidate(id, borrower);
      }
    }

    if (data.length > 0) IMoolahLiquidateCallback(msg.sender).onMoolahLiquidate(repaidAssets, data);

    IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);

    require(_isHealthyAfterLiquidate(marketParams, borrower), ErrorsLib.UNHEALTHY_POSITION);

    return (seizedAssets, repaidAssets);
  }

  /* FLASH LOANS */

  /// @inheritdoc IMoolahBase
  function flashLoan(address token, uint256 assets, bytes calldata data) external whenNotPaused {
    require(assets != 0, ErrorsLib.ZERO_ASSETS);

    emit EventsLib.FlashLoan(msg.sender, token, assets);

    IERC20(token).safeTransfer(msg.sender, assets);

    IMoolahFlashLoanCallback(msg.sender).onMoolahFlashLoan(assets, data);

    IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
  }

  /* AUTHORIZATION */

  /// @inheritdoc IMoolahBase
  function setAuthorization(address authorized, bool newIsAuthorized) external {
    require(newIsAuthorized != isAuthorized[msg.sender][authorized], ErrorsLib.ALREADY_SET);

    isAuthorized[msg.sender][authorized] = newIsAuthorized;

    emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized);
  }

  /// @inheritdoc IMoolahBase
  function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
    /// Do not check whether authorization is already set because the nonce increment is a desired side effect.
    require(block.timestamp <= authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED);
    require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE);

    bytes32 hashStruct = keccak256(abi.encode(AUTHORIZATION_TYPEHASH, authorization));
    bytes32 digest = keccak256(bytes.concat("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
    address signatory = ecrecover(digest, signature.v, signature.r, signature.s);

    require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);

    emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce);

    isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized;

    emit EventsLib.SetAuthorization(
      msg.sender,
      authorization.authorizer,
      authorization.authorized,
      authorization.isAuthorized
    );
  }

  /// @dev Returns whether the sender is authorized to manage `onBehalf`'s positions.
  function _isSenderAuthorized(address onBehalf) internal view returns (bool) {
    return msg.sender == onBehalf || isAuthorized[onBehalf][msg.sender];
  }

  /* INTEREST MANAGEMENT */

  /// @inheritdoc IMoolahBase
  function accrueInterest(MarketParams memory marketParams) external {
    Id id = marketParams.id();
    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);

    _accrueInterest(marketParams, id);
  }

  /// @dev Accrues interest for the given market `marketParams`.
  /// @dev Assumes that the inputs `marketParams` and `id` match.
  function _accrueInterest(MarketParams memory marketParams, Id id) internal {
    uint256 elapsed = block.timestamp - market[id].lastUpdate;
    if (elapsed == 0) return;

    if (marketParams.irm != address(0)) {
      uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
      uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
      market[id].totalBorrowAssets += interest.toUint128();
      market[id].totalSupplyAssets += interest.toUint128();

      uint256 feeShares;
      if (market[id].fee != 0) {
        uint256 feeAmount = interest.wMulDown(market[id].fee);
        // The fee amount is subtracted from the total supply in this calculation to compensate for the fact
        // that total supply is already increased by the full interest (including the fee amount).
        feeShares = feeAmount.toSharesDown(market[id].totalSupplyAssets - feeAmount, market[id].totalSupplyShares);
        position[id][feeRecipient].supplyShares += feeShares;
        market[id].totalSupplyShares += feeShares.toUint128();
      }

      emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares);
    }

    // Safe "unchecked" cast.
    market[id].lastUpdate = uint128(block.timestamp);
  }

  /* HEALTH CHECK */

  /// @dev Returns whether the position of `borrower` in the given market `marketParams` is healthy.
  /// @dev Assumes that the inputs `marketParams` and `id` match.
  function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) {
    if (position[id][borrower].borrowShares == 0) return true;
    uint256 collateralPrice = getPrice(marketParams);

    return _isHealthy(marketParams, id, borrower, collateralPrice);
  }

  /// @dev Returns whether the position of `borrower` in the given market `marketParams` is healthy.
  /// @dev Assumes that the inputs `marketParams` and `id` match.
  function isHealthy(MarketParams memory marketParams, Id id, address borrower) external view returns (bool) {
    return _isHealthy(marketParams, id, borrower);
  }

  /// @dev Returns whether the position of `borrower` in the given market `marketParams` with the given
  /// `collateralPrice` is healthy.
  /// @dev Assumes that the inputs `marketParams` and `id` match.
  /// @dev Rounds in favor of the protocol, so one might not be able to borrow exactly `maxBorrow` but one unit less.
  function _isHealthy(
    MarketParams memory marketParams,
    Id id,
    address borrower,
    uint256 collateralPrice
  ) internal view returns (bool) {
    uint256 borrowed = uint256(position[id][borrower].borrowShares).toAssetsUp(
      market[id].totalBorrowAssets,
      market[id].totalBorrowShares
    );
    uint256 maxBorrow = uint256(position[id][borrower].collateral)
      .mulDivDown(collateralPrice, ORACLE_PRICE_SCALE)
      .wMulDown(marketParams.lltv);

    return maxBorrow >= borrowed;
  }

  function getPrice(MarketParams memory marketParams) public view returns (uint256) {
    IOracle _oracle = IOracle(marketParams.oracle);
    uint256 baseTokenDecimals = IERC20Metadata(marketParams.collateralToken).decimals();
    uint256 quotaTokenDecimals = IERC20Metadata(marketParams.loanToken).decimals();
    uint256 basePrice = _oracle.peek(marketParams.collateralToken);
    uint256 quotaPrice = _oracle.peek(marketParams.loanToken);

    uint256 scaleFactor = 10 ** (36 + quotaTokenDecimals - baseTokenDecimals);
    return scaleFactor.mulDivDown(basePrice, quotaPrice);
  }

  /// @inheritdoc IMoolahBase
  function getWhiteList(Id id) external view returns (address[] memory) {
    return whiteList[id].values();
  }

  /// @inheritdoc IMoolahBase
  function isWhiteList(Id id, address account) public view returns (bool) {
    return whiteList[id].length() == 0 || whiteList[id].contains(account);
  }

  /// @inheritdoc IMoolahBase
  function getLiquidationWhitelist(Id id) external view returns (address[] memory) {
    address[] memory whitelist = new address[](liquidationWhitelist[id].length());
    for (uint256 i = 0; i < liquidationWhitelist[id].length(); i++) {
      whitelist[i] = liquidationWhitelist[id].at(i);
    }
    return whitelist;
  }

  /// @inheritdoc IMoolahBase
  function isLiquidationWhitelist(Id id, address account) external view returns (bool) {
    return _checkLiquidationWhiteList(id, account);
  }

  function _checkLiquidationWhiteList(Id id, address account) internal view returns (bool) {
    return liquidationWhitelist[id].length() == 0 || liquidationWhitelist[id].contains(account);
  }

  function _checkSupplyAssets(MarketParams memory marketParams, address account) internal view returns (bool) {
    Id id = marketParams.id();
    if (position[id][account].supplyShares == 0) {
      return true;
    }

    return
      uint256(position[id][account].supplyShares).toAssetsDown(
        market[id].totalSupplyAssets,
        market[id].totalSupplyShares
      ) >= minLoan(marketParams);
  }

  function _checkBorrowAssets(MarketParams memory marketParams, address account) internal view returns (bool) {
    Id id = marketParams.id();
    if (position[id][account].borrowShares == 0) {
      return true;
    }

    return
      uint256(position[id][account].borrowShares).toAssetsDown(
        market[id].totalBorrowAssets,
        market[id].totalBorrowShares
      ) >= minLoan(marketParams);
  }

  function _isHealthyAfterLiquidate(MarketParams memory marketParams, address account) internal view returns (bool) {
    Id id = marketParams.id();
    if (position[id][account].borrowShares == 0 || position[id][account].collateral == 0) {
      return true;
    }

    uint256 borrowAssets = uint256(position[id][account].borrowShares).toAssetsDown(
      market[id].totalBorrowAssets,
      market[id].totalBorrowShares
    );
    if (borrowAssets >= minLoan(marketParams)) {
      return true;
    }
    return _isHealthy(marketParams, marketParams.id(), account, getPrice(marketParams));
  }

  /// @inheritdoc IMoolahBase
  function minLoan(MarketParams memory marketParams) public view returns (uint256) {
    uint256 price = IOracle(marketParams.oracle).peek(marketParams.loanToken);
    uint8 decimals = IERC20Metadata(marketParams.loanToken).decimals();
    return price == 0 ? 0 : minLoanValue.mulDivDown(10 ** decimals, price);
  }

  /**
   * @dev pause contract
   */
  function pause() external onlyRole(PAUSER) {
    _pause();
  }

  /**
   * @dev unpause contract
   */
  function unpause() external onlyRole(MANAGER) {
    _unpause();
  }

  function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {}
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/access/extensions/AccessControlEnumerableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/AccessControlEnumerable.sol)

pragma solidity ^0.8.20;

import {IAccessControlEnumerable} from "@openzeppelin/contracts/access/extensions/IAccessControlEnumerable.sol";
import {AccessControlUpgradeable} from "../AccessControlUpgradeable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";

/**
 * @dev Extension of {AccessControl} that allows enumerating the members of each role.
 */
abstract contract AccessControlEnumerableUpgradeable is Initializable, IAccessControlEnumerable, AccessControlUpgradeable {
    using EnumerableSet for EnumerableSet.AddressSet;

    /// @custom:storage-location erc7201:openzeppelin.storage.AccessControlEnumerable
    struct AccessControlEnumerableStorage {
        mapping(bytes32 role => EnumerableSet.AddressSet) _roleMembers;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControlEnumerable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant AccessControlEnumerableStorageLocation = 0xc1f6fe24621ce81ec5827caf0253cadb74709b061630e6b55e82371705932000;

    function _getAccessControlEnumerableStorage() private pure returns (AccessControlEnumerableStorage storage $) {
        assembly {
            $.slot := AccessControlEnumerableStorageLocation
        }
    }

    function __AccessControlEnumerable_init() internal onlyInitializing {
    }

    function __AccessControlEnumerable_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns one of the accounts that have `role`. `index` must be a
     * value between 0 and {getRoleMemberCount}, non-inclusive.
     *
     * Role bearers are not sorted in any particular way, and their ordering may
     * change at any point.
     *
     * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
     * you perform all queries on the same block. See the following
     * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
     * for more information.
     */
    function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address) {
        AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
        return $._roleMembers[role].at(index);
    }

    /**
     * @dev Returns the number of accounts that have `role`. Can be used
     * together with {getRoleMember} to enumerate all bearers of a role.
     */
    function getRoleMemberCount(bytes32 role) public view virtual returns (uint256) {
        AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
        return $._roleMembers[role].length();
    }

    /**
     * @dev Return all accounts that have `role`
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function getRoleMembers(bytes32 role) public view virtual returns (address[] memory) {
        AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
        return $._roleMembers[role].values();
    }

    /**
     * @dev Overload {AccessControl-_grantRole} to track enumerable memberships
     */
    function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
        AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
        bool granted = super._grantRole(role, account);
        if (granted) {
            $._roleMembers[role].add(account);
        }
        return granted;
    }

    /**
     * @dev Overload {AccessControl-_revokeRole} to track enumerable memberships
     */
    function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
        AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
        bool revoked = super._revokeRole(role, account);
        if (revoked) {
            $._roleMembers[role].remove(account);
        }
        return revoked;
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/utils/UUPSUpgradeable.sol)

pragma solidity ^0.8.22;

import {IERC1822Proxiable} from "../../interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol";

/**
 * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
 * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
 *
 * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
 * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
 * `UUPSUpgradeable` with a custom implementation of upgrades.
 *
 * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
 */
abstract contract UUPSUpgradeable is IERC1822Proxiable {
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
    address private immutable __self = address(this);

    /**
     * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
     * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
     * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
     * If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
     * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
     * during an upgrade.
     */
    string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";

    /**
     * @dev The call is from an unauthorized context.
     */
    error UUPSUnauthorizedCallContext();

    /**
     * @dev The storage `slot` is unsupported as a UUID.
     */
    error UUPSUnsupportedProxiableUUID(bytes32 slot);

    /**
     * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
     * a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
     * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
     * function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
     * fail.
     */
    modifier onlyProxy() {
        _checkProxy();
        _;
    }

    /**
     * @dev Check that the execution is not being performed through a delegate call. This allows a function to be
     * callable on the implementing contract but not through proxies.
     */
    modifier notDelegated() {
        _checkNotDelegated();
        _;
    }

    /**
     * @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
     * implementation. It is used to validate the implementation's compatibility when performing an upgrade.
     *
     * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
     * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
     * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
     */
    function proxiableUUID() external view virtual notDelegated returns (bytes32) {
        return ERC1967Utils.IMPLEMENTATION_SLOT;
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
     * encoded in `data`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     *
     * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
     */
    function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallUUPS(newImplementation, data);
    }

    /**
     * @dev Reverts if the execution is not performed via delegatecall or the execution
     * context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
     * See {_onlyProxy}.
     */
    function _checkProxy() internal view virtual {
        if (
            address(this) == __self || // Must be called through delegatecall
            ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
        ) {
            revert UUPSUnauthorizedCallContext();
        }
    }

    /**
     * @dev Reverts if the execution is performed via delegatecall.
     * See {notDelegated}.
     */
    function _checkNotDelegated() internal view virtual {
        if (address(this) != __self) {
            // Must not be called through delegatecall
            revert UUPSUnauthorizedCallContext();
        }
    }

    /**
     * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
     * {upgradeToAndCall}.
     *
     * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
     *
     * ```solidity
     * function _authorizeUpgrade(address) internal onlyOwner {}
     * ```
     */
    function _authorizeUpgrade(address newImplementation) internal virtual;

    /**
     * @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
     *
     * As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
     * is expected to be the implementation slot in ERC-1967.
     *
     * Emits an {IERC1967-Upgraded} event.
     */
    function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
        try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
            if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
                revert UUPSUnsupportedProxiableUUID(slot);
            }
            ERC1967Utils.upgradeToAndCall(newImplementation, data);
        } catch {
            // The implementation is not UUPS
            revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
        }
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC-20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/utils/PausableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)

pragma solidity ^0.8.20;

import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Pausable
    struct PausableStorage {
        bool _paused;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;

    function _getPausableStorage() private pure returns (PausableStorage storage $) {
        assembly {
            $.slot := PausableStorageLocation
        }
    }

    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    /**
     * @dev The operation failed because the contract is paused.
     */
    error EnforcedPause();

    /**
     * @dev The operation failed because the contract is not paused.
     */
    error ExpectedPause();

    /**
     * @dev Initializes the contract in unpaused state.
     */
    function __Pausable_init() internal onlyInitializing {
        __Pausable_init_unchained();
    }

    function __Pausable_init_unchained() internal onlyInitializing {
        PausableStorage storage $ = _getPausableStorage();
        $._paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        PausableStorage storage $ = _getPausableStorage();
        return $._paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        if (paused()) {
            revert EnforcedPause();
        }
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        if (!paused()) {
            revert ExpectedPause();
        }
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        PausableStorage storage $ = _getPausableStorage();
        $._paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        PausableStorage storage $ = _getPausableStorage();
        $._paused = false;
        emit Unpaused(_msgSender());
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuardUpgradeable is Initializable {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    /// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
    struct ReentrancyGuardStorage {
        uint256 _status;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;

    function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
        assembly {
            $.slot := ReentrancyGuardStorageLocation
        }
    }

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    function __ReentrancyGuard_init() internal onlyInitializing {
        __ReentrancyGuard_init_unchained();
    }

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        $._status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if ($._status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        $._status = ENTERED;
    }

    function _nonReentrantAfter() private {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        $._status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        return $._status == ENTERED;
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position is the index of the value in the `values` array plus 1.
        // Position 0 is used to mean a value is not in the set.
        mapping(bytes32 value => uint256) _positions;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._positions[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We cache the value's position to prevent multiple reads from the same storage slot
        uint256 position = set._positions[value];

        if (position != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 valueIndex = position - 1;
            uint256 lastIndex = set._values.length - 1;

            if (valueIndex != lastIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the lastValue to the index where the value to delete is
                set._values[valueIndex] = lastValue;
                // Update the tracked position of the lastValue (that was just moved)
                set._positions[lastValue] = position;
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the tracked position for the deleted slot
            delete set._positions[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._positions[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is 

Tags:
ERC20, ERC165, Proxy, Pausable, Swap, Liquidity, Upgradeable, Factory, Oracle|addr:0x703ac563d4dfb3404dd8a31551f978e85eebb1ce|verified:true|block:23445769|tx:0x08e645ee4ab31294c31d81d010fe301aecb716745e84d329ba03630a4f9f176f|first_check:1758878364

Submitted on: 2025-09-26 11:19:27

Comments

Log in to comment.

No comments yet.