ETH Price: $2,267.62 (+7.56%)

Contract

0xdFD097cD3FE208fcBbee09B4bEf79442F5693071
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

Multichain Info

1 address found via
Transaction Hash
Method
Block
From
To
Rescue Tokens215730592025-01-07 14:01:59432 days ago1736258519IN
0xdFD097cD...2F5693071
0 ETH0.0007359311.39162746

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
StrategyManagerVE

Compiler Version
v0.8.25+commit.b61c2a91

Optimization Enabled:
Yes with 1 runs

Other Settings:
paris EvmVersion
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

// contracts
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

// libraries
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { RayMath } from "../libs/RayMath.sol";
import { IsContract } from "../libs/IsContract.sol";

// interfaces
import { IStrategyManager } from "../interfaces/IStrategyManager.sol";
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
import { ILiquidityManager } from "../interfaces/ILiquidityManager.sol";
import { IEcclesiaDao } from "../interfaces/IEcclesiaDao.sol";
import { IAaveLendingPoolV3 } from "../interfaces/IAaveLendingPoolV3.sol";
import { IAaveRewardsController } from "../interfaces/IAaveRewardsController.sol";

//======== ERRORS ========//

error NotAValidStrategy();
error NotLiquidityManager();
error OnlyWhitelistCanDepositLiquidity();
error UseOfUnderlyingAssetNotSupported();
error RateAboveMax();
error ArgumentLengthMismatch();
error TransferCallFailed();

/**
 * @title Athena Strategy Manager
 * @author vblackwhale
 *
 * This contract manages the assets deposited in Athena pools as liquidity.
 * It is responsible for depositing and withdrawing assets from various DeFi protocols.
 * It also computes the rewards and performance fees for the DAO.
 *
 * @dev This version is an upgraded version of the v0 allowing for Amphor strategies
 * on top of the Aave v3 USDC strategy.
 *
 */
contract StrategyManagerVE is IStrategyManager, Ownable {
  using SafeERC20 for IERC20;
  using RayMath for uint256;

  uint256 constant PERCENTAGE_BASE = 100;
  uint256 constant HUNDRED_PERCENT = PERCENTAGE_BASE * RayMath.RAY;

  //======== STORAGE ========//
  ILiquidityManager public liquidityManager;
  IEcclesiaDao public ecclesiaDao;
  // Address of the buyback & burn wallet
  address public buybackWallet;

  // Amount of underlying to be deducted from payout in RAY
  uint256 public payoutDeductibleRate;
  // Amount of performance fee to be paid to ecclesiaDao in RAY
  uint256 public strategyFeeRate;

  // (((Strategy 0))) - AAVE v3 USDC
  IAaveLendingPoolV3 public aaveLendingPool;
  address public USDC; // underlyingAsset
  address public aUSDC; // wrappedAsset

  // Lido LST Token
  address public wstETH; // 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0
  // (((Strategy 1))) - Amphor Restaked ETH
  address public amphrETH; // 0x5fD13359Ba15A84B76f7F87568309040176167cd
  // (((Strategy 2))) - Amphor Symbiotic LRT
  address public amphrLRT; // 0x06824c27c8a0dbde5f72f770ec82e3c0fd4dcec3

  bool public isWhitelistEnabled;
  mapping(address account_ => bool isWhiteListed_)
    public whiteListedLiquidityProviders;

  //======== CONSTRCUTOR ========//

  constructor(
    ILiquidityManager liquidityManager_,
    IEcclesiaDao ecclesiaDao_,
    IAaveLendingPoolV3 aaveLendingPool_,
    address reserveAsset_, // USDC for Strategy Manager v0
    address buybackWallet_,
    uint256 payoutDeductibleRate_, // in rays
    uint256 performanceFee_, // in rays
    address wstETH_,
    address amphrETH_,
    address amphrLRT_
  ) Ownable(msg.sender) {
    liquidityManager = liquidityManager_;
    ecclesiaDao = ecclesiaDao_;
    aaveLendingPool = aaveLendingPool_;

    USDC = reserveAsset_;
    buybackWallet = buybackWallet_;

    // Amphor Restaked ETH & Amphor Symbiotic LRT
    wstETH = wstETH_;
    amphrETH = amphrETH_;
    amphrLRT = amphrLRT_;

    if (
      HUNDRED_PERCENT < payoutDeductibleRate_ ||
      HUNDRED_PERCENT < performanceFee_
    ) revert RateAboveMax();

    payoutDeductibleRate = payoutDeductibleRate_;
    strategyFeeRate = performanceFee_;

    aUSDC = aaveLendingPool.getReserveData(USDC).aTokenAddress;
  }

  //======== MODIFIERS ========//

  modifier onlyLiquidityManager() {
    if (msg.sender != address(liquidityManager))
      revert NotLiquidityManager();
    _;
  }

  modifier onlyWhiteListedLiquidityProviders() {
    if (
      // @dev using tx origin since the contract is called by the liquidity manager
      isWhitelistEnabled && !whiteListedLiquidityProviders[tx.origin]
    ) revert OnlyWhitelistCanDepositLiquidity();
    _;
  }

  modifier checkId(uint256 strategyId_) {
    if (2 < strategyId_) revert NotAValidStrategy();
    _;
  }

  //======== VIEWS ========//

  /**
   * @notice Returns true if a strategy compounds yield through the balance
   * @param strategyId_ The ID of the strategy
   * @return True if the strategy compounds
   */
  function itCompounds(
    uint256 strategyId_
  ) external pure checkId(strategyId_) returns (bool) {
    if (strategyId_ == 0) {
      // AAVE v3 USDC
      return true;
    } else {
      // Amphor Restaked ETH & Amphor Symbiotic LRT
      return false;
    }
  }

  /**
   * @notice Returns the current index between wrapped and underlying token
   * @return uint256 The current reward index in rays
   *
   * @dev A reward index of 1e27 means 1 wrapped = 1 underlying token
   */
  function getRewardIndex(
    uint256 strategyId_
  ) public view checkId(strategyId_) returns (uint256) {
    if (strategyId_ == 0) {
      // AAVE v3 USDC
      return aaveLendingPool.getReserveNormalizedIncome(USDC);
    } else {
      // Amphor Restaked ETH & Amphor Symbiotic LRT
      /// @dev The token compounds in value so amount is constant
      return RayMath.RAY;
    }
  }

  /**
   * @notice Returns the current reward rate for the strategy
   * @param strategyId_ The ID of the strategy
   * @return uint256 The reward rate in RAY
   *
   * @dev A reward rate of 1e28 means 100% APR
   * @dev This is used for UI display purposes only
   */
  function getRewardRate(
    uint256 strategyId_
  ) public view checkId(strategyId_) returns (uint256) {
    if (strategyId_ == 0) {
      // AAVE v3 USDC
      return
        aaveLendingPool.getReserveData(USDC).currentLiquidityRate;
    } else {
      // Amphor Restaked ETH & Amphor Symbiotic LRT
      /// @dev Retrieving the reward rate is not supported for Amphor strategies
      return 0;
    }
  }

  /**
   * @notice Computes rewards given their amount of underlying & start and end reward indexes
   * @param strategyId_ The ID of the strategy
   * @param amount_ The amount of underlying tokens
   * @param startRewardIndex_ The reward index at the time of deposit
   * @param endRewardIndex_ The reward index at the time of withdrawal
   * @return uint256 The amount of rewards in underlying tokens
   */
  function computeReward(
    uint256 strategyId_,
    uint256 amount_,
    uint256 startRewardIndex_,
    uint256 endRewardIndex_
  ) external pure checkId(strategyId_) returns (uint256) {
    if (strategyId_ == 0) {
      // AAVE v3 USDC
      return
        amount_.rayMul(endRewardIndex_).rayDiv(startRewardIndex_) -
        amount_;
    } else {
      // Amphor Restaked ETH & Amphor Symbiotic LRT
      /// @dev For value compounding strategies rewards are not computed
      return 0;
    }
  }

  /**
   * @notice Returns the underlying asset token address for a strategy
   * @param strategyId_ The ID of the strategy
   * @return The address of the underlying asset
   */
  function underlyingAsset(
    uint256 strategyId_
  ) public view checkId(strategyId_) returns (address) {
    if (strategyId_ == 0) {
      // AAVE v3 USDC
      return USDC;
    } else {
      // Amphor Restaked ETH & Amphor Symbiotic LRT
      /// @dev deposits/withdrawals in underlying are not supported for Amphor strategies
      return wstETH;
    }
  }

  /**
   * @notice Returns the wrapped asset token address for a strategy
   * @param strategyId_ The ID of the strategy
   * @return The address of the wrapped asset
   */
  function wrappedAsset(
    uint256 strategyId_
  ) public view checkId(strategyId_) returns (address) {
    if (strategyId_ == 0) {
      // AAVE v3 USDC
      return aUSDC;
    } else if (strategyId_ == 1) {
      // Amphor Restaked ETH
      return amphrETH;
    } else {
      // Amphor Symbiotic LRT
      return amphrLRT;
    }
  }

  /**
   * @notice Returns the underlying and wrapped asset token addresses for a strategy
   * @param strategyId_ The ID of the strategy
   * @return underlying The address of the underlying asset
   * @return wrapped The address of the wrapped asset
   */
  function assets(
    uint256 strategyId_
  )
    public
    view
    checkId(strategyId_)
    returns (address underlying, address wrapped)
  {
    underlying = underlyingAsset(strategyId_);
    wrapped = wrappedAsset(strategyId_);
  }

  /**
   * @notice Returns the amount of underlying tokens for a given amount of wrapped tokens
   * @param strategyId_ The ID of the strategy
   * @param amountWrapped_ The amount of wrapped tokens
   * @return The amount of underlying tokens
   */
  function wrappedToUnderlying(
    uint256 strategyId_,
    uint256 amountWrapped_
  ) public pure checkId(strategyId_) returns (uint256) {
    // For AAVE underlying === wrapped since aToken amounts autocompound
    // For Amphor underlying === wrapped since underlying is not supported
    return amountWrapped_;
  }

  /**
   * @notice Returns the amount of wrapped tokens for a given amount of underlying tokens
   * @param strategyId_ The ID of the strategy
   * @param amountUnderlying_ The amount of underlying tokens
   * @return The amount of wrapped tokens
   */
  function underlyingToWrapped(
    uint256 strategyId_,
    uint256 amountUnderlying_
  ) public pure checkId(strategyId_) returns (uint256) {
    // For AAVE underlying === wrapped since aToken amounts autocompound
    // For Amphor underlying === wrapped since underlying is not supported
    return amountUnderlying_;
  }

  //======== HELPERS ========//

  /**
   * @notice Withdraws DAO revenue from the strategy and accrues it in the DAO
   * @param token_ The address of the token
   * @param amount_ The amount of tokens to accrue
   */
  function _accrueToDao(address token_, uint256 amount_) private {
    // Since we remove 1 for rounding errors
    if (amount_ < 1) return;

    if (token_ == USDC) {
      // AAVE v3 USDC
      // Withdraw the revenue from the strategy to the DAO contract
      aaveLendingPool.withdraw(
        token_,
        amount_ - 1,
        address(ecclesiaDao)
      );
    } else {
      // Amphor Restaked ETH & Amphor Symbiotic LRT
      IERC20(token_).safeTransfer(address(ecclesiaDao), amount_ - 1);
    }

    // This will register the revenue in the DAO for distribution
    if (IsContract._isContract(address(ecclesiaDao))) {
      ecclesiaDao.accrueRevenue(token_, amount_, 0);
    }
  }

  //======== UNDERLYING I/O ========//

  /**
   * @notice Deposits underlying tokens into the strategy
   * @param strategyId_ The ID of the strategy
   * @param amountUnderlying_ The amount of underlying tokens to deposit
   */
  function depositToStrategy(
    uint256 strategyId_,
    uint256 amountUnderlying_
  )
    external
    checkId(strategyId_)
    onlyLiquidityManager
    onlyWhiteListedLiquidityProviders
  {
    if (strategyId_ == 0) {
      // AAVE v3 USDC
      IERC20(USDC).forceApprove(
        address(aaveLendingPool),
        amountUnderlying_
      );

      aaveLendingPool.deposit(
        USDC,
        amountUnderlying_,
        address(this),
        0
      );
    } else {
      // Amphor Restaked ETH & Amphor Symbiotic LRT
      revert UseOfUnderlyingAssetNotSupported();
    }
  }

  /**
   * @notice Withdraws underlying tokens from the strategy
   * @param strategyId_ The ID of the strategy
   * @param amountCapitalUnderlying_ The amount of capital underlying tokens to withdraw
   * @param amountRewardsUnderlying_ The amount of rewards underlying tokens to withdraw
   * @param account_ The address to send the underlying tokens to
   * @param yieldBonus_ The yield bonus in RAY
   */
  function withdrawFromStrategy(
    uint256 strategyId_,
    uint256 amountCapitalUnderlying_,
    uint256 amountRewardsUnderlying_,
    address account_,
    uint256 yieldBonus_
  )
    external
    checkId(strategyId_)
    onlyLiquidityManager
    onlyWhiteListedLiquidityProviders
  {
    if (strategyId_ == 0) {
      // AAVE v3 USDC
      uint256 amountToWithdraw = amountCapitalUnderlying_ +
        amountRewardsUnderlying_;

      // If the strategy has performance fees then compute the DAO share
      // @dev the bonus is subtracted from the performance fee
      if (
        strategyFeeRate != 0 &&
        amountRewardsUnderlying_ != 0 &&
        yieldBonus_ < strategyFeeRate
      ) {
        // @bw simplify by deduction bonus from fee rate ?
        uint256 daoShare = ((amountRewardsUnderlying_ *
          strategyFeeRate) -
          (amountRewardsUnderlying_ * yieldBonus_)) / HUNDRED_PERCENT;

        if (daoShare != 0) {
          // Deduct the daoShare from the amount to withdraw
          amountToWithdraw -= daoShare;
          _accrueToDao(USDC, daoShare);
        }
      }

      // Since we remove 1 for rounding errors
      if (amountToWithdraw <= 1) return;

      // @dev No need to approve aToken since they are burned in pool
      // @dev Remove 1 for rounding errors
      aaveLendingPool.withdraw(USDC, amountToWithdraw - 1, account_);
    } else {
      // Amphor Restaked ETH & Amphor Symbiotic LRT
      // @dev deposits/withdrawals in underlying are not supported for Amphor strategies
      return
        withdrawWrappedFromStrategy(
          strategyId_,
          underlyingToWrapped(strategyId_, amountCapitalUnderlying_),
          underlyingToWrapped(strategyId_, amountRewardsUnderlying_),
          account_,
          yieldBonus_
        );
    }
  }

  //======== WRAPPED I/O ========//

  /**
   * @notice Deposits wrapped tokens into the strategy
   * @param strategyId_ The ID of the strategy
   */
  function depositWrappedToStrategy(
    uint256 strategyId_
  )
    external
    checkId(strategyId_)
    onlyLiquidityManager
    onlyWhiteListedLiquidityProviders
  {
    // No need to deposit wrapped asset into strategy as they already compound by holding
  }

  /**
   * @notice Withdraws wrapped tokens from the strategy
   * @param strategyId_ The ID of the strategy
   * @param amountCapitalUnderlying_ The amount of capital underlying tokens to withdraw
   * @param amountRewardsUnderlying_ The amount of rewards underlying tokens to withdraw
   * @param account_ The address to send the underlying tokens to
   * @param yieldBonus_ The yield bonus in RAY
   */
  function withdrawWrappedFromStrategy(
    uint256 strategyId_,
    uint256 amountCapitalUnderlying_,
    uint256 amountRewardsUnderlying_,
    address account_,
    uint256 yieldBonus_
  )
    public
    checkId(strategyId_)
    onlyLiquidityManager
    onlyWhiteListedLiquidityProviders
  {
    /// @dev override underlying for Amphor strategies since it is not supported
    (address underlying, address wrapped) = strategyId_ == 0
      ? assets(strategyId_) // AAVE v3 USDC
      : (wrappedAsset(strategyId_), wrappedAsset(strategyId_)); // Amphor Restaked ETH & Amphor Symbiotic LRT

    // Compute amount of wrapped to send to account
    uint256 amountToWithdraw = underlyingToWrapped(
      strategyId_,
      amountCapitalUnderlying_
    ) + underlyingToWrapped(strategyId_, amountRewardsUnderlying_);

    // If the strategy has performance fees then compute the DAO share
    if (strategyFeeRate != 0 && amountRewardsUnderlying_ != 0) {
      uint256 daoShare = (amountRewardsUnderlying_ *
        (strategyFeeRate - yieldBonus_)) / RayMath.RAY;

      if (daoShare != 0) {
        // Deduct the daoShare from the amount to withdraw
        amountToWithdraw -= daoShare;
        _accrueToDao(underlying, daoShare);
      }
    }

    // Since we remove 1 for rounding errors
    if (amountToWithdraw <= 1) return;

    // @dev Remove 1 for rounding errors
    IERC20(wrapped).safeTransfer(account_, amountToWithdraw - 1);
  }

  //======== CLAIMS ========//

  /**
   * @notice Pay a valid claim compensation from the strategy
   * @param strategyId_ The ID of the strategy
   * @param amountUnderlying_ The amount of underlying tokens to payout
   * @param account_ The address to send the underlying tokens to
   */
  function payoutFromStrategy(
    uint256 strategyId_,
    uint256 amountUnderlying_,
    address account_
  ) external checkId(strategyId_) onlyLiquidityManager {
    uint256 deductible = (amountUnderlying_ * payoutDeductibleRate) /
      HUNDRED_PERCENT;

    // @dev No need to approve aToken since they are burned in pool
    // @dev Remove 1 for rounding errors
    uint256 amountToPayout = (amountUnderlying_ - deductible);

    if (strategyId_ == 0) {
      // AAVE v3 USDC

      // If there is a deductible, withdraw it from the pool to buy back & burn wallet
      if (0 < deductible)
        aaveLendingPool.withdraw(USDC, deductible, buybackWallet);

      // Since we remove 1 for rounding errors
      if (amountToPayout <= 1) return;

      aaveLendingPool.withdraw(USDC, amountToPayout - 1, account_);
    } else {
      // Amphor Restaked ETH & Amphor Symbiotic LRT
      address asset = wrappedAsset(strategyId_);

      // If there is a deductible, withdraw it from the pool to buy back & burn wallet
      if (0 < deductible)
        IERC20(asset).safeTransfer(buybackWallet, deductible);

      // Since we remove 1 for rounding errors
      if (amountToPayout <= 1) return;

      IERC20(asset).safeTransfer(account_, amountToPayout - 1);
    }
  }

  //======== ADMIN ========//

  /**
   * @notice Updates the addresses of the liquidity manager, ecclesiaDao, and buyback wallet
   * @param liquidityManager_ The address of the liquidity manager
   * @param ecclesiaDao_ The address of the ecclesiaDao
   * @param buybackWallet_ The address of the buyback & burn wallet
   */
  function updateAddressList(
    ILiquidityManager liquidityManager_,
    IEcclesiaDao ecclesiaDao_,
    address buybackWallet_
  ) external onlyOwner {
    if (address(liquidityManager_) != address(0))
      liquidityManager = liquidityManager_;
    if (address(ecclesiaDao_) != address(0))
      ecclesiaDao = ecclesiaDao_;
    if (address(buybackWallet_) != address(0))
      buybackWallet = buybackWallet_;
  }

  /**
   * @notice Updates the performance fee for the strategy
   * @param rate_ The new performance fee rate in RAY
   */
  function updateStrategyFeeRate(
    uint256 rate_ // in rays
  ) external onlyOwner {
    if (HUNDRED_PERCENT < rate_) revert RateAboveMax();
    strategyFeeRate = rate_;
  }

  /**
   * @notice Updates the deductible rate for compensations
   * @param rate_ The new deductible rate in RAY
   */
  function updatePayoutDeductibleRate(
    uint256 rate_ // in rays
  ) external onlyOwner {
    if (HUNDRED_PERCENT < rate_) revert RateAboveMax();
    payoutDeductibleRate = rate_;
  }

  /**
   * @notice Turns the whitelist on or off
   * @param isEnabled_ The new whitelist status
   */
  function setWhitelistStatus(bool isEnabled_) external onlyOwner {
    isWhitelistEnabled = isEnabled_;
  }

  /**
   * @notice Adds or removes addresses from the whitelist
   * @param address_ The addresses to add or remove
   * @param status_ The status of the addresses
   */
  function editWhitelistAddresses(
    address[] calldata address_,
    bool[] calldata status_
  ) external onlyOwner {
    uint256 length = address_.length;

    if (length != status_.length) revert ArgumentLengthMismatch();

    for (uint256 i; i < length; i++) {
      whiteListedLiquidityProviders[address_[i]] = status_[i];
    }
  }

  /**
   * @notice Claims extra AAVE rewards
   * @param rewardsController The address of the rewards controller
   * @param rewardableAssets The list of assets to check eligible distributions
   * @param amount The amount of rewards to claim
   * @param reward The address of the reward token
   */
  function claimAaveRewards(
    address rewardsController,
    address[] calldata rewardableAssets,
    uint256 amount,
    address reward
  ) external onlyOwner {
    IAaveRewardsController(rewardsController).claimRewards(
      rewardableAssets,
      amount,
      msg.sender,
      reward
    );
  }

  /**
   * @notice Claims all extra AAVE rewards
   * @param rewardsController The address of the rewards controller
   * @param rewardableAssets The list of assets to check eligible distributions
   */
  function claimAllAaveRewards(
    address rewardsController,
    address[] calldata rewardableAssets
  ) external onlyOwner {
    IAaveRewardsController(rewardsController).claimAllRewards(
      rewardableAssets,
      msg.sender
    );
  }

  /**
   * @notice Rescue and transfer tokens locked in this contract
   * @param token The address of the token
   * @param to The address of the recipient
   * @param amount The amount of token to transfer
   *
   * @dev This function is for emergency use only in case of a critical bug in
   * the v0 strategy manager
   */
  function rescueTokens(
    address token,
    address to,
    uint256 amount
  ) external onlyOwner {
    if (token == address(0)) {
      (bool success, ) = payable(to).call{ value: amount }("");
      if (!success) revert TransferCallFailed();
    } else {
      IERC20(token).safeTransfer(to, amount);
    }
  }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

File 3 of 21 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)

pragma solidity ^0.8.20;

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @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 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 ReentrancyGuard {
    // 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;

    uint256 private _status;

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

    constructor() {
        _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 {
        // 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 {
        // 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) {
        return _status == ENTERED;
    }
}

// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.25;

library DataTypes {
  struct ReserveData {
    //stores the reserve configuration
    ReserveConfigurationMap configuration;
    //the liquidity index. Expressed in ray
    uint128 liquidityIndex;
    //the current supply rate. Expressed in ray
    uint128 currentLiquidityRate;
    //variable borrow index. Expressed in ray
    uint128 variableBorrowIndex;
    //the current variable borrow rate. Expressed in ray
    uint128 currentVariableBorrowRate;
    //the current stable borrow rate. Expressed in ray
    uint128 currentStableBorrowRate;
    //timestamp of last update
    uint40 lastUpdateTimestamp;
    //the id of the reserve. Represents the position in the list of the active reserves
    uint16 id;
    //aToken address
    address aTokenAddress;
    //stableDebtToken address
    address stableDebtTokenAddress;
    //variableDebtToken address
    address variableDebtTokenAddress;
    //address of the interest rate strategy
    address interestRateStrategyAddress;
    //the current treasury balance, scaled
    uint128 accruedToTreasury;
    //the outstanding unbacked aTokens minted through the bridging feature
    uint128 unbacked;
    //the outstanding debt borrowed against this asset in isolation mode
    uint128 isolationModeTotalDebt;
  }

  struct ReserveConfigurationMap {
    //bit 0-15: LTV
    //bit 16-31: Liq. threshold
    //bit 32-47: Liq. bonus
    //bit 48-55: Decimals
    //bit 56: reserve is active
    //bit 57: reserve is frozen
    //bit 58: borrowing is enabled
    //bit 59: stable rate borrowing enabled
    //bit 60: asset is paused
    //bit 61: borrowing in isolation mode is enabled
    //bit 62: siloed borrowing enabled
    //bit 63: flashloaning enabled
    //bit 64-79: reserve factor
    //bit 80-115 borrow cap in whole tokens, borrowCap == 0 => no cap
    //bit 116-151 supply cap in whole tokens, supplyCap == 0 => no cap
    //bit 152-167 liquidation protocol fee
    //bit 168-175 eMode category
    //bit 176-211 unbacked mint cap in whole tokens, unbackedMintCap == 0 => minting disabled
    //bit 212-251 debt ceiling for isolation mode with (ReserveConfiguration::DEBT_CEILING_DECIMALS) decimals
    //bit 252-255 unused

    uint256 data;
  }
}

/**
 * @title IPool
 * @author Aave
 * @notice Defines the basic interface for an Aave Pool.
 */
interface IAaveLendingPoolV3 {
  /**
   * @notice Supplies an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
   * - E.g. User supplies 100 USDC and gets in return 100 aUSDC
   * @param asset The address of the underlying asset to supply
   * @param amount The amount to be supplied
   * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
   *   wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
   *   is a different wallet
   * @param referralCode Code used to register the integrator originating the operation, for potential rewards.
   *   0 if the action is executed directly by the user, without any middle-man
   */
  function supply(
    address asset,
    uint256 amount,
    address onBehalfOf,
    uint16 referralCode
  ) external;

  /**
   * @notice Supplies an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
   * - E.g. User supplies 100 USDC and gets in return 100 aUSDC
   * @dev Deprecated: Use the `supply` function instead
   * @param asset The address of the underlying asset to supply
   * @param amount The amount to be supplied
   * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
   *   wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
   *   is a different wallet
   * @param referralCode Code used to register the integrator originating the operation, for potential rewards.
   *   0 if the action is executed directly by the user, without any middle-man
   */
  function deposit(
    address asset,
    uint256 amount,
    address onBehalfOf,
    uint16 referralCode
  ) external;

  /**
   * @notice Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
   * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
   * @param asset The address of the underlying asset to withdraw
   * @param amount The underlying amount to be withdrawn
   *   - Send the value type(uint256).max in order to withdraw the whole aToken balance
   * @param to The address that will receive the underlying, same as msg.sender if the user
   *   wants to receive it on his own wallet, or a different address if the beneficiary is a
   *   different wallet
   * @return The final amount withdrawn
   */
  function withdraw(
    address asset,
    uint256 amount,
    address to
  ) external returns (uint256);

  /**
   * @notice Returns the normalized income of the reserve
   * @param asset The address of the underlying asset of the reserve
   * @return The reserve's normalized income
   */
  function getReserveNormalizedIncome(
    address asset
  ) external view returns (uint256);

  /**
   * @notice Returns the normalized variable debt per unit of asset
   * @dev WARNING: This function is intended to be used primarily by the protocol itself to get a
   * "dynamic" variable index based on time, current stored index and virtual rate at the current
   * moment (approx. a borrower would get if opening a position). This means that is always used in
   * combination with variable debt supply/balances.
   * If using this function externally, consider that is possible to have an increasing normalized
   * variable debt that is not equivalent to how the variable debt index would be updated in storage
   * (e.g. only updates with non-zero variable debt supply)
   * @param asset The address of the underlying asset of the reserve
   * @return The reserve normalized variable debt
   */
  function getReserveNormalizedVariableDebt(
    address asset
  ) external view returns (uint256);

  /**
   * @notice Returns the state and configuration of the reserve
   * @param asset The address of the underlying asset of the reserve
   * @return The state and configuration data of the reserve
   */
  function getReserveData(
    address asset
  ) external view returns (DataTypes.ReserveData memory);
}

// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.25;

/**
 * @title IRewardsController
 * @author Aave
 * @notice Defines the basic interface for a Rewards Controller.
 */
interface IAaveRewardsController {
  /**
   * @dev Claims reward for a user to the desired address, on all the assets of the pool, accumulating the pending rewards
   * @param assets List of assets to check eligible distributions before claiming rewards
   * @param amount The amount of rewards to claim
   * @param to The address that will be receiving the rewards
   * @param reward The address of the reward token
   * @return The amount of rewards claimed
   **/
  function claimRewards(
    address[] calldata assets,
    uint256 amount,
    address to,
    address reward
  ) external returns (uint256);

  /**
   * @dev Claims all rewards for a user to the desired address, on all the assets of the pool, accumulating the pending rewards
   * @param assets The list of assets to check eligible distributions before claiming rewards
   * @param to The address that will be receiving the rewards
   * @return rewardsList List of addresses of the reward tokens
   * @return claimedAmounts List that contains the claimed amount per reward, following same order as "rewardList"
   **/
  function claimAllRewards(
    address[] calldata assets,
    address to
  )
    external
    returns (
      address[] memory rewardsList,
      uint256[] memory claimedAmounts
    );
}

File 12 of 21 : IEcclesiaDao.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

interface IEcclesiaDao {
  function accrueRevenue(
    address _token,
    uint256 _amount,
    uint256 leverageFee_
  ) external;
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

// libraries
import { VirtualPool } from "../libs/VirtualPool.sol";
import { PoolMath } from "../libs/PoolMath.sol";
import { DataTypes } from "../libs/DataTypes.sol";
// interfaces
import { IEcclesiaDao } from "../interfaces/IEcclesiaDao.sol";
import { IStrategyManager } from "../interfaces/IStrategyManager.sol";

interface ILiquidityManager {
  // ======= STRUCTS ======= //

  struct CoverRead {
    uint256 coverId;
    uint64 poolId;
    uint256 coverAmount;
    bool isActive;
    uint256 premiumsLeft;
    uint256 dailyCost;
    uint256 premiumRate;
    uint32 lastTick; // Last last tick for which the cover is active
  }

  struct PositionRead {
    uint256 positionId;
    uint256 supplied;
    uint256 suppliedWrapped;
    uint256 commitWithdrawalTimestamp;
    uint256 strategyRewardIndex;
    uint64[] poolIds;
    uint256 newUserCapital;
    uint256 newUserCapitalWrapped;
    uint256[] coverRewards;
    uint256 strategyRewards;
  }

  struct Position {
    uint256 supplied;
    uint256 commitWithdrawalTimestamp;
    uint256 strategyRewardIndex;
    uint64[] poolIds;
  }

  struct PoolOverlap {
    uint64 poolId;
    uint256 amount;
  }

  struct VPoolRead {
    uint64 poolId;
    uint256 feeRate; // amount of fees on premiums in RAY
    uint256 leverageFeePerPool; // amount of fees per pool when using leverage
    IEcclesiaDao dao;
    IStrategyManager strategyManager;
    PoolMath.Formula formula;
    DataTypes.Slot0 slot0;
    uint256 strategyId;
    uint256 strategyRewardRate;
    address paymentAsset; // asset used to pay LP premiums
    address underlyingAsset; // asset required by the strategy
    address wrappedAsset; // tokenised strategy shares (ex: aTokens)
    bool isPaused;
    uint64[] overlappedPools;
    uint256 ongoingClaims;
    uint256[] compensationIds;
    uint256[] overlappedCapital;
    uint256 utilizationRate;
    uint256 totalLiquidity;
    uint256 availableLiquidity;
    uint256 strategyRewardIndex;
    uint256 lastOnchainUpdateTimestamp;
    uint256 premiumRate;
    // The amount of liquidity index that is in the current unfinished tick
    uint256 liquidityIndexLead;
  }

  function strategyManager() external view returns (IStrategyManager);

  function positions(
    uint256 tokenId_
  ) external view returns (Position memory);

  function coverToPool(
    uint256 tokenId_
  ) external view returns (uint64);

  function poolOverlaps(
    uint64 poolIdA_,
    uint64 poolIdB_
  ) external view returns (uint256);

  function coverInfo(
    uint256 tokenId_
  ) external view returns (CoverRead memory);

  function isCoverActive(
    uint256 tokenId
  ) external view returns (bool);

  function addClaimToPool(uint256 coverId_) external;

  function removeClaimFromPool(uint256 coverId_) external;

  function payoutClaim(uint256 poolId_, uint256 amount_) external;

  function takeInterestsWithYieldBonus(
    address account_,
    uint256 yieldBonus_,
    uint256[] calldata positionIds_
  ) external;
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

interface IStrategyManager {
  function getRewardIndex(
    uint256 strategyId
  ) external view returns (uint256);

  function getRewardRate(
    uint256 strategyId_
  ) external view returns (uint256);

  function underlyingAsset(
    uint256 strategyId_
  ) external view returns (address);

  function assets(
    uint256 strategyId_
  ) external view returns (address underlying, address wrapped);

  function wrappedToUnderlying(
    uint256 strategyId_,
    uint256 amountWrapped_
  ) external view returns (uint256);

  function depositToStrategy(
    uint256 strategyId_,
    uint256 amountUnderlying_
  ) external;

  function withdrawFromStrategy(
    uint256 strategyId_,
    uint256 amountCapitalUnderlying_,
    uint256 amountRewardsUnderlying_,
    address account_,
    uint256 /*yieldBonus_*/
  ) external;

  function depositWrappedToStrategy(uint256 strategyId_) external;

  function withdrawWrappedFromStrategy(
    uint256 strategyId_,
    uint256 amountCapitalUnderlying_,
    uint256 amountRewardsUnderlying_,
    address account_,
    uint256 /*yieldBonus_*/
  ) external;

  function payoutFromStrategy(
    uint256 strategyId_,
    uint256 amount,
    address claimant
  ) external;

  function computeReward(
    uint256 strategyId_,
    uint256 amount_,
    uint256 startRewardIndex_,
    uint256 endRewardIndex_
  ) external pure returns (uint256);

  function itCompounds(
    uint256 strategyId_
  ) external pure returns (bool);
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.25;

/// @title BitMath
/// @dev This library provides functionality for computing bit properties of an unsigned integer
library BitMath {
  /// @notice Returns the index of the least significant bit of the number,
  ///     where the least significant bit is at index 0 and the most significant bit is at index 255
  /// @dev The function satisfies the property:
  ///     (x & 2**leastSignificantBit(x)) != 0 and (x & (2**(leastSignificantBit(x)) - 1)) == 0)
  /// @param x the value for which to compute the least significant bit, must be greater than 0
  /// @return r the index of the least significant bit
  function leastSignificantBit(
    uint256 x
  ) internal pure returns (uint8 r) {
    require(x > 0);

    r = 255;
    if (x & type(uint128).max > 0) {
      r -= 128;
    } else {
      x >>= 128;
    }
    if (x & type(uint64).max > 0) {
      r -= 64;
    } else {
      x >>= 64;
    }
    if (x & type(uint32).max > 0) {
      r -= 32;
    } else {
      x >>= 32;
    }
    if (x & type(uint16).max > 0) {
      r -= 16;
    } else {
      x >>= 16;
    }
    if (x & type(uint8).max > 0) {
      r -= 8;
    } else {
      x >>= 8;
    }
    if (x & 0xf > 0) {
      r -= 4;
    } else {
      x >>= 4;
    }
    if (x & 0x3 > 0) {
      r -= 2;
    } else {
      x >>= 2;
    }
    if (x & 0x1 > 0) r -= 1;
  }
}

File 16 of 21 : DataTypes.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

// Libraries
import { RayMath } from "../libs/RayMath.sol";
import { TickBitmap } from "../libs/TickBitmap.sol";
import { PoolMath } from "../libs/PoolMath.sol";

// Interfaces
import { IEcclesiaDao } from "../interfaces/IEcclesiaDao.sol";
import { IStrategyManager } from "../interfaces/IStrategyManager.sol";

library DataTypes {
  struct Slot0 {
    // The last tick at which the pool's liquidity was updated
    uint32 tick;
    // The distance in seconds between ticks
    uint256 secondsPerTick;
    uint256 coveredCapital;
    /**
     * The last timestamp at which the current tick changed
     * This value indicates the start of the current stored tick
     */
    uint256 lastUpdateTimestamp;
    // The index tracking how much premiums have been consumed in favor of LP
    uint256 liquidityIndex;
  }

  struct LpInfo {
    uint256 beginLiquidityIndex;
    uint256 beginClaimIndex;
  }

  struct Cover {
    uint256 coverAmount;
    uint256 beginPremiumRate;
    /**
     * If cover is active: last last tick for which the cover is valid
     * If cover is expired: slot0 tick at which the cover was expired minus 1
     */
    uint32 lastTick;
  }

  struct Compensation {
    uint64 fromPoolId;
    // The ratio is the claimed amount/ total liquidity in the claim pool
    uint256 ratio;
    uint256 strategyRewardIndexBeforeClaim;
    mapping(uint64 _poolId => uint256 _amount) liquidityIndexBeforeClaim;
  }

  struct VPool {
    uint64 poolId;
    uint256 feeRate; // amount of fees on premiums in RAY
    uint256 leverageFeePerPool; // amount of fees per pool when using leverage
    IEcclesiaDao dao;
    IStrategyManager strategyManager;
    PoolMath.Formula formula;
    Slot0 slot0;
    uint256 strategyId;
    address paymentAsset; // asset used to pay LP premiums
    address underlyingAsset; // asset covered & used by the strategy
    address wrappedAsset; // tokenised strategy shares (ex: aTokens)
    bool isPaused;
    uint64[] overlappedPools;
    uint256 ongoingClaims;
    uint256[] compensationIds;
    /**
     * Maps poolId 0 -> poolId 1 -> overlapping capital
     *
     * @dev poolId 0 -> poolId 0 points to a pool's own liquidity
     * @dev liquidity overlap is always registered in the lower poolId
     */
    mapping(uint64 _poolId => uint256 _amount) overlaps;
    mapping(uint256 _positionId => LpInfo) lpInfos;
    // Maps an word position index to a bitmap of tick states (initialized or not)
    mapping(uint24 _wordPos => uint256 _bitmap) tickBitmap;
    // Maps a tick to the amount of cover that expires after that tick ends
    mapping(uint32 _tick => uint256 _coverAmount) ticks;
    // Maps a cover ID to the premium position of the cover
    mapping(uint256 _coverId => Cover) covers;
  }

  struct VPoolConstructorParams {
    uint64 poolId;
    IEcclesiaDao dao;
    IStrategyManager strategyManager;
    uint256 strategyId;
    address paymentAsset;
    uint256 feeRate; //Ray
    uint256 leverageFeePerPool; //Ray
    uint256 uOptimal; //Ray
    uint256 r0; //Ray
    uint256 rSlope1; //Ray
    uint256 rSlope2; //Ray
  }
}

File 17 of 21 : IsContract.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

library IsContract {
  /**
   * @notice Checks if address is a contract
   * @param address_ address to check
   * @return true if address is a contract
   *
   * @dev This function will return false if the address is:
   * - an externally-owned account
   * - a contract in construction
   * - an address where a contract will be created
   * - an address where a contract lived, but was destroyed
   * All this is considered acceptable for the intended use cases.
   *
   */
  function _isContract(
    address address_
  ) internal view returns (bool) {
    uint32 size;
    assembly {
      size := extcodesize(address_)
    }
    return (size > 0);
  }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

// Libraries
import { RayMath } from "../libs/RayMath.sol";

// @bw move back into vpool ?

library PoolMath {
  using RayMath for uint256;

  // ======= CONSTANTS ======= //

  uint256 constant YEAR = 365 days;
  uint256 constant RAY = RayMath.RAY;
  uint256 constant MAX_SECONDS_PER_TICK = 1 days;
  uint256 constant FEE_BASE = RAY;
  uint256 constant PERCENTAGE_BASE = 100;
  uint256 constant FULL_CAPACITY = PERCENTAGE_BASE * RAY;

  // ======= STRUCTURES ======= //

  struct Formula {
    uint256 uOptimal;
    uint256 r0;
    uint256 rSlope1;
    uint256 rSlope2;
  }

  // ======= FUNCTIONS ======= //

  /**
   * @notice Computes the premium rate of a cover,
   * the premium rate is the APR cost for a cover  ,
   * these are paid by cover buyer on their cover amount.
   *
   * @param formula The formula of the pool
   * @param utilizationRate_ The utilization rate of the pool
   *
   * @return The premium rate of the cover expressed in rays
   *
   * @dev Not pure since reads self but pure for all practical purposes
   */
  function getPremiumRate(
    Formula calldata formula,
    uint256 utilizationRate_
  ) public pure returns (uint256 /* premiumRate */) {
    if (utilizationRate_ < formula.uOptimal) {
      // Return base rate + proportional slope 1 rate
      return
        formula.r0 +
        formula.rSlope1.rayMul(
          utilizationRate_.rayDiv(formula.uOptimal)
        );
    } else if (utilizationRate_ < FULL_CAPACITY) {
      // Return base rate + slope 1 rate + proportional slope 2 rate
      return
        formula.r0 +
        formula.rSlope1 +
        formula.rSlope2.rayMul(
          (utilizationRate_ - formula.uOptimal).rayDiv(
            FULL_CAPACITY - formula.uOptimal
          )
        );
    } else {
      // Return base rate + slope 1 rate + slope 2 rate
      /**
       * @dev Premium rate is capped because in case of overusage the
       * liquidity providers are exposed to the same risk as 100% usage but
       * cover buyers are not fully covered.
       * This means cover buyers only pay for the effective cover they have.
       */
      return formula.r0 + formula.rSlope1 + formula.rSlope2;
    }
  }

  /**
   * @notice Computes the liquidity index for a given period
   * @param utilizationRate_ The utilization rate
   * @param premiumRate_ The premium rate
   * @param timeSeconds_ The time in seconds
   * @return The liquidity index to add for the given time
   */
  function computeLiquidityIndex(
    uint256 utilizationRate_,
    uint256 premiumRate_,
    uint256 timeSeconds_
  ) public pure returns (uint /* liquidityIndex */) {
    return
      utilizationRate_
        .rayMul(premiumRate_)
        .rayMul(timeSeconds_)
        .rayDiv(YEAR);
  }

  /**
   * @notice Computes the premiums or interests earned by a liquidity position
   * @param userCapital_ The amount of liquidity in the position
   * @param endLiquidityIndex_ The end liquidity index
   * @param startLiquidityIndex_ The start liquidity index
   */
  function getCoverRewards(
    uint256 userCapital_,
    uint256 startLiquidityIndex_,
    uint256 endLiquidityIndex_
  ) public pure returns (uint256) {
    return
      (userCapital_.rayMul(endLiquidityIndex_) -
        userCapital_.rayMul(startLiquidityIndex_)) / 10_000;
  }

  /**
   * @notice Computes the new daily cost of a cover,
   * the emmission rate is the daily cost of a cover  .
   *
   * @param oldDailyCost_ The daily cost of the cover before the change
   * @param oldPremiumRate_ The premium rate of the cover before the change
   * @param newPremiumRate_ The premium rate of the cover after the change
   *
   * @return The new daily cost of the cover expressed in tokens/day
   */
  function getDailyCost(
    uint256 oldDailyCost_,
    uint256 oldPremiumRate_,
    uint256 newPremiumRate_
  ) public pure returns (uint256) {
    return (oldDailyCost_ * newPremiumRate_) / oldPremiumRate_;
  }

  /**
   * @notice Computes the new seconds per tick of a pool,
   * the seconds per tick is the time between two ticks  .
   *
   * @param oldSecondsPerTick_ The seconds per tick before the change
   * @param oldPremiumRate_ The premium rate before the change
   * @param newPremiumRate_ The premium rate after the change
   *
   * @return The new seconds per tick of the pool
   */
  function secondsPerTick(
    uint256 oldSecondsPerTick_,
    uint256 oldPremiumRate_,
    uint256 newPremiumRate_
  ) public pure returns (uint256) {
    return
      oldSecondsPerTick_.rayMul(oldPremiumRate_).rayDiv(
        newPremiumRate_
      );
  }

  /**
   * @notice Computes the updated premium rate of the pool based on utilization.
   * @param formula The formula of the pool
   * @param secondsPerTick_ The seconds per tick of the pool
   * @param coveredCapital_ The amount of covered capital
   * @param totalLiquidity_ The total amount liquidity
   * @param newCoveredCapital_ The new amount of covered capital
   * @param newTotalLiquidity_ The new total amount liquidity
   *
   * @return newPremiumRate The updated premium rate of the pool
   * @return newSecondsPerTick The updated seconds per tick of the pool
   */
  function updatePoolMarket(
    Formula calldata formula,
    uint256 secondsPerTick_,
    uint256 totalLiquidity_,
    uint256 coveredCapital_,
    uint256 newTotalLiquidity_,
    uint256 newCoveredCapital_
  )
    public
    pure
    returns (
      uint256 newPremiumRate,
      uint256 newSecondsPerTick,
      uint256 newUtilizationRate
    )
  {
    uint256 previousPremiumRate = getPremiumRate(
      formula,
      _utilization(coveredCapital_, totalLiquidity_)
    );

    newUtilizationRate = _utilization(
      newCoveredCapital_,
      newTotalLiquidity_
    );

    newPremiumRate = getPremiumRate(formula, newUtilizationRate);

    newSecondsPerTick = secondsPerTick(
      secondsPerTick_,
      previousPremiumRate,
      newPremiumRate
    );
  }

  /**
   * @notice Computes the percentage of the pool's liquidity used for covers.
   * @param coveredCapital_ The amount of covered capital
   * @param liquidity_ The total amount liquidity
   *
   * @return rate The utilization rate of the pool
   *
   * @dev The utilization rate is capped at 100%.
   */
  function _utilization(
    uint256 coveredCapital_,
    uint256 liquidity_
  ) public pure returns (uint256 /* rate */) {
    // If the pool has no liquidity then the utilization rate is 0
    if (liquidity_ == 0) return 0;

    /**
     * @dev Utilization rate is capped at 100% because in case of overusage the
     * liquidity providers are exposed to the same risk as 100% usage but
     * cover buyers are not fully covered.
     * This means cover buyers only pay for the effective cover they have.
     */
    if (liquidity_ < coveredCapital_) return FULL_CAPACITY;

    // Get a base PERCENTAGE_BASE percentage
    return (coveredCapital_ * PERCENTAGE_BASE).rayDiv(liquidity_);
  }
}

// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.25;

/**
 * @title RayMath library
 * @author Aave
 * @dev Provides mul and div function for rays (decimals with 27 digits)
 **/

library RayMath {
  uint256 internal constant RAY = 1e27;
  uint256 internal constant halfRAY = RAY / 2;

  /**
   * @dev Multiplies two ray, rounding half up to the nearest ray
   * @param a Ray
   * @param b Ray
   * @return The result of a*b, in ray
   **/
  function rayMul(
    uint256 a,
    uint256 b
  ) internal pure returns (uint256) {
    return (a * b + halfRAY) / RAY;
  }

  /**
   * @dev Divides two ray, rounding half up to the nearest ray
   * @param a Ray
   * @param b Ray
   * @return The result of a/b, in ray
   **/
  function rayDiv(
    uint256 a,
    uint256 b
  ) internal pure returns (uint256) {
    return ((a * RAY) + (b / 2)) / b;
  }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

// Libraries
import { BitMath } from "./BitMath.sol";

/// @title Packed tick initialized state library
/// @notice Stores a packed mapping of tick index to its initialized state
/// @dev The mapping uses int24 for keys since ticks are represented as int32 and there are 256 (2^8) values per word.
library TickBitmap {
  /// @notice Computes the position in the mapping where the initialized bit for a tick lives
  /// @param tick The tick for which to compute the position
  /// @return wordPos The key in the mapping containing the word in which the bit is stored
  /// @return bitPos The bit position in the word where the flag is stored
  function position(
    uint32 tick
  ) private pure returns (uint24 wordPos, uint8 bitPos) {
    wordPos = uint24(tick >> 8);
    bitPos = uint8(uint32(tick % 256));
  }

  /// @notice Flips the initialized state for a given tick from false to true, or vice versa
  /// @param self The mapping in which to flip the tick
  /// @param tick The tick to flip
  function flipTick(
    mapping(uint24 => uint256) storage self,
    uint32 tick
  ) internal {
    (uint24 wordPos, uint8 bitPos) = position(tick);
    uint256 mask = 1 << bitPos;
    self[wordPos] ^= mask;
  }

  function isInitializedTick(
    mapping(uint24 => uint256) storage self,
    uint32 tick
  ) internal view returns (bool) {
    (uint24 wordPos, uint8 bitPos) = position(tick);
    uint256 mask = 1 << bitPos;
    return (self[wordPos] & mask) != 0;
  }

  /// @notice Returns the next initialized tick contained in the same word (or adjacent word)
  /// as the tick that is to the left (greater than) of the given tick
  /// @param self The mapping in which to compute the next initialized tick
  /// @param tick The starting tick
  function nextTick(
    mapping(uint24 => uint256) storage self,
    uint32 tick
  ) internal view returns (uint32 next, bool initialized) {
    // start from the word of the next tick, since the current tick state doesn't matter
    (uint24 wordPos, uint8 bitPos) = position(tick + 1);
    // all the 1s at or to the left of the bitPos
    uint256 mask = ~((1 << bitPos) - 1);
    uint256 masked = self[wordPos] & mask;

    // if there are no initialized ticks to the left of the current tick, return leftmost in the word
    initialized = masked != 0;
    // overflow/underflow is possible, but prevented externally by limiting tick
    next = initialized
      ? (tick +
        1 +
        uint32(BitMath.leastSignificantBit(masked) - bitPos))
      : (tick + 1 + uint32(type(uint8).max - bitPos));
  }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

// Libraries
import { RayMath } from "../libs/RayMath.sol";
import { TickBitmap } from "../libs/TickBitmap.sol";
import { PoolMath } from "../libs/PoolMath.sol";
import { DataTypes } from "../libs/DataTypes.sol";
import { IsContract } from "../libs/IsContract.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

// Interfaces
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IEcclesiaDao } from "../interfaces/IEcclesiaDao.sol";
import { IStrategyManager } from "../interfaces/IStrategyManager.sol";

// ======= ERRORS ======= //

error ZeroAddressAsset();
error DurationBelowOneTick();
error DurationOverflow();
error InsufficientCapacity();
error NotEnoughLiquidityForRemoval();

/**
 * @title Athena Virtual Pool
 * @author vblackwhale
 *
 * This library provides the logic to create and manage virtual pools.
 * The pool storage is located in the Liquidity Manager contract.
 *
 * Definitions:
 *
 * Ticks:
 * They are a serie equidistant points in time who's distance from one another is variable.
 * The initial tick spacing is its maximum possible value of 86400 seconds or 1 day.
 * The distance between ticks will reduce as usage grows and increase when usage falls.
 * The change in distance represents the speed at which cover premiums are spent given the pool's usage.
 *
 * Core pool metrics are computed with the following flow:
 * Utilization Rate (ray %) -> Premium Rate (ray %) -> Daily Cost (token/day)
 */
library VirtualPool {
  // ======= LIBS ======= //
  using VirtualPool for DataTypes.VPool;
  using RayMath for uint256;
  using SafeERC20 for IERC20;
  using TickBitmap for mapping(uint24 => uint256);

  // ======= CONSTANTS ======= //

  bytes32 private constant POOL_SLOT_HASH =
    keccak256("diamond.storage.VPool");
  bytes32 private constant COMPENSATION_SLOT_HASH =
    keccak256("diamond.storage.Compensation");

  uint256 constant YEAR = 365 days;
  uint256 constant RAY = RayMath.RAY;
  uint256 constant MAX_SECONDS_PER_TICK = 1 days;
  uint256 constant FEE_BASE = RAY;
  uint256 constant PERCENTAGE_BASE = 100;
  uint256 constant HUNDRED_PERCENT = FEE_BASE * PERCENTAGE_BASE;

  // ======= STRUCTS ======= //

  struct CoverInfo {
    uint256 premiumsLeft;
    uint256 dailyCost;
    uint256 premiumRate;
    bool isActive;
  }

  struct UpdatePositionParams {
    uint256 currentLiquidityIndex;
    uint256 tokenId;
    uint256 userCapital;
    uint256 strategyRewardIndex;
    uint256 latestStrategyRewardIndex;
    uint256 strategyId;
    bool itCompounds;
    uint256 endCompensationIndex;
    uint256 nbPools;
  }

  struct UpdatedPositionInfo {
    uint256 newUserCapital;
    uint256 coverRewards;
    uint256 strategyRewards;
    DataTypes.LpInfo newLpInfo;
  }

  // ======= STORAGE GETTERS ======= //

  /**
   * @notice Returns the storage slot position of a pool.
   *
   * @param poolId_ The pool ID
   *
   * @return pool The storage slot position of the pool
   */
  function getPool(
    uint64 poolId_
  ) internal pure returns (DataTypes.VPool storage pool) {
    // Generate a random storage storage slot position based on the pool ID
    bytes32 storagePosition = keccak256(
      abi.encodePacked(POOL_SLOT_HASH, poolId_)
    );

    // Set the position of our struct in contract storage
    assembly {
      pool.slot := storagePosition
    }
  }

  /**
   * @notice Returns the storage slot position of a compensation.
   *
   * @param compensationId_ The compensation ID
   *
   * @return comp The storage slot position of the compensation
   *
   * @dev Enables VirtualPool library to access child compensation storage
   */
  function getCompensation(
    uint256 compensationId_
  ) internal pure returns (DataTypes.Compensation storage comp) {
    // Generate a random storage storage slot position based on the compensation ID
    bytes32 storagePosition = keccak256(
      abi.encodePacked(COMPENSATION_SLOT_HASH, compensationId_)
    );

    // Set the position of our struct in contract storage
    assembly {
      comp.slot := storagePosition
    }
  }

  // ======= VIRTUAL STORAGE INIT ======= //

  /**
   * @notice Initializes a virtual pool & populates its storage
   *
   * @param params The pool's constructor parameters
   */
  function _vPoolConstructor(
    DataTypes.VPoolConstructorParams memory params
  ) internal {
    DataTypes.VPool storage pool = VirtualPool.getPool(params.poolId);

    (address underlyingAsset, address wrappedAsset) = params
      .strategyManager
      .assets(params.strategyId);

    if (
      underlyingAsset == address(0) ||
      params.paymentAsset == address(0)
    ) {
      revert ZeroAddressAsset();
    }

    pool.poolId = params.poolId;
    pool.dao = params.dao;
    pool.strategyManager = params.strategyManager;
    pool.paymentAsset = params.paymentAsset;
    pool.strategyId = params.strategyId;
    pool.underlyingAsset = underlyingAsset;
    pool.wrappedAsset = wrappedAsset;
    pool.feeRate = params.feeRate;
    pool.leverageFeePerPool = params.leverageFeePerPool;

    pool.formula = PoolMath.Formula({
      uOptimal: params.uOptimal,
      r0: params.r0,
      rSlope1: params.rSlope1,
      rSlope2: params.rSlope2
    });

    /// @dev the initial tick spacing is its maximum value 86400 seconds
    pool.slot0.secondsPerTick = MAX_SECONDS_PER_TICK;
    pool.slot0.lastUpdateTimestamp = block.timestamp;
    /// @dev initialize at 1 to enable expiring covers created a first tick
    pool.slot0.tick = 1;

    pool.overlappedPools.push(params.poolId);
  }

  // ================================= //
  // ======= LIQUIDITY METHODS ======= //
  // ================================= //

  /**
   * @notice Returns the total liquidity of the pool.
   *
   * @param poolId_ The pool ID
   */
  function totalLiquidity(
    uint64 poolId_
  ) public view returns (uint256) {
    return getPool(poolId_).overlaps[poolId_];
  }

  /**
   * @notice Returns the available liquidity of the pool.
   *
   * @param poolId_ The pool ID
   */
  function availableLiquidity(
    uint64 poolId_
  ) public view returns (uint256) {
    DataTypes.VPool storage self = getPool(poolId_);

    /// @dev Since payout can lead to available capital underflow, we return 0
    if (totalLiquidity(poolId_) <= self.slot0.coveredCapital)
      return 0;

    return totalLiquidity(poolId_) - self.slot0.coveredCapital;
  }

  /**
   * @notice Computes an updated slot0 & liquidity index up to a timestamp.
   * These changes are virtual an not reflected in storage in this function.
   *
   * @param poolId_ The pool ID
   * @param timestamp_ The timestamp to update the slot0 & liquidity index to
   *
   * @return slot0 The updated slot0
   */
  function _refreshSlot0(
    uint64 poolId_,
    uint256 timestamp_
  ) public view returns (DataTypes.Slot0 memory slot0) {
    DataTypes.VPool storage self = VirtualPool.getPool(poolId_);

    // Make copy in memory to allow for mutations
    slot0 = self.slot0;

    // The remaining time in seconds to run through to sync up to the timestamp
    uint256 remaining = timestamp_ - slot0.lastUpdateTimestamp;

    // If the remaining time is less than the tick spacing then return the slot0
    if (remaining < slot0.secondsPerTick) return slot0;

    uint256 utilization = PoolMath._utilization(
      slot0.coveredCapital,
      totalLiquidity(self.poolId)
    );
    uint256 premiumRate = PoolMath.getPremiumRate(
      self.formula,
      utilization
    );

    // Default to ignore remaining time in case we do not enter loop
    uint256 secondsSinceTickStart = remaining;
    uint256 secondsParsed;

    // @bw could opti here by searching for next initialized tick to compute the liquidity index with same premium & utilization in one go, parsing multiple 256 value bitmaps. This should exit when remaining < secondsToNextTickEnd before finishing with the partial tick operation.
    while (slot0.secondsPerTick <= remaining) {
      secondsSinceTickStart = 0;

      // Search for the next tick, either last in bitmap or next initialized
      (uint32 nextTick, bool isInitialized) = self
        .tickBitmap
        .nextTick(slot0.tick);

      uint256 secondsToNextTickEnd = slot0.secondsPerTick *
        (nextTick - slot0.tick);

      if (secondsToNextTickEnd <= remaining) {
        // Remove parsed tick size from remaining time to current timestamp
        remaining -= secondsToNextTickEnd;
        secondsParsed = secondsToNextTickEnd;

        slot0.liquidityIndex += PoolMath.computeLiquidityIndex(
          utilization,
          premiumRate,
          secondsParsed
        );

        // If the tick has covers then update pool metrics
        if (isInitialized) {
          (slot0, utilization, premiumRate) = self
            ._crossingInitializedTick(slot0, nextTick);
        }
        // Pool is now synched at the start of nextTick
        slot0.tick = nextTick;
      } else {
        /**
         * Time bewteen start of the new tick and the current timestamp
         * This is ignored since this is not enough for a full tick to be processed
         */
        secondsSinceTickStart = remaining % slot0.secondsPerTick;
        // Ignore interests of current uncompleted tick
        secondsParsed = remaining - secondsSinceTickStart;
        // Number of complete ticks that we can take into account
        slot0.tick += uint32(secondsParsed / slot0.secondsPerTick);
        // Exit loop after the liquidity index update
        remaining = 0;

        slot0.liquidityIndex += PoolMath.computeLiquidityIndex(
          utilization,
          premiumRate,
          secondsParsed
        );
      }
    }

    // Remove ignored duration so the update aligns with current tick start
    slot0.lastUpdateTimestamp = timestamp_ - secondsSinceTickStart;
  }

  /**
   * @notice Updates the pool's slot0 when the available liquidity changes.
   *
   * @param poolId_ The pool ID
   * @param liquidityToAdd_ The amount of liquidity to add
   * @param liquidityToRemove_ The amount of liquidity to remove
   * @param skipLimitCheck_ Whether to skip the available liquidity check
   *
   * @dev The skipLimitCheck_ is used for deposits & payouts
   */
  function _syncLiquidity(
    uint64 poolId_,
    uint256 liquidityToAdd_,
    uint256 liquidityToRemove_,
    bool skipLimitCheck_
  ) public {
    DataTypes.VPool storage self = VirtualPool.getPool(poolId_);

    uint256 liquidity = totalLiquidity(self.poolId);
    uint256 available = availableLiquidity(self.poolId);

    // Skip liquidity check for deposits & payouts
    if (!skipLimitCheck_)
      if (available + liquidityToAdd_ < liquidityToRemove_)
        revert NotEnoughLiquidityForRemoval();

    // uint256 totalCovered = self.slot0.coveredCapital;
    uint256 newTotalLiquidity = (liquidity + liquidityToAdd_) -
      liquidityToRemove_;

    (, self.slot0.secondsPerTick, ) = PoolMath.updatePoolMarket(
      self.formula,
      self.slot0.secondsPerTick,
      liquidity,
      self.slot0.coveredCapital,
      newTotalLiquidity,
      self.slot0.coveredCapital
    );
  }

  // =================================== //
  // ======= COVERS & LP METHODS ======= //
  // =================================== //

  // ======= LIQUIDITY POSITIONS ======= //

  /**
   * @notice Adds liquidity info to the pool and updates the pool's state.
   *
   * @param poolId_ The pool ID
   * @param tokenId_ The LP position token ID
   * @param amount_ The amount of liquidity to deposit
   */
  function _depositToPool(
    uint64 poolId_,
    uint256 tokenId_,
    uint256 amount_
  ) external {
    DataTypes.VPool storage self = VirtualPool.getPool(poolId_);

    // Skip liquidity check for deposits
    _syncLiquidity(poolId_, amount_, 0, true);

    // This sets the point from which the position earns rewards & is impacted by claims
    // also overwrites previous LpInfo after a withdrawal
    self.lpInfos[tokenId_] = DataTypes.LpInfo({
      beginLiquidityIndex: self.slot0.liquidityIndex,
      beginClaimIndex: self.compensationIds.length
    });
  }

  /**
   * @notice Pays the rewards and fees to the position owner and the DAO.
   *
   * @param poolId_ The pool ID
   * @param rewards_ The rewards to pay
   * @param account_ The account to pay the rewards to
   * @param yieldBonus_ The yield bonus to apply to the rewards
   * @param nbPools_ The number of pools in the position
   */
  function _payRewardsAndFees(
    uint64 poolId_,
    uint256 rewards_,
    address account_,
    uint256 yieldBonus_,
    uint256 nbPools_
  ) public {
    DataTypes.VPool storage self = VirtualPool.getPool(poolId_);

    if (0 < rewards_) {
      uint256 fees = (rewards_ * self.feeRate) / HUNDRED_PERCENT;
      uint256 yieldBonus = (rewards_ *
        (HUNDRED_PERCENT - yieldBonus_)) / HUNDRED_PERCENT;

      uint256 netFees = fees == 0 || fees < yieldBonus
        ? 0
        : fees - yieldBonus;

      uint256 leverageFee;
      if (1 < nbPools_) {
        // The risk fee is only applied when using leverage
        // @dev The leverage fee is per pool so it starts at 2 * leverageFeePerPool
        leverageFee =
          (rewards_ * (self.leverageFeePerPool * nbPools_)) /
          HUNDRED_PERCENT;
      } else if (account_ == address(self.dao)) {
        // Take profits for the DAO accumulate the net in the leverage risk wallet
        leverageFee = rewards_ - netFees;
      }

      uint256 totalFees = netFees + leverageFee;
      // With insane leverage then the user could have a total fee rate above 100%
      uint256 net = rewards_ < totalFees ? 0 : rewards_ - totalFees;

      // Pay position owner
      if (net != 0) {
        IERC20(self.paymentAsset).safeTransfer(account_, net);
      }

      // Pay treasury & leverage risk wallet
      if (totalFees != 0) {
        IERC20(self.paymentAsset).safeTransfer(
          address(self.dao),
          totalFees
        );

        // This will register the revenue in the DAO for distribution
        if (IsContract._isContract(address(self.dao))) {
          self.dao.accrueRevenue(
            self.paymentAsset,
            netFees,
            leverageFee
          );
        }
      }
    }
  }

  /// -------- TAKE INTERESTS -------- ///

  /**
   * @notice Takes the interests of a position and updates the pool's state.
   *
   * @param poolId_ The pool ID
   * @param tokenId_ The LP position token ID
   * @param account_ The account to pay the rewards to
   * @param supplied_ The amount of liquidity to take interest on
   * @param yieldBonus_ The yield bonus to apply to the rewards
   * @param poolIds_ The pool IDs of the position
   *
   * @return newUserCapital The user's capital after claims
   * @return coverRewards The rewards earned from cover premiums
   *
   * @dev Need to update user capital & payout strategy rewards upon calling this function
   */
  function _takePoolInterests(
    uint64 poolId_,
    uint256 tokenId_,
    address account_,
    uint256 supplied_,
    uint256 strategyRewardIndex_,
    uint256 latestStrategyRewardIndex_,
    uint256 yieldBonus_,
    uint64[] storage poolIds_
  )
    external
    returns (uint256 /*newUserCapital*/, uint256 /*coverRewards*/)
  {
    if (supplied_ == 0) return (0, 0);

    DataTypes.VPool storage self = VirtualPool.getPool(poolId_);

    // Get the updated position info
    UpdatedPositionInfo memory info = _getUpdatedPositionInfo(
      poolId_,
      poolIds_,
      UpdatePositionParams({
        currentLiquidityIndex: self.slot0.liquidityIndex,
        tokenId: tokenId_,
        userCapital: supplied_,
        strategyRewardIndex: strategyRewardIndex_,
        latestStrategyRewardIndex: latestStrategyRewardIndex_,
        strategyId: self.strategyId,
        itCompounds: self.strategyManager.itCompounds(
          self.strategyId
        ),
        endCompensationIndex: self.compensationIds.length,
        nbPools: poolIds_.length
      })
    );

    // Pay cover rewards and send fees to treasury
    _payRewardsAndFees(
      poolId_,
      info.coverRewards,
      account_,
      yieldBonus_,
      poolIds_.length
    );

    // Update lp info to reflect the new state of the position
    self.lpInfos[tokenId_] = info.newLpInfo;

    // Return the user's capital & strategy rewards for withdrawal
    return (info.newUserCapital, info.strategyRewards);
  }

  /// -------- WITHDRAW -------- ///

  /**
   * @notice Withdraws liquidity from the pool and updates the pool's state.
   *
   * @param poolId_ The pool ID
   * @param tokenId_ The LP position token ID
   * @param supplied_ The amount of liquidity to withdraw
   * @param poolIds_ The pool IDs of the position
   *
   * @return newUserCapital The user's capital after claims
   * @return strategyRewards The rewards earned by the strategy
   */
  function _withdrawLiquidity(
    uint64 poolId_,
    uint256 tokenId_,
    uint256 supplied_,
    uint256 amount_,
    uint256 strategyRewardIndex_,
    uint256 latestStrategyRewardIndex_,
    uint64[] storage poolIds_
  ) external returns (uint256, uint256) {
    DataTypes.VPool storage self = VirtualPool.getPool(poolId_);

    // Get the updated position info
    UpdatedPositionInfo memory info = _getUpdatedPositionInfo(
      poolId_,
      poolIds_,
      UpdatePositionParams({
        currentLiquidityIndex: self.slot0.liquidityIndex,
        tokenId: tokenId_,
        userCapital: supplied_,
        strategyRewardIndex: strategyRewardIndex_,
        latestStrategyRewardIndex: latestStrategyRewardIndex_,
        strategyId: self.strategyId,
        itCompounds: self.strategyManager.itCompounds(
          self.strategyId
        ),
        endCompensationIndex: self.compensationIds.length,
        nbPools: poolIds_.length
      })
    );

    // Pool rewards after commit are paid in favor of the DAO's leverage risk wallet
    _payRewardsAndFees(
      poolId_,
      info.coverRewards,
      address(self.dao),
      0, // No yield bonus for the DAO
      poolIds_.length
    );

    // Update lp info to reflect the new state of the position
    self.lpInfos[tokenId_] = info.newLpInfo;

    // Update liquidity index
    _syncLiquidity(poolId_, 0, amount_, false);

    // Return the user's capital & strategy rewards for withdrawal
    return (info.newUserCapital, info.strategyRewards);
  }

  // ======= COVERS ======= //

  /// -------- BUY -------- ///

  /**
   * @notice Registers a premium position for a cover,
   * it also initializes the last tick (expiration tick) of the cover is needed.
   *
   * @param self The pool
   * @param coverId_ The cover ID
   * @param beginPremiumRate_ The premium rate at the beginning of the cover
   * @param lastTick_ The last tick of the cover
   */
  function _addPremiumPosition(
    DataTypes.VPool storage self,
    uint256 coverId_,
    uint256 coverAmount_,
    uint256 beginPremiumRate_,
    uint32 lastTick_
  ) internal {
    self.ticks[lastTick_] += coverAmount_;

    self.covers[coverId_] = DataTypes.Cover({
      coverAmount: coverAmount_,
      beginPremiumRate: beginPremiumRate_,
      lastTick: lastTick_
    });

    /**
     * If the tick at which the cover expires is not initialized then initialize it
     * this indicates that the tick is not empty and has covers that expire
     */
    if (!self.tickBitmap.isInitializedTick(lastTick_)) {
      self.tickBitmap.flipTick(lastTick_);
    }
  }

  /**
   * @notice Registers a premium position of a cover and updates the pool's slot0.
   *
   * @param poolId_ The pool ID
   * @param coverId_ The cover ID
   * @param coverAmount_ The amount of cover to buy
   * @param premiums_ The amount of premiums deposited
   */
  function _registerCover(
    uint64 poolId_,
    uint256 coverId_,
    uint256 coverAmount_,
    uint256 premiums_
  ) external {
    DataTypes.VPool storage self = VirtualPool.getPool(poolId_);

    // @bw could compute amount of time lost to rounding and conseqentially the amount of premiums lost, then register them to be able to harvest them / redistrib them
    uint256 available = availableLiquidity(self.poolId);

    /**
     * Check if pool has enough liquidity, when updating a cover
     * we closed the previous cover at this point so check for total
     * */
    if (available < coverAmount_) revert InsufficientCapacity();

    uint256 liquidity = totalLiquidity(self.poolId);

    (uint256 newPremiumRate, uint256 newSecondsPerTick, ) = PoolMath
      .updatePoolMarket(
        self.formula,
        self.slot0.secondsPerTick,
        liquidity,
        self.slot0.coveredCapital,
        liquidity,
        self.slot0.coveredCapital + coverAmount_
      );

    uint256 durationInSeconds = (premiums_ * YEAR * PERCENTAGE_BASE)
      .rayDiv(newPremiumRate) / coverAmount_;

    if (durationInSeconds < newSecondsPerTick)
      revert DurationBelowOneTick();

    /**
     * @dev The user can loose up to almost 1 tick of cover due to the floored division
     * The user can also win up to almost 1 tick of cover if it is opened at the start of a tick
     */
    uint256 tickDuration = durationInSeconds / newSecondsPerTick;
    // Check for overflow in case the cover amount is very low
    if (type(uint32).max < tickDuration) revert DurationOverflow();

    uint32 lastTick = self.slot0.tick + uint32(tickDuration);

    self._addPremiumPosition(
      coverId_,
      coverAmount_,
      newPremiumRate,
      lastTick
    );

    self.slot0.coveredCapital += coverAmount_;
    self.slot0.secondsPerTick = newSecondsPerTick;
  }

  /// -------- CLOSE -------- ///

  /**
   * @notice Closes a cover and updates the pool's slot0.
   *
   * @param poolId_ The pool ID
   * @param coverId_ The cover ID
   */
  function _closeCover(uint64 poolId_, uint256 coverId_) external {
    DataTypes.VPool storage self = VirtualPool.getPool(poolId_);

    DataTypes.Cover memory cover = self.covers[coverId_];

    // Remove cover amount from the tick at which it expires
    uint256 coverAmount = cover.coverAmount;
    self.ticks[cover.lastTick] -= coverAmount;

    // If there is no more cover in the tick then flip it to uninitialized
    if (self.ticks[cover.lastTick] == 0) {
      self.tickBitmap.flipTick(cover.lastTick);
    }

    uint256 liquidity = totalLiquidity(self.poolId);

    (, self.slot0.secondsPerTick, ) = PoolMath.updatePoolMarket(
      self.formula,
      self.slot0.secondsPerTick,
      liquidity,
      self.slot0.coveredCapital,
      liquidity,
      self.slot0.coveredCapital - coverAmount
    );

    self.slot0.coveredCapital -= coverAmount;

    // @dev We remove 1 since the covers expire at the end of the tick
    self.covers[coverId_].lastTick = self.slot0.tick - 1;
  }

  // ======= INTERNAL POOL HELPERS ======= //

  /**
   * @notice Purges expired covers from the pool and updates the pool's slot0 up to the latest timestamp
   *
   * @param poolId_ The pool ID
   *
   * @dev function _purgeExpiredCoversUpTo
   */
  function _purgeExpiredCovers(uint64 poolId_) external {
    _purgeExpiredCoversUpTo(poolId_, block.timestamp);
  }

  /**
   * @notice Removes expired covers from the pool and updates the pool's slot0.
   * Required before any operation that requires the slot0 to be up to date.
   * This includes all position and cover operations.
   *
   * @param poolId_ The pool ID
   */
  function _purgeExpiredCoversUpTo(
    uint64 poolId_,
    uint256 timestamp_
  ) public {
    DataTypes.VPool storage self = VirtualPool.getPool(poolId_);
    self.slot0 = _refreshSlot0(poolId_, timestamp_);
  }

  // ======= VIEW HELPERS ======= //

  /**
   * @notice Checks if a cover is active or if it has expired or been closed
   * @dev The user is protected during lastTick but the cover cannot be updated
   *
   * @param poolId_ The pool ID
   * @param coverId_ The cover ID
   *
   * @return Whether the cover is active
   */
  function _isCoverActive(
    uint64 poolId_,
    uint256 coverId_
  ) external view returns (bool) {
    DataTypes.VPool storage self = VirtualPool.getPool(poolId_);

    return self.slot0.tick < self.covers[coverId_].lastTick;
  }

  /**
   * @notice Computes the cover and strategy rewards for an LP position.
   *
   * @param self The pool
   * @param info The updated position information
   * @param coverRewards The current rewards earned from cover premiums
   * @param strategyRewards The current rewards earned by the strategy
   * @param strategyId The strategy ID
   * @param itCompounds Whether the strategy compounds
   * @param endliquidityIndex The end liquidity index
   * @param startStrategyRewardIndex The start strategy reward index
   * @param endStrategyRewardIndex The end strategy reward index
   *
   * @return coverRewards The aggregated rewards earned from cover premiums
   * @return strategyRewards The aggregated rewards earned by the strategy
   */
  function computePositionRewards(
    DataTypes.VPool storage self,
    UpdatedPositionInfo memory info,
    uint256 coverRewards,
    uint256 strategyRewards,
    uint256 strategyId,
    bool itCompounds,
    uint256 endliquidityIndex,
    uint256 startStrategyRewardIndex,
    uint256 endStrategyRewardIndex
  )
    internal
    view
    returns (
      uint256 /* coverRewards */,
      uint256 /* strategyRewards */
    )
  {
    coverRewards += PoolMath.getCoverRewards(
      info.newUserCapital,
      info.newLpInfo.beginLiquidityIndex,
      endliquidityIndex
    );

    strategyRewards += self.strategyManager.computeReward(
      strategyId,
      // If strategy compounds then add to capital to compute next new rewards
      itCompounds
        ? info.newUserCapital + info.strategyRewards
        : info.newUserCapital,
      startStrategyRewardIndex,
      endStrategyRewardIndex
    );

    return (coverRewards, strategyRewards);
  }

  /**
   * @notice Computes the state changes of an LP position,
   * it aggregates the fees earned by the position and
   * computes the losses incurred by the claims in this pool.
   *
   * @param poolId_ The pool ID
   * @param poolIds_ The pool IDs of the position
   * @param params The update position parameters
   * - currentLiquidityIndex_ The current liquidity index
   * - tokenId_ The LP position token ID
   * - userCapital_ The user's capital
   * - strategyRewardIndex_ The strategy reward index
   * - latestStrategyRewardIndex_ The latest strategy reward index
   *
   * @return info Updated information about the position:
   * - newUserCapital The user's capital after claims
   * - coverRewards The rewards earned from cover premiums
   * - strategyRewards The rewards earned by the strategy
   * - newLpInfo The updated LpInfo of the position
   *
   * @dev Used for takeInterest, withdrawLiquidity and rewardsOf
   */
  function _getUpdatedPositionInfo(
    uint64 poolId_,
    uint64[] storage poolIds_,
    UpdatePositionParams memory params
  ) public view returns (UpdatedPositionInfo memory info) {
    DataTypes.VPool storage self = VirtualPool.getPool(poolId_);

    // Make copy of current LP info state for position
    info.newLpInfo = self.lpInfos[params.tokenId];
    info.newUserCapital = params.userCapital;

    // This index is not bubbled up in info because it is updated by the LiquidityManager
    // @dev Left unitilized because _processCompensationsForPosition will update it event with no compensations
    uint256 upToStrategyRewardIndex;

    (
      info,
      upToStrategyRewardIndex
    ) = _processCompensationsForPosition(poolId_, poolIds_, params);

    /**
     * Finally add the rewards from the last claim or update to the current block
     * & register latest reward & claim indexes
     */
    (info.coverRewards, info.strategyRewards) = self
      .computePositionRewards(
        info,
        info.coverRewards,
        info.strategyRewards,
        params.strategyId,
        params.itCompounds,
        params.currentLiquidityIndex,
        upToStrategyRewardIndex,
        params.latestStrategyRewardIndex
      );

    // Register up to where the position has been updated
    // @dev
    info.newLpInfo.beginLiquidityIndex = params.currentLiquidityIndex;
    info.newLpInfo.beginClaimIndex = params.endCompensationIndex;
  }

  /**
   * @notice Updates the capital in an LP position post compensation payouts.
   *
   * @param poolId_ The pool ID
   * @param poolIds_ The pool IDs of the position
   * @param params The update position parameters
   *
   * @return info Updated information about the position:
   * @return upToStrategyRewardIndex The latest strategy reward index
   */
  function _processCompensationsForPosition(
    uint64 poolId_,
    uint64[] storage poolIds_,
    UpdatePositionParams memory params
  )
    public
    view
    returns (
      UpdatedPositionInfo memory info,
      uint256 upToStrategyRewardIndex
    )
  {
    DataTypes.VPool storage self = VirtualPool.getPool(poolId_);

    info.newLpInfo = self.lpInfos[params.tokenId];
    info.newUserCapital = params.userCapital;

    // This index is not bubbled up in info because it is updated by the LiquidityManager
    upToStrategyRewardIndex = params.strategyRewardIndex;
    uint256 compensationIndex = info.newLpInfo.beginClaimIndex;

    /**
     * Parse each claim that may affect capital due to overlap in order to
     * compute rewards on post compensation capital
     */
    for (
      compensationIndex;
      compensationIndex < params.endCompensationIndex;
      compensationIndex++
    ) {
      DataTypes.Compensation storage comp = getCompensation(
        self.compensationIds[compensationIndex]
      );

      // For each pool in the position
      for (uint256 j; j < params.nbPools; j++) {
        // Skip if the comp is not incoming from one of the pools in the position
        if (poolIds_[j] != comp.fromPoolId) continue;

        // We want the liquidity index of this pool at the time of the claim
        uint256 liquidityIndexBeforeClaim = comp
          .liquidityIndexBeforeClaim[self.poolId];

        // Compute the rewards accumulated up to the claim
        (info.coverRewards, info.strategyRewards) = self
          .computePositionRewards(
            info,
            info.coverRewards,
            info.strategyRewards,
            params.strategyId,
            params.itCompounds,
            liquidityIndexBeforeClaim,
            upToStrategyRewardIndex,
            comp.strategyRewardIndexBeforeClaim
          );

        info
          .newLpInfo
          .beginLiquidityIndex = liquidityIndexBeforeClaim;
        // Reduce capital after the comp
        info.newUserCapital -= info.newUserCapital.rayMul(comp.ratio);

        // Register up to where the rewards have been accumulated
        upToStrategyRewardIndex = comp.strategyRewardIndexBeforeClaim;

        break;
      }
    }

    // Register up to where the position has been updated
    info.newLpInfo.beginClaimIndex = params.endCompensationIndex;
  }

  /**
   * @notice Computes the updated state of a cover.
   *
   * @param poolId_ The pool ID
   * @param coverId_ The cover ID
   *
   * @return info The cover data
   */
  function _computeRefreshedCoverInfo(
    uint64 poolId_,
    uint256 coverId_
  ) external view returns (CoverInfo memory info) {
    DataTypes.VPool storage self = VirtualPool.getPool(poolId_);

    return
      self._computeCoverInfo(
        coverId_,
        // For reads we sync the slot0 to the current timestamp to have latests data
        _refreshSlot0(poolId_, block.timestamp)
      );
  }

  /**
   * @notice Returns the current state of a cover.
   *
   * @param poolId_ The pool ID
   * @param coverId_ The cover ID
   *
   * @return info The cover data
   */
  function _computeCurrentCoverInfo(
    uint64 poolId_,
    uint256 coverId_
  ) external view returns (CoverInfo memory info) {
    DataTypes.VPool storage self = VirtualPool.getPool(poolId_);

    return self._computeCoverInfo(coverId_, self.slot0);
  }

  /**
   * @notice Computes the premium rate & daily cost of a cover,
   * this parses the pool's ticks to compute how much premiums are left and
   * what is the daily cost of keeping the cover openened.
   *
   * @param self The pool
   * @param coverId_ The cover ID
   *
   * @return info A struct containing the cover's premium rate & the cover's daily cost
   */
  function _computeCoverInfo(
    DataTypes.VPool storage self,
    uint256 coverId_,
    DataTypes.Slot0 memory slot0_
  ) internal view returns (CoverInfo memory info) {
    DataTypes.Cover storage cover = self.covers[coverId_];

    /**
     * If the cover's last tick is overtaken then it's expired & no premiums are left.
     * Return default 0 / false values in the returned struct.
     */
    if (cover.lastTick < slot0_.tick) return info;

    info.isActive = true;

    info.premiumRate = PoolMath.getPremiumRate(
      self.formula,
      PoolMath._utilization(
        slot0_.coveredCapital,
        totalLiquidity(self.poolId)
      )
    );

    /// @dev Skip division by premium rate PERCENTAGE_BASE for precision
    uint256 beginDailyCost = cover
      .coverAmount
      .rayMul(cover.beginPremiumRate)
      .rayDiv(365);
    info.dailyCost = PoolMath.getDailyCost(
      beginDailyCost,
      cover.beginPremiumRate,
      info.premiumRate
    );

    uint256 nbTicksLeft = cover.lastTick - slot0_.tick;
    // Duration in seconds between currentTick & minNextTick
    uint256 duration = nbTicksLeft * slot0_.secondsPerTick;

    /// @dev Unscale amount by PERCENTAGE_BASE & RAY
    info.premiumsLeft =
      (duration * info.dailyCost) /
      (1 days * PERCENTAGE_BASE * RAY);
    /// @dev Unscale amount by PERCENTAGE_BASE & RAY
    info.dailyCost = info.dailyCost / (PERCENTAGE_BASE * RAY);
  }

  /**
   * @notice Mutates a slot0 to reflect states changes upon crossing an initialized tick.
   * The covers crossed tick are expired and the pool's liquidity is updated.
   *
   * @dev It must be mutative so it can be used by read & write fns.
   *
   * @param self The pool
   * @param slot0_ The slot0 to mutate
   * @param tick_ The tick to cross
   *
   * @return The mutated slot0
   */
  function _crossingInitializedTick(
    DataTypes.VPool storage self,
    DataTypes.Slot0 memory slot0_,
    uint32 tick_
  )
    internal
    view
    returns (
      DataTypes.Slot0 memory /* slot0_ */,
      uint256 utilization,
      uint256 premiumRate
    )
  {
    uint256 liquidity = totalLiquidity(self.poolId);
    // Remove expired cover amount from the pool's covered capital
    uint256 newCoveredCapital = slot0_.coveredCapital -
      self.ticks[tick_];

    (premiumRate, slot0_.secondsPerTick, utilization) = PoolMath
      .updatePoolMarket(
        self.formula,
        self.slot0.secondsPerTick,
        liquidity,
        self.slot0.coveredCapital,
        liquidity,
        newCoveredCapital
      );

    // Remove expired cover amount from the pool's covered capital
    slot0_.coveredCapital = newCoveredCapital;

    return (slot0_, utilization, premiumRate);
  }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 1
  },
  "evmVersion": "paris",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"contract ILiquidityManager","name":"liquidityManager_","type":"address"},{"internalType":"contract IEcclesiaDao","name":"ecclesiaDao_","type":"address"},{"internalType":"contract IAaveLendingPoolV3","name":"aaveLendingPool_","type":"address"},{"internalType":"address","name":"reserveAsset_","type":"address"},{"internalType":"address","name":"buybackWallet_","type":"address"},{"internalType":"uint256","name":"payoutDeductibleRate_","type":"uint256"},{"internalType":"uint256","name":"performanceFee_","type":"uint256"},{"internalType":"address","name":"wstETH_","type":"address"},{"internalType":"address","name":"amphrETH_","type":"address"},{"internalType":"address","name":"amphrLRT_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"ArgumentLengthMismatch","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"NotAValidStrategy","type":"error"},{"inputs":[],"name":"NotLiquidityManager","type":"error"},{"inputs":[],"name":"OnlyWhitelistCanDepositLiquidity","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"RateAboveMax","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"TransferCallFailed","type":"error"},{"inputs":[],"name":"UseOfUnderlyingAssetNotSupported","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"USDC","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"aUSDC","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"aaveLendingPool","outputs":[{"internalType":"contract IAaveLendingPoolV3","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"amphrETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"amphrLRT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"strategyId_","type":"uint256"}],"name":"assets","outputs":[{"internalType":"address","name":"underlying","type":"address"},{"internalType":"address","name":"wrapped","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"buybackWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"rewardsController","type":"address"},{"internalType":"address[]","name":"rewardableAssets","type":"address[]"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"reward","type":"address"}],"name":"claimAaveRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"rewardsController","type":"address"},{"internalType":"address[]","name":"rewardableAssets","type":"address[]"}],"name":"claimAllAaveRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"strategyId_","type":"uint256"},{"internalType":"uint256","name":"amount_","type":"uint256"},{"internalType":"uint256","name":"startRewardIndex_","type":"uint256"},{"internalType":"uint256","name":"endRewardIndex_","type":"uint256"}],"name":"computeReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"strategyId_","type":"uint256"},{"internalType":"uint256","name":"amountUnderlying_","type":"uint256"}],"name":"depositToStrategy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"strategyId_","type":"uint256"}],"name":"depositWrappedToStrategy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"ecclesiaDao","outputs":[{"internalType":"contract IEcclesiaDao","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"address_","type":"address[]"},{"internalType":"bool[]","name":"status_","type":"bool[]"}],"name":"editWhitelistAddresses","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"strategyId_","type":"uint256"}],"name":"getRewardIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"strategyId_","type":"uint256"}],"name":"getRewardRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isWhitelistEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"strategyId_","type":"uint256"}],"name":"itCompounds","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"liquidityManager","outputs":[{"internalType":"contract ILiquidityManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"payoutDeductibleRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"strategyId_","type":"uint256"},{"internalType":"uint256","name":"amountUnderlying_","type":"uint256"},{"internalType":"address","name":"account_","type":"address"}],"name":"payoutFromStrategy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"rescueTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"isEnabled_","type":"bool"}],"name":"setWhitelistStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"strategyFeeRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"strategyId_","type":"uint256"}],"name":"underlyingAsset","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"strategyId_","type":"uint256"},{"internalType":"uint256","name":"amountUnderlying_","type":"uint256"}],"name":"underlyingToWrapped","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"contract ILiquidityManager","name":"liquidityManager_","type":"address"},{"internalType":"contract IEcclesiaDao","name":"ecclesiaDao_","type":"address"},{"internalType":"address","name":"buybackWallet_","type":"address"}],"name":"updateAddressList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"rate_","type":"uint256"}],"name":"updatePayoutDeductibleRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"rate_","type":"uint256"}],"name":"updateStrategyFeeRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account_","type":"address"}],"name":"whiteListedLiquidityProviders","outputs":[{"internalType":"bool","name":"isWhiteListed_","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"strategyId_","type":"uint256"},{"internalType":"uint256","name":"amountCapitalUnderlying_","type":"uint256"},{"internalType":"uint256","name":"amountRewardsUnderlying_","type":"uint256"},{"internalType":"address","name":"account_","type":"address"},{"internalType":"uint256","name":"yieldBonus_","type":"uint256"}],"name":"withdrawFromStrategy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"strategyId_","type":"uint256"},{"internalType":"uint256","name":"amountCapitalUnderlying_","type":"uint256"},{"internalType":"uint256","name":"amountRewardsUnderlying_","type":"uint256"},{"internalType":"address","name":"account_","type":"address"},{"internalType":"uint256","name":"yieldBonus_","type":"uint256"}],"name":"withdrawWrappedFromStrategy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"strategyId_","type":"uint256"}],"name":"wrappedAsset","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"strategyId_","type":"uint256"},{"internalType":"uint256","name":"amountWrapped_","type":"uint256"}],"name":"wrappedToUnderlying","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"wstETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]

608060405234801561001057600080fd5b506040516126ef3803806126ef83398101604081905261002f9161025e565b338061005a576000604051631e4fbdf760e01b81526004016100519190610324565b60405180910390fd5b610063816101e6565b50600180546001600160a01b03199081166001600160a01b038d8116919091179092556002805482168c84161790556006805482168b84161790556007805482168a8416179055600380548216898416179055600980548216868416179055600a80548216858416179055600b8054909116918316919091179055846100f66b033b2e3c9fd0803ce80000006064610338565b10806101175750836101156b033b2e3c9fd0803ce80000006064610338565b105b1561013557604051633aaeda2b60e01b815260040160405180910390fd5b600485815560058590556006546007546040516335ea6a7560e01b81526001600160a01b03928316936335ea6a759361017093169101610324565b6101e060405180830381865afa15801561018e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101b29190610427565b6101000151600880546001600160a01b0319166001600160a01b039092169190911790555061054a98505050505050505050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b038116811461024b57600080fd5b50565b805161025981610236565b919050565b6000806000806000806000806000806101408b8d03121561027e57600080fd5b8a5161028981610236565b60208c0151909a5061029a81610236565b60408c01519099506102ab81610236565b60608c01519098506102bc81610236565b60808c01519097506102cd81610236565b8096505060a08b0151945060c08b0151935060e08b01516102ed81610236565b6101008c01519093506102ff81610236565b6101208c015190925061031181610236565b809150509295989b9194979a5092959850565b6001600160a01b0391909116815260200190565b808202811582820484141761035d57634e487b7160e01b600052601160045260246000fd5b92915050565b6040516101e081016001600160401b038111828210171561039457634e487b7160e01b600052604160045260246000fd5b60405290565b6000602082840312156103ac57600080fd5b604051602081016001600160401b03811182821017156103dc57634e487b7160e01b600052604160045260246000fd5b6040529151825250919050565b80516001600160801b038116811461025957600080fd5b805164ffffffffff8116811461025957600080fd5b805161ffff8116811461025957600080fd5b60006101e0828403121561043a57600080fd5b610442610363565b61044c848461039a565b815261045a602084016103e9565b602082015261046b604084016103e9565b604082015261047c606084016103e9565b606082015261048d608084016103e9565b608082015261049e60a084016103e9565b60a08201526104af60c08401610400565b60c08201526104c060e08401610415565b60e08201526101006104d381850161024e565b908201526101206104e584820161024e565b908201526101406104f784820161024e565b9082015261016061050984820161024e565b9082015261018061051b8482016103e9565b908201526101a061052d8482016103e9565b908201526101c061053f8482016103e9565b908201529392505050565b612196806105596000396000f3fe608060405234801561001057600080fd5b50600436106101c25760003560e01c80630827b460146101c7578063184d69ab146101ed5780631f5f33291461021157806324e483c21461022457806324f607d41461023957806325f7911f1461024c578063338274381461025f5780634904130b1461027f5780634a999118146102925780634aa07e64146102a55780634e402e57146102b8578063559fda33146102cb57806358d4ee9f146102de578063715018a6146102f15780637c03c8c9146102f95780637f361a121461030257806387c076301461031557806389a30271146103285780638da5cb5b1461033b578063996be521146103435780639cea72e214610356578063a65135621461035f578063ac84725114610372578063b0656c3914610395578063bc39f66f146103a8578063c628148a146103bb578063cab75a65146103ce578063caf901c1146103e1578063cea9d26f146103f4578063cf35bdd014610407578063de1e9d4d146101c7578063deab8aea1461043a578063e1e891201461044d578063e6e58b5f14610460578063e6fcd8bd14610473578063e72724db14610486578063e9d337b814610499578063f2fde38b146104ac575b600080fd5b6101da6101d53660046118af565b6104bf565b6040519081526020015b60405180910390f35b600b5461020190600160a01b900460ff1681565b60405190151581526020016101e4565b6101da61021f3660046118d1565b6104ec565b61023761023236600461194a565b6105aa565b005b6102016102473660046118d1565b610631565b6101da61025a3660046119bd565b610670565b600154610272906001600160a01b031681565b6040516101e491906119ef565b61023761028d3660046118d1565b6106d1565b6102376102a0366004611a11565b610714565b600954610272906001600160a01b031681565b600254610272906001600160a01b031681565b600854610272906001600160a01b031681565b600a54610272906001600160a01b031681565b61023761073a565b6101da60045481565b610237610310366004611a2e565b61074e565b610237610323366004611a79565b6107dc565b600754610272906001600160a01b031681565b6102726109fc565b6102376103513660046118d1565b610a0b565b6101da60055481565b6101da61036d3660046118d1565b610a4e565b610201610380366004611aa7565b600c6020526000908152604090205460ff1681565b6102376103a33660046118af565b610b05565b6102376103b6366004611ac4565b610c49565b6102376103c9366004611b18565b610ccf565b6102376103dc3660046118d1565b610d85565b6102726103ef3660046118d1565b610e1e565b610237610402366004611b83565b610e71565b61041a6104153660046118d1565b610f0f565b604080516001600160a01b039384168152929091166020830152016101e4565b600354610272906001600160a01b031681565b61023761045b366004611bc4565b610f51565b61023761046e366004611bc4565b61114f565b600b54610272906001600160a01b031681565b6102726104943660046118d1565b6112d5565b600654610272906001600160a01b031681565b6102376104ba366004611aa7565b611343565b60008280600210156104e45760405163544420af60e01b815260040160405180910390fd5b509092915050565b60008180600210156105115760405163544420af60e01b815260040160405180910390fd5b826000036105955760065460075460405163d15e005360e01b81526001600160a01b039283169263d15e00539261054d929116906004016119ef565b602060405180830381865afa15801561056a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061058e9190611c0d565b91506105a4565b676765c793fa10079d601b1b91505b50919050565b6105b261138a565b6040516308d8c03760e21b81526001600160a01b0386169063236300dc906105e69087908790879033908890600401611c6f565b6020604051808303816000875af1158015610605573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106299190611c0d565b505050505050565b60008180600210156106565760405163544420af60e01b815260040160405180910390fd5b8260000361066757600191506105a4565b600091506105a4565b60008480600210156106955760405163544420af60e01b815260040160405180910390fd5b856000036106c357846106b2856106ac83876113bc565b906113fc565b6106bc9190611cc0565b91506106c8565b600091505b50949350505050565b6106d961138a565b806106f0676765c793fa10079d601b1b6064611cd3565b101561070f57604051633aaeda2b60e01b815260040160405180910390fd5b600455565b61071c61138a565b600b8054911515600160a01b0260ff60a01b19909216919091179055565b61074261138a565b61074c600061141f565b565b61075661138a565b6001600160a01b0383161561078157600180546001600160a01b0319166001600160a01b0385161790555b6001600160a01b038216156107ac57600280546001600160a01b0319166001600160a01b0384161790555b6001600160a01b038116156107d757600380546001600160a01b0319166001600160a01b0383161790555b505050565b8280600210156107ff5760405163544420af60e01b815260040160405180910390fd5b6001546001600160a01b0316331461082a5760405163bf4a682160e01b815260040160405180910390fd5b6000610842676765c793fa10079d601b1b6064611cd3565b60045461084f9086611cd3565b6108599190611cea565b905060006108678286611cc0565b9050856000036109955781156108fa57600654600754600354604051631a4ca37b60e21b81526001600160a01b03938416936369328dec936108b59390821692889290911690600401611d0c565b6020604051808303816000875af11580156108d4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108f89190611c0d565b505b600181116109095750506109f6565b6006546007546001600160a01b03918216916369328dec911661092d600185611cc0565b876040518463ffffffff1660e01b815260040161094c93929190611d0c565b6020604051808303816000875af115801561096b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061098f9190611c0d565b50610629565b60006109a0876112d5565b905082156109c2576003546109c2906001600160a01b0383811691168561146f565b600182116109d2575050506109f6565b6109f2856109e1600185611cc0565b6001600160a01b038416919061146f565b5050505b50505050565b6000546001600160a01b031690565b610a1361138a565b80610a2a676765c793fa10079d601b1b6064611cd3565b1015610a4957604051633aaeda2b60e01b815260040160405180910390fd5b600555565b6000818060021015610a735760405163544420af60e01b815260040160405180910390fd5b82600003610667576006546007546040516335ea6a7560e01b81526001600160a01b03928316926335ea6a7592610aaf929116906004016119ef565b6101e060405180830381865afa158015610acd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610af19190611e2d565b604001516001600160801b031691506105a4565b818060021015610b285760405163544420af60e01b815260040160405180910390fd5b6001546001600160a01b03163314610b535760405163bf4a682160e01b815260040160405180910390fd5b600b54600160a01b900460ff168015610b7c5750326000908152600c602052604090205460ff16155b15610b9a5760405163b709dc6560e01b815260040160405180910390fd5b82600003610c3057600654600754610bbf916001600160a01b039182169116846114c7565b60065460075460405163e8eda9df60e01b81526001600160a01b039182166004820152602481018590523060448201526000606482015291169063e8eda9df90608401600060405180830381600087803b158015610c1c57600080fd5b505af11580156109f2573d6000803e3d6000fd5b604051634b30351160e11b815260040160405180910390fd5b610c5161138a565b60405163bb492bf560e01b81526001600160a01b0384169063bb492bf590610c8190859085903390600401611f50565b6000604051808303816000875af1158015610ca0573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610cc8919081019061200e565b5050505050565b610cd761138a565b82818114610cf857604051634b87fa4960e11b815260040160405180910390fd5b60005b8181101561062957838382818110610d1557610d156120d2565b9050602002016020810190610d2a9190611a11565b600c6000888885818110610d4057610d406120d2565b9050602002016020810190610d559190611aa7565b6001600160a01b031681526020810191909152604001600020805460ff1916911515919091179055600101610cfb565b808060021015610da85760405163544420af60e01b815260040160405180910390fd5b6001546001600160a01b03163314610dd35760405163bf4a682160e01b815260040160405180910390fd5b600b54600160a01b900460ff168015610dfc5750326000908152600c602052604090205460ff16155b15610e1a5760405163b709dc6560e01b815260040160405180910390fd5b5050565b6000818060021015610e435760405163544420af60e01b815260040160405180910390fd5b82600003610e5e576007546001600160a01b031691506105a4565b6009546001600160a01b031691506105a4565b610e7961138a565b6001600160a01b038316610efb576000826001600160a01b03168260405160006040518083038185875af1925050503d8060008114610ed4576040519150601f19603f3d011682016040523d82523d6000602084013e610ed9565b606091505b50509050806109f657604051630aa5c45f60e11b815260040160405180910390fd5b6107d76001600160a01b038416838361146f565b600080828060021015610f355760405163544420af60e01b815260040160405180910390fd5b610f3e84610e1e565b9250610f49846112d5565b915050915091565b848060021015610f745760405163544420af60e01b815260040160405180910390fd5b6001546001600160a01b03163314610f9f5760405163bf4a682160e01b815260040160405180910390fd5b600b54600160a01b900460ff168015610fc85750326000908152600c602052604090205460ff16155b15610fe65760405163b709dc6560e01b815260040160405180910390fd5b8560000361112b576000610ffa85876120e8565b905060055460001415801561100e57508415155b801561101b575060055483105b15611090576000611038676765c793fa10079d601b1b6064611cd3565b6110428588611cd3565b60055461104f9089611cd3565b6110599190611cc0565b6110639190611cea565b9050801561108e576110758183611cc0565b60075490925061108e906001600160a01b031682611559565b505b6001811161109e5750610629565b6006546007546001600160a01b03918216916369328dec91166110c2600185611cc0565b876040518463ffffffff1660e01b81526004016110e193929190611d0c565b6020604051808303816000875af1158015611100573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111249190611c0d565b5050610629565b61114a8661113988886104bf565b61114389886104bf565b868661114f565b610629565b8480600210156111725760405163544420af60e01b815260040160405180910390fd5b6001546001600160a01b0316331461119d5760405163bf4a682160e01b815260040160405180910390fd5b600b54600160a01b900460ff1680156111c65750326000908152600c602052604090205460ff16155b156111e45760405163b709dc6560e01b815260040160405180910390fd5b6000808715611204576111f6886112d5565b6111ff896112d5565b61120d565b61120d88610f0f565b91509150600061121d89886104bf565b6112278a8a6104bf565b61123191906120e8565b905060055460001415801561124557508615155b1561129a576000676765c793fa10079d601b1b866005546112669190611cc0565b611270908a611cd3565b61127a9190611cea565b905080156112985761128c8183611cc0565b91506112988482611559565b505b600181116112aa57505050610629565b6112ca866112b9600184611cc0565b6001600160a01b038516919061146f565b505050505050505050565b60008180600210156112fa5760405163544420af60e01b815260040160405180910390fd5b82600003611315576008546001600160a01b031691506105a4565b8260010361133057600a546001600160a01b031691506105a4565b600b546001600160a01b031691506105a4565b61134b61138a565b6001600160a01b03811661137e576000604051631e4fbdf760e01b815260040161137591906119ef565b60405180910390fd5b6113878161141f565b50565b336113936109fc565b6001600160a01b03161461074c573360405163118cdaa760e01b815260040161137591906119ef565b6000676765c793fa10079d601b1b6113d5600282611cea565b6113df8486611cd3565b6113e991906120e8565b6113f39190611cea565b90505b92915050565b60008161140a600282611cea565b6113df676765c793fa10079d601b1b86611cd3565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6107d783846001600160a01b031663a9059cbb85856040516024016114959291906120fb565b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050506116ad565b6000836001600160a01b031663095ea7b384846040516024016114eb9291906120fb565b604051602081830303815290604052915060e01b6020820180516001600160e01b03838183161783525050505090506115248482611707565b6109f65761154f84856001600160a01b031663095ea7b38660006040516024016114959291906120fb565b6109f684826116ad565b6001811015611566575050565b6007546001600160a01b0390811690831603611611576006546001600160a01b03166369328dec83611599600185611cc0565b6002546040516001600160e01b031960e086901b1681526115c89392916001600160a01b031690600401611d0c565b6020604051808303816000875af11580156115e7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061160b9190611c0d565b5061162c565b60025461162c906001600160a01b03166112b9600184611cc0565b6002546001600160a01b03163b63ffffffff1615610e1a57600254604051630768cd4560e51b81526001600160a01b03848116600483015260248201849052600060448301529091169063ed19a8a090606401600060405180830381600087803b15801561169957600080fd5b505af1158015610629573d6000803e3d6000fd5b60006116c26001600160a01b038416836117af565b905080516000141580156116e75750808060200190518101906116e59190612114565b155b156107d75782604051635274afe760e01b815260040161137591906119ef565b6000806000846001600160a01b0316846040516117249190612131565b6000604051808303816000865af19150503d8060008114611761576040519150601f19603f3d011682016040523d82523d6000602084013e611766565b606091505b50915091508180156117905750805115806117905750808060200190518101906117909190612114565b80156117a657506000856001600160a01b03163b115b95945050505050565b60606113f38383600084600080856001600160a01b031684866040516117d59190612131565b60006040518083038185875af1925050503d8060008114611812576040519150601f19603f3d011682016040523d82523d6000602084013e611817565b606091505b5091509150611827868383611833565b925050505b9392505050565b6060826118485761184382611886565b61182c565b815115801561185f57506001600160a01b0384163b155b1561187f5783604051639996b31560e01b815260040161137591906119ef565b508061182c565b8051156118965780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b600080604083850312156118c257600080fd5b50508035926020909101359150565b6000602082840312156118e357600080fd5b5035919050565b6001600160a01b038116811461138757600080fd5b60008083601f84011261191157600080fd5b5081356001600160401b0381111561192857600080fd5b6020830191508360208260051b850101111561194357600080fd5b9250929050565b60008060008060006080868803121561196257600080fd5b853561196d816118ea565b945060208601356001600160401b0381111561198857600080fd5b611994888289016118ff565b9095509350506040860135915060608601356119af816118ea565b809150509295509295909350565b600080600080608085870312156119d357600080fd5b5050823594602084013594506040840135936060013592509050565b6001600160a01b0391909116815260200190565b801515811461138757600080fd5b600060208284031215611a2357600080fd5b813561182c81611a03565b600080600060608486031215611a4357600080fd5b8335611a4e816118ea565b92506020840135611a5e816118ea565b91506040840135611a6e816118ea565b809150509250925092565b600080600060608486031215611a8e57600080fd5b83359250602084013591506040840135611a6e816118ea565b600060208284031215611ab957600080fd5b813561182c816118ea565b600080600060408486031215611ad957600080fd5b8335611ae4816118ea565b925060208401356001600160401b03811115611aff57600080fd5b611b0b868287016118ff565b9497909650939450505050565b60008060008060408587031215611b2e57600080fd5b84356001600160401b0380821115611b4557600080fd5b611b51888389016118ff565b90965094506020870135915080821115611b6a57600080fd5b50611b77878288016118ff565b95989497509550505050565b600080600060608486031215611b9857600080fd5b8335611ba3816118ea565b92506020840135611bb3816118ea565b929592945050506040919091013590565b600080600080600060a08688031215611bdc57600080fd5b8535945060208601359350604086013592506060860135611bfc816118ea565b949793965091946080013592915050565b600060208284031215611c1f57600080fd5b5051919050565b8183526000602080850194508260005b85811015611c64578135611c49816118ea565b6001600160a01b031687529582019590820190600101611c36565b509495945050505050565b608081526000611c83608083018789611c26565b6020830195909552506001600160a01b039283166040820152911660609091015292915050565b634e487b7160e01b600052601160045260246000fd5b818103818111156113f6576113f6611caa565b80820281158282048414176113f6576113f6611caa565b600082611d0757634e487b7160e01b600052601260045260246000fd5b500490565b6001600160a01b0393841681526020810192909252909116604082015260600190565b634e487b7160e01b600052604160045260246000fd5b6040516101e081016001600160401b0381118282101715611d6857611d68611d2f565b60405290565b604051601f8201601f191681016001600160401b0381118282101715611d9657611d96611d2f565b604052919050565b600060208284031215611db057600080fd5b604051602081016001600160401b0381118282101715611dd257611dd2611d2f565b6040529151825250919050565b80516001600160801b0381168114611df657600080fd5b919050565b805164ffffffffff81168114611df657600080fd5b805161ffff81168114611df657600080fd5b8051611df6816118ea565b60006101e08284031215611e4057600080fd5b611e48611d45565b611e528484611d9e565b8152611e6060208401611ddf565b6020820152611e7160408401611ddf565b6040820152611e8260608401611ddf565b6060820152611e9360808401611ddf565b6080820152611ea460a08401611ddf565b60a0820152611eb560c08401611dfb565b60c0820152611ec660e08401611e10565b60e0820152610100611ed9818501611e22565b90820152610120611eeb848201611e22565b90820152610140611efd848201611e22565b90820152610160611f0f848201611e22565b90820152610180611f21848201611ddf565b908201526101a0611f33848201611ddf565b908201526101c0611f45848201611ddf565b908201529392505050565b604081526000611f64604083018587611c26565b905060018060a01b0383166020830152949350505050565b60006001600160401b03821115611f9557611f95611d2f565b5060051b60200190565b600082601f830112611fb057600080fd5b81516020611fc5611fc083611f7c565b611d6e565b8083825260208201915060208460051b870101935086841115611fe757600080fd5b602086015b848110156120035780518352918301918301611fec565b509695505050505050565b6000806040838503121561202157600080fd5b82516001600160401b038082111561203857600080fd5b818501915085601f83011261204c57600080fd5b8151602061205c611fc083611f7c565b82815260059290921b8401810191818101908984111561207b57600080fd5b948201945b838610156120a2578551612093816118ea565b82529482019490820190612080565b918801519196509093505050808211156120bb57600080fd5b506120c885828601611f9f565b9150509250929050565b634e487b7160e01b600052603260045260246000fd5b808201808211156113f6576113f6611caa565b6001600160a01b03929092168252602082015260400190565b60006020828403121561212657600080fd5b815161182c81611a03565b6000825160005b818110156121525760208186018101518583015201612138565b50600092019182525091905056fea2646970667358221220013cde20eec9f82248f7392ed2c6989226691c36efe09beb7536f583950009b864736f6c63430008190033000000000000000000000000bc69d8d072fa7f7b2d6a3773f915e497917a22d9000000000000000000000000205a73f1b0bb5c549309dcae9dac4342e145f65d00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000205a73f1b0bb5c549309dcae9dac4342e145f65d00000000000000000000000000000000000000001027e72f1f1281308800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca00000000000000000000000005fd13359ba15a84b76f7f87568309040176167cd00000000000000000000000006824c27c8a0dbde5f72f770ec82e3c0fd4dcec3

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106101c25760003560e01c80630827b460146101c7578063184d69ab146101ed5780631f5f33291461021157806324e483c21461022457806324f607d41461023957806325f7911f1461024c578063338274381461025f5780634904130b1461027f5780634a999118146102925780634aa07e64146102a55780634e402e57146102b8578063559fda33146102cb57806358d4ee9f146102de578063715018a6146102f15780637c03c8c9146102f95780637f361a121461030257806387c076301461031557806389a30271146103285780638da5cb5b1461033b578063996be521146103435780639cea72e214610356578063a65135621461035f578063ac84725114610372578063b0656c3914610395578063bc39f66f146103a8578063c628148a146103bb578063cab75a65146103ce578063caf901c1146103e1578063cea9d26f146103f4578063cf35bdd014610407578063de1e9d4d146101c7578063deab8aea1461043a578063e1e891201461044d578063e6e58b5f14610460578063e6fcd8bd14610473578063e72724db14610486578063e9d337b814610499578063f2fde38b146104ac575b600080fd5b6101da6101d53660046118af565b6104bf565b6040519081526020015b60405180910390f35b600b5461020190600160a01b900460ff1681565b60405190151581526020016101e4565b6101da61021f3660046118d1565b6104ec565b61023761023236600461194a565b6105aa565b005b6102016102473660046118d1565b610631565b6101da61025a3660046119bd565b610670565b600154610272906001600160a01b031681565b6040516101e491906119ef565b61023761028d3660046118d1565b6106d1565b6102376102a0366004611a11565b610714565b600954610272906001600160a01b031681565b600254610272906001600160a01b031681565b600854610272906001600160a01b031681565b600a54610272906001600160a01b031681565b61023761073a565b6101da60045481565b610237610310366004611a2e565b61074e565b610237610323366004611a79565b6107dc565b600754610272906001600160a01b031681565b6102726109fc565b6102376103513660046118d1565b610a0b565b6101da60055481565b6101da61036d3660046118d1565b610a4e565b610201610380366004611aa7565b600c6020526000908152604090205460ff1681565b6102376103a33660046118af565b610b05565b6102376103b6366004611ac4565b610c49565b6102376103c9366004611b18565b610ccf565b6102376103dc3660046118d1565b610d85565b6102726103ef3660046118d1565b610e1e565b610237610402366004611b83565b610e71565b61041a6104153660046118d1565b610f0f565b604080516001600160a01b039384168152929091166020830152016101e4565b600354610272906001600160a01b031681565b61023761045b366004611bc4565b610f51565b61023761046e366004611bc4565b61114f565b600b54610272906001600160a01b031681565b6102726104943660046118d1565b6112d5565b600654610272906001600160a01b031681565b6102376104ba366004611aa7565b611343565b60008280600210156104e45760405163544420af60e01b815260040160405180910390fd5b509092915050565b60008180600210156105115760405163544420af60e01b815260040160405180910390fd5b826000036105955760065460075460405163d15e005360e01b81526001600160a01b039283169263d15e00539261054d929116906004016119ef565b602060405180830381865afa15801561056a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061058e9190611c0d565b91506105a4565b676765c793fa10079d601b1b91505b50919050565b6105b261138a565b6040516308d8c03760e21b81526001600160a01b0386169063236300dc906105e69087908790879033908890600401611c6f565b6020604051808303816000875af1158015610605573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106299190611c0d565b505050505050565b60008180600210156106565760405163544420af60e01b815260040160405180910390fd5b8260000361066757600191506105a4565b600091506105a4565b60008480600210156106955760405163544420af60e01b815260040160405180910390fd5b856000036106c357846106b2856106ac83876113bc565b906113fc565b6106bc9190611cc0565b91506106c8565b600091505b50949350505050565b6106d961138a565b806106f0676765c793fa10079d601b1b6064611cd3565b101561070f57604051633aaeda2b60e01b815260040160405180910390fd5b600455565b61071c61138a565b600b8054911515600160a01b0260ff60a01b19909216919091179055565b61074261138a565b61074c600061141f565b565b61075661138a565b6001600160a01b0383161561078157600180546001600160a01b0319166001600160a01b0385161790555b6001600160a01b038216156107ac57600280546001600160a01b0319166001600160a01b0384161790555b6001600160a01b038116156107d757600380546001600160a01b0319166001600160a01b0383161790555b505050565b8280600210156107ff5760405163544420af60e01b815260040160405180910390fd5b6001546001600160a01b0316331461082a5760405163bf4a682160e01b815260040160405180910390fd5b6000610842676765c793fa10079d601b1b6064611cd3565b60045461084f9086611cd3565b6108599190611cea565b905060006108678286611cc0565b9050856000036109955781156108fa57600654600754600354604051631a4ca37b60e21b81526001600160a01b03938416936369328dec936108b59390821692889290911690600401611d0c565b6020604051808303816000875af11580156108d4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108f89190611c0d565b505b600181116109095750506109f6565b6006546007546001600160a01b03918216916369328dec911661092d600185611cc0565b876040518463ffffffff1660e01b815260040161094c93929190611d0c565b6020604051808303816000875af115801561096b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061098f9190611c0d565b50610629565b60006109a0876112d5565b905082156109c2576003546109c2906001600160a01b0383811691168561146f565b600182116109d2575050506109f6565b6109f2856109e1600185611cc0565b6001600160a01b038416919061146f565b5050505b50505050565b6000546001600160a01b031690565b610a1361138a565b80610a2a676765c793fa10079d601b1b6064611cd3565b1015610a4957604051633aaeda2b60e01b815260040160405180910390fd5b600555565b6000818060021015610a735760405163544420af60e01b815260040160405180910390fd5b82600003610667576006546007546040516335ea6a7560e01b81526001600160a01b03928316926335ea6a7592610aaf929116906004016119ef565b6101e060405180830381865afa158015610acd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610af19190611e2d565b604001516001600160801b031691506105a4565b818060021015610b285760405163544420af60e01b815260040160405180910390fd5b6001546001600160a01b03163314610b535760405163bf4a682160e01b815260040160405180910390fd5b600b54600160a01b900460ff168015610b7c5750326000908152600c602052604090205460ff16155b15610b9a5760405163b709dc6560e01b815260040160405180910390fd5b82600003610c3057600654600754610bbf916001600160a01b039182169116846114c7565b60065460075460405163e8eda9df60e01b81526001600160a01b039182166004820152602481018590523060448201526000606482015291169063e8eda9df90608401600060405180830381600087803b158015610c1c57600080fd5b505af11580156109f2573d6000803e3d6000fd5b604051634b30351160e11b815260040160405180910390fd5b610c5161138a565b60405163bb492bf560e01b81526001600160a01b0384169063bb492bf590610c8190859085903390600401611f50565b6000604051808303816000875af1158015610ca0573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610cc8919081019061200e565b5050505050565b610cd761138a565b82818114610cf857604051634b87fa4960e11b815260040160405180910390fd5b60005b8181101561062957838382818110610d1557610d156120d2565b9050602002016020810190610d2a9190611a11565b600c6000888885818110610d4057610d406120d2565b9050602002016020810190610d559190611aa7565b6001600160a01b031681526020810191909152604001600020805460ff1916911515919091179055600101610cfb565b808060021015610da85760405163544420af60e01b815260040160405180910390fd5b6001546001600160a01b03163314610dd35760405163bf4a682160e01b815260040160405180910390fd5b600b54600160a01b900460ff168015610dfc5750326000908152600c602052604090205460ff16155b15610e1a5760405163b709dc6560e01b815260040160405180910390fd5b5050565b6000818060021015610e435760405163544420af60e01b815260040160405180910390fd5b82600003610e5e576007546001600160a01b031691506105a4565b6009546001600160a01b031691506105a4565b610e7961138a565b6001600160a01b038316610efb576000826001600160a01b03168260405160006040518083038185875af1925050503d8060008114610ed4576040519150601f19603f3d011682016040523d82523d6000602084013e610ed9565b606091505b50509050806109f657604051630aa5c45f60e11b815260040160405180910390fd5b6107d76001600160a01b038416838361146f565b600080828060021015610f355760405163544420af60e01b815260040160405180910390fd5b610f3e84610e1e565b9250610f49846112d5565b915050915091565b848060021015610f745760405163544420af60e01b815260040160405180910390fd5b6001546001600160a01b03163314610f9f5760405163bf4a682160e01b815260040160405180910390fd5b600b54600160a01b900460ff168015610fc85750326000908152600c602052604090205460ff16155b15610fe65760405163b709dc6560e01b815260040160405180910390fd5b8560000361112b576000610ffa85876120e8565b905060055460001415801561100e57508415155b801561101b575060055483105b15611090576000611038676765c793fa10079d601b1b6064611cd3565b6110428588611cd3565b60055461104f9089611cd3565b6110599190611cc0565b6110639190611cea565b9050801561108e576110758183611cc0565b60075490925061108e906001600160a01b031682611559565b505b6001811161109e5750610629565b6006546007546001600160a01b03918216916369328dec91166110c2600185611cc0565b876040518463ffffffff1660e01b81526004016110e193929190611d0c565b6020604051808303816000875af1158015611100573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111249190611c0d565b5050610629565b61114a8661113988886104bf565b61114389886104bf565b868661114f565b610629565b8480600210156111725760405163544420af60e01b815260040160405180910390fd5b6001546001600160a01b0316331461119d5760405163bf4a682160e01b815260040160405180910390fd5b600b54600160a01b900460ff1680156111c65750326000908152600c602052604090205460ff16155b156111e45760405163b709dc6560e01b815260040160405180910390fd5b6000808715611204576111f6886112d5565b6111ff896112d5565b61120d565b61120d88610f0f565b91509150600061121d89886104bf565b6112278a8a6104bf565b61123191906120e8565b905060055460001415801561124557508615155b1561129a576000676765c793fa10079d601b1b866005546112669190611cc0565b611270908a611cd3565b61127a9190611cea565b905080156112985761128c8183611cc0565b91506112988482611559565b505b600181116112aa57505050610629565b6112ca866112b9600184611cc0565b6001600160a01b038516919061146f565b505050505050505050565b60008180600210156112fa5760405163544420af60e01b815260040160405180910390fd5b82600003611315576008546001600160a01b031691506105a4565b8260010361133057600a546001600160a01b031691506105a4565b600b546001600160a01b031691506105a4565b61134b61138a565b6001600160a01b03811661137e576000604051631e4fbdf760e01b815260040161137591906119ef565b60405180910390fd5b6113878161141f565b50565b336113936109fc565b6001600160a01b03161461074c573360405163118cdaa760e01b815260040161137591906119ef565b6000676765c793fa10079d601b1b6113d5600282611cea565b6113df8486611cd3565b6113e991906120e8565b6113f39190611cea565b90505b92915050565b60008161140a600282611cea565b6113df676765c793fa10079d601b1b86611cd3565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6107d783846001600160a01b031663a9059cbb85856040516024016114959291906120fb565b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050506116ad565b6000836001600160a01b031663095ea7b384846040516024016114eb9291906120fb565b604051602081830303815290604052915060e01b6020820180516001600160e01b03838183161783525050505090506115248482611707565b6109f65761154f84856001600160a01b031663095ea7b38660006040516024016114959291906120fb565b6109f684826116ad565b6001811015611566575050565b6007546001600160a01b0390811690831603611611576006546001600160a01b03166369328dec83611599600185611cc0565b6002546040516001600160e01b031960e086901b1681526115c89392916001600160a01b031690600401611d0c565b6020604051808303816000875af11580156115e7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061160b9190611c0d565b5061162c565b60025461162c906001600160a01b03166112b9600184611cc0565b6002546001600160a01b03163b63ffffffff1615610e1a57600254604051630768cd4560e51b81526001600160a01b03848116600483015260248201849052600060448301529091169063ed19a8a090606401600060405180830381600087803b15801561169957600080fd5b505af1158015610629573d6000803e3d6000fd5b60006116c26001600160a01b038416836117af565b905080516000141580156116e75750808060200190518101906116e59190612114565b155b156107d75782604051635274afe760e01b815260040161137591906119ef565b6000806000846001600160a01b0316846040516117249190612131565b6000604051808303816000865af19150503d8060008114611761576040519150601f19603f3d011682016040523d82523d6000602084013e611766565b606091505b50915091508180156117905750805115806117905750808060200190518101906117909190612114565b80156117a657506000856001600160a01b03163b115b95945050505050565b60606113f38383600084600080856001600160a01b031684866040516117d59190612131565b60006040518083038185875af1925050503d8060008114611812576040519150601f19603f3d011682016040523d82523d6000602084013e611817565b606091505b5091509150611827868383611833565b925050505b9392505050565b6060826118485761184382611886565b61182c565b815115801561185f57506001600160a01b0384163b155b1561187f5783604051639996b31560e01b815260040161137591906119ef565b508061182c565b8051156118965780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b600080604083850312156118c257600080fd5b50508035926020909101359150565b6000602082840312156118e357600080fd5b5035919050565b6001600160a01b038116811461138757600080fd5b60008083601f84011261191157600080fd5b5081356001600160401b0381111561192857600080fd5b6020830191508360208260051b850101111561194357600080fd5b9250929050565b60008060008060006080868803121561196257600080fd5b853561196d816118ea565b945060208601356001600160401b0381111561198857600080fd5b611994888289016118ff565b9095509350506040860135915060608601356119af816118ea565b809150509295509295909350565b600080600080608085870312156119d357600080fd5b5050823594602084013594506040840135936060013592509050565b6001600160a01b0391909116815260200190565b801515811461138757600080fd5b600060208284031215611a2357600080fd5b813561182c81611a03565b600080600060608486031215611a4357600080fd5b8335611a4e816118ea565b92506020840135611a5e816118ea565b91506040840135611a6e816118ea565b809150509250925092565b600080600060608486031215611a8e57600080fd5b83359250602084013591506040840135611a6e816118ea565b600060208284031215611ab957600080fd5b813561182c816118ea565b600080600060408486031215611ad957600080fd5b8335611ae4816118ea565b925060208401356001600160401b03811115611aff57600080fd5b611b0b868287016118ff565b9497909650939450505050565b60008060008060408587031215611b2e57600080fd5b84356001600160401b0380821115611b4557600080fd5b611b51888389016118ff565b90965094506020870135915080821115611b6a57600080fd5b50611b77878288016118ff565b95989497509550505050565b600080600060608486031215611b9857600080fd5b8335611ba3816118ea565b92506020840135611bb3816118ea565b929592945050506040919091013590565b600080600080600060a08688031215611bdc57600080fd5b8535945060208601359350604086013592506060860135611bfc816118ea565b949793965091946080013592915050565b600060208284031215611c1f57600080fd5b5051919050565b8183526000602080850194508260005b85811015611c64578135611c49816118ea565b6001600160a01b031687529582019590820190600101611c36565b509495945050505050565b608081526000611c83608083018789611c26565b6020830195909552506001600160a01b039283166040820152911660609091015292915050565b634e487b7160e01b600052601160045260246000fd5b818103818111156113f6576113f6611caa565b80820281158282048414176113f6576113f6611caa565b600082611d0757634e487b7160e01b600052601260045260246000fd5b500490565b6001600160a01b0393841681526020810192909252909116604082015260600190565b634e487b7160e01b600052604160045260246000fd5b6040516101e081016001600160401b0381118282101715611d6857611d68611d2f565b60405290565b604051601f8201601f191681016001600160401b0381118282101715611d9657611d96611d2f565b604052919050565b600060208284031215611db057600080fd5b604051602081016001600160401b0381118282101715611dd257611dd2611d2f565b6040529151825250919050565b80516001600160801b0381168114611df657600080fd5b919050565b805164ffffffffff81168114611df657600080fd5b805161ffff81168114611df657600080fd5b8051611df6816118ea565b60006101e08284031215611e4057600080fd5b611e48611d45565b611e528484611d9e565b8152611e6060208401611ddf565b6020820152611e7160408401611ddf565b6040820152611e8260608401611ddf565b6060820152611e9360808401611ddf565b6080820152611ea460a08401611ddf565b60a0820152611eb560c08401611dfb565b60c0820152611ec660e08401611e10565b60e0820152610100611ed9818501611e22565b90820152610120611eeb848201611e22565b90820152610140611efd848201611e22565b90820152610160611f0f848201611e22565b90820152610180611f21848201611ddf565b908201526101a0611f33848201611ddf565b908201526101c0611f45848201611ddf565b908201529392505050565b604081526000611f64604083018587611c26565b905060018060a01b0383166020830152949350505050565b60006001600160401b03821115611f9557611f95611d2f565b5060051b60200190565b600082601f830112611fb057600080fd5b81516020611fc5611fc083611f7c565b611d6e565b8083825260208201915060208460051b870101935086841115611fe757600080fd5b602086015b848110156120035780518352918301918301611fec565b509695505050505050565b6000806040838503121561202157600080fd5b82516001600160401b038082111561203857600080fd5b818501915085601f83011261204c57600080fd5b8151602061205c611fc083611f7c565b82815260059290921b8401810191818101908984111561207b57600080fd5b948201945b838610156120a2578551612093816118ea565b82529482019490820190612080565b918801519196509093505050808211156120bb57600080fd5b506120c885828601611f9f565b9150509250929050565b634e487b7160e01b600052603260045260246000fd5b808201808211156113f6576113f6611caa565b6001600160a01b03929092168252602082015260400190565b60006020828403121561212657600080fd5b815161182c81611a03565b6000825160005b818110156121525760208186018101518583015201612138565b50600092019182525091905056fea2646970667358221220013cde20eec9f82248f7392ed2c6989226691c36efe09beb7536f583950009b864736f6c63430008190033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000bc69d8d072fa7f7b2d6a3773f915e497917a22d9000000000000000000000000205a73f1b0bb5c549309dcae9dac4342e145f65d00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000205a73f1b0bb5c549309dcae9dac4342e145f65d00000000000000000000000000000000000000001027e72f1f1281308800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca00000000000000000000000005fd13359ba15a84b76f7f87568309040176167cd00000000000000000000000006824c27c8a0dbde5f72f770ec82e3c0fd4dcec3

-----Decoded View---------------
Arg [0] : liquidityManager_ (address): 0xBc69d8D072Fa7F7b2D6A3773F915e497917a22D9
Arg [1] : ecclesiaDao_ (address): 0x205a73F1B0bB5c549309dCaE9dac4342e145F65D
Arg [2] : aaveLendingPool_ (address): 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2
Arg [3] : reserveAsset_ (address): 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
Arg [4] : buybackWallet_ (address): 0x205a73F1B0bB5c549309dCaE9dac4342e145F65D
Arg [5] : payoutDeductibleRate_ (uint256): 5000000000000000000000000000
Arg [6] : performanceFee_ (uint256): 0
Arg [7] : wstETH_ (address): 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0
Arg [8] : amphrETH_ (address): 0x5fD13359Ba15A84B76f7F87568309040176167cd
Arg [9] : amphrLRT_ (address): 0x06824C27C8a0DbDe5F72f770eC82e3c0FD4DcEc3

-----Encoded View---------------
10 Constructor Arguments found :
Arg [0] : 000000000000000000000000bc69d8d072fa7f7b2d6a3773f915e497917a22d9
Arg [1] : 000000000000000000000000205a73f1b0bb5c549309dcae9dac4342e145f65d
Arg [2] : 00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e2
Arg [3] : 000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48
Arg [4] : 000000000000000000000000205a73f1b0bb5c549309dcae9dac4342e145f65d
Arg [5] : 00000000000000000000000000000000000000001027e72f1f12813088000000
Arg [6] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [7] : 0000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca0
Arg [8] : 0000000000000000000000005fd13359ba15a84b76f7f87568309040176167cd
Arg [9] : 00000000000000000000000006824c27c8a0dbde5f72f770ec82e3c0fd4dcec3


Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.