S Price: $0.557599 (+1.53%)

Contract

0xe9d414373b81Bb94623C5C9Ec7334A18D63Fc4FB

Overview

S Balance

Sonic LogoSonic LogoSonic Logo0 S

S Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

Parent Transaction Hash Block From To
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
DXToken

Compiler Version
v0.8.28+commit.7893614a

Optimization Enabled:
Yes with 200 runs

Other Settings:
shanghai EvmVersion
File 1 of 54 : DXToken.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

// Import statements
import { ERC20PermitUpgradeable, ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import { SafeERC20Upgradeable, IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { CustomRevert } from "./libs/CustomRevert.sol";
import { ABDKMathQuad } from "abdk-libraries-solidity/ABDKMathQuad.sol";

// Interface definitions
import { IDXToken } from "./interfaces/IDXToken.sol";
import { GroupId } from "./types/GroupId.sol";
import { DDXToken } from "./declarations/DDXToken.sol";
import { FeeModel } from "./types/CommonTypes.sol";
import { ITreasury } from "./interfaces/ITreasury.sol";
import { IAToken } from "./interfaces/IAToken.sol";

contract DXToken is ERC20PermitUpgradeable, AccessControlUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable, IDXToken {
  using SafeERC20Upgradeable for IERC20Upgradeable;
  using CustomRevert for bytes4;
  using ABDKMathQuad for bytes16;

  // @dev we consider using 18 decimals, otherwise we can update these before deployment
  uint256 private PRECISION;
  uint256 private constant MAX_COOLING_OFF_PERIOD = 1 days;
  uint256 private constant YEAR = 365 days;
  uint256 private constant MIN_MINTING_BURNING_AMOUNT = 1e6; // 0.001 tokens
  uint256 private constant BASIS_POINTS = 10_000;
  uint256 private constant MAX_RATIO = 3_000; // 30%
  uint256 private constant MAX_VARIABLE_FEE = 1e21; // 1000 tokens
  uint256 private constant DUST = 1e2; // Small constant to avoid rounding issues
  uint256 public MAX_SUPPLY;
  uint256 public coolingOffPeriod;

  bytes32 public constant TREASURY_ROLE = keccak256("TREASURY_ROLE");
  bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

  uint8 private _decimals;

  GroupId public associatedGroupId;
  IAToken public aToken;
  ITreasury public treasury;
  FeeModel public feeModel;

  uint256 public lastDecayTimestamp;
  uint256 public managementFeeRate; // in basis points (bps)
  uint256 public fixedYieldAmount; // VARIABLE_FUNDING_FEE model
  uint256 public annualFundingFeeRate; // FIXED_FUNDING_FEE model, in bps
  uint256 private _totalRawSupply;
  bytes16 public decayFactor; // Starts at 1e18 in ABDKMathQuad format

  bool public beta;

  mapping(address => uint256) private _rawBalances;
  mapping(address => uint256) public mintAt;

  /// @custom:oz-upgrades-unsafe-allow constructor
  constructor() initializer {}

  /**
   * @dev Fallback function to prevent accidental Ether transfers.
   */
  receive() external payable {
    IDXToken.NotPermitted.selector.revertWith();
  }

  /**
   * @dev Fallback function to prevent accidental Ether transfers.
   */
  fallback() external payable {
    IDXToken.NotPermitted.selector.revertWith();
  }

  /**
   * @notice Initializes the DXToken contract with the given parameters.
   * @param tokenParams The parameters for the token.
   * @param feeParams The parameters for the fee model.
   * @param _admin The address of the admin.
   */
  function initialize(
    DDXToken.TokenParams calldata tokenParams,
    DDXToken.FeeParams calldata feeParams,
    address _admin,
    bool _beta
  ) external initializer {
    // Input validations
    if (tokenParams.aTokenAddress == address(0)) IDXToken.ZeroAddress.selector.revertWith();
    if (tokenParams.treasuryAddress == address(0)) IDXToken.ZeroAddress.selector.revertWith();
    if (_admin == address(0)) IDXToken.ZeroAddress.selector.revertWith();
    if (tokenParams.decimals == 0 || tokenParams.decimals > 18) IDXToken.InvalidDecimals.selector.revertWith();
    if (tokenParams.initialCoolingOffPeriod > MAX_COOLING_OFF_PERIOD) IDXToken.CoolingOffPeriodTooLarge.selector.revertWith();

    // Initialize inherited contracts
    __ERC20_init(tokenParams.name, tokenParams.symbol);
    __ERC20Permit_init(tokenParams.name);
    __AccessControl_init();
    __Pausable_init();
    __ReentrancyGuard_init();

    // Grant roles
    _grantRole(DEFAULT_ADMIN_ROLE, _admin);
    _grantRole(TREASURY_ROLE, tokenParams.treasuryAddress);
    _grantRole(PAUSER_ROLE, _admin);

    // Set initial state variables
    _decimals = tokenParams.decimals;
    MAX_SUPPLY = tokenParams.maxSupply;
    associatedGroupId = tokenParams.groupId;
    aToken = IAToken(tokenParams.aTokenAddress);
    treasury = ITreasury(tokenParams.treasuryAddress);
    coolingOffPeriod = tokenParams.initialCoolingOffPeriod;
    feeModel = feeParams.feeModel;

    _validateAndSetFeeParams(feeParams);

    // Set PRECISION
    PRECISION = 10 ** uint256(_decimals);

    // Initialize decay variables
    decayFactor = ABDKMathQuad.fromUInt(PRECISION); // Starts at 1e18 in bytes16
    lastDecayTimestamp = block.timestamp;
    beta = _beta;
  }

  /**
   * @dev Validates and sets the fee parameters based on the fee model.
   * @param feeParams The fee parameters to validate and set.
   */
  function _validateAndSetFeeParams(DDXToken.FeeParams calldata feeParams) internal {
    if (feeModel == FeeModel.MANAGEMENT_FEE) {
      if (feeParams.managementFeeRate == 0 || feeParams.managementFeeRate > MAX_RATIO) IDXToken.InvalidManagementFee.selector.revertWith();
      managementFeeRate = feeParams.managementFeeRate;
    } else if (feeModel == FeeModel.VARIABLE_FUNDING_FEE) {
      if (feeParams.fixedYieldAmount == 0 || feeParams.fixedYieldAmount > MAX_VARIABLE_FEE)
        IDXToken.InvalidFixedYieldAmount.selector.revertWith();
      fixedYieldAmount = feeParams.fixedYieldAmount;
    } else if (feeModel == FeeModel.FIXED_FUNDING_FEE) {
      if (feeParams.annualFundingFeeRate == 0 || feeParams.annualFundingFeeRate > MAX_RATIO)
        IDXToken.InvalidFundingFeeRate.selector.revertWith();
      annualFundingFeeRate = feeParams.annualFundingFeeRate;
    } else if (feeModel == FeeModel.NONE) {
      // No fee parameters to set
      assert(true);
    } else {
      IDXToken.InvalidFeeModel.selector.revertWith();
    }
  }

  /**
   * @notice Calculates the Net Asset Value (NAV) of the token.
   * @return The NAV of the token in 18 decimal precision.
   */
  function nav() public view override returns (uint256) {
    uint256 _baseNav = treasury.currentBaseTokenPrice(associatedGroupId);
    uint256 _baseSupply = treasury.totalBaseToken(associatedGroupId);

    uint256 _xSupply = totalSupply();
    uint256 _aSupply = aToken.totalSupply();

    if (beta) {
      if (_aSupply + _xSupply < DUST) return PRECISION;

      return (_baseNav * _baseSupply) / (_xSupply + _aSupply);
    }

    if (treasury.isUnderCollateral(associatedGroupId)) return 0;
    if (_xSupply < DUST || _baseSupply < DUST) return PRECISION;

    return ((_baseNav * _baseSupply) / PRECISION - (_aSupply * PRECISION)) / _xSupply;
  }

  /**
   * @notice Converts a given amount of DXToken to the equivalent amount of base token.
   * @param dxTokenAmount The amount of DXToken to convert.
   * @return baseTokenAmount The equivalent amount of base token.
   */
  function dxTokenToBaseToken(uint256 dxTokenAmount) external view override returns (uint256 baseTokenAmount) {
    uint256 navPerToken = nav();
    uint256 baseTokenPrice = treasury.currentBaseTokenPrice(associatedGroupId);

    if (navPerToken == 0 || baseTokenPrice == 0) IDXToken.InvalidBaseTokenPrice.selector.revertWith();

    baseTokenAmount = (dxTokenAmount * navPerToken) / baseTokenPrice;
  }

  /**
   * @dev Calculates the updated decay factor as of the current block timestamp.
   * @return newDecayFactor The updated decay factor.
   */
  function _getUpdatedDecayFactor() internal view returns (bytes16) {
    uint256 timeElapsed = block.timestamp - lastDecayTimestamp;
    if (timeElapsed < DUST) return decayFactor;

    bytes16 newDecayFactor = decayFactor;

    if (feeModel == FeeModel.MANAGEMENT_FEE) {
      // Compute ratePerSecond = managementFeeRate / (BASIS_POINTS * YEAR)
      bytes16 annualRate = ABDKMathQuad.div(ABDKMathQuad.fromUInt(managementFeeRate), ABDKMathQuad.fromUInt(BASIS_POINTS));
      bytes16 ratePerSecond = ABDKMathQuad.div(annualRate, ABDKMathQuad.fromUInt(YEAR));

      // Compute exponent = -ratePerSecond * timeElapsed
      bytes16 exponent = ABDKMathQuad.mul(ABDKMathQuad.neg(ratePerSecond), ABDKMathQuad.fromUInt(timeElapsed));

      // Compute decayMultiplier = exp(exponent)
      bytes16 decayMultiplier = ABDKMathQuad.exp(exponent);

      // Update newDecayFactor
      newDecayFactor = ABDKMathQuad.mul(decayFactor, decayMultiplier);
    } else if (feeModel == FeeModel.FIXED_FUNDING_FEE) {
      // Similar computation for fixed funding fee
      bytes16 annualRate = ABDKMathQuad.div(ABDKMathQuad.fromUInt(annualFundingFeeRate), ABDKMathQuad.fromUInt(BASIS_POINTS));
      bytes16 ratePerSecond = ABDKMathQuad.div(annualRate, ABDKMathQuad.fromUInt(YEAR));
      bytes16 exponent = ABDKMathQuad.mul(ABDKMathQuad.neg(ratePerSecond), ABDKMathQuad.fromUInt(timeElapsed));
      bytes16 decayMultiplier = ABDKMathQuad.exp(exponent);
      newDecayFactor = ABDKMathQuad.mul(decayFactor, decayMultiplier);
    } else if (feeModel == FeeModel.VARIABLE_FUNDING_FEE) {
      // Variable funding fee model
      if (_totalRawSupply < DUST) {
        // No decay if total raw supply is zero
        return decayFactor;
      }

      uint256 feeIncrement = (fixedYieldAmount * timeElapsed * PRECISION) / (_totalRawSupply * 1 days);
      bytes16 feeIncrementQuad = ABDKMathQuad.fromUInt(feeIncrement);
      newDecayFactor = ABDKMathQuad.sub(decayFactor, feeIncrementQuad);
      if (ABDKMathQuad.cmp(newDecayFactor, ABDKMathQuad.fromUInt(0)) < 0) {
        newDecayFactor = ABDKMathQuad.fromUInt(0);
      }
    } else if (feeModel == FeeModel.NONE) {
      // No decay
      return decayFactor;
    } else {
      IDXToken.InvalidFeeModel.selector.revertWith();
    }

    return newDecayFactor;
  }

  /**
   * @notice Returns the balance of a specific account, adjusted for decay.
   * @param account The address of the account.
   * @return The adjusted balance of the account.
   */
  function balanceOf(address account) public view override(ERC20Upgradeable, IERC20Upgradeable) returns (uint256) {
    bytes16 updatedDecayFactor = _getUpdatedDecayFactor();
    bytes16 adjustedBalance = ABDKMathQuad.mul(ABDKMathQuad.fromUInt(_rawBalances[account]), updatedDecayFactor);
    return ABDKMathQuad.toUInt(adjustedBalance) / PRECISION;
  }

  /**
   * @notice Returns the total supply of the token, adjusted for decay.
   * @return The adjusted total supply of the token.
   */
  function totalSupply() public view override(ERC20Upgradeable, IERC20Upgradeable) returns (uint256) {
    bytes16 updatedDecayFactor = _getUpdatedDecayFactor();
    bytes16 adjustedTotalSupply = ABDKMathQuad.mul(ABDKMathQuad.fromUInt(_totalRawSupply), updatedDecayFactor);
    return ABDKMathQuad.toUInt(adjustedTotalSupply) / PRECISION;
  }

  /**
   * @dev Updates the global decay factor based on the elapsed time and fee model.
   */
  function _updateDecayFactor() internal {
    bytes16 newDecayFactor = _getUpdatedDecayFactor();
    decayFactor = newDecayFactor;
    lastDecayTimestamp = block.timestamp;
  }

  /**
   * @notice Mints new tokens to a specified address.
   * @param _to The address to mint tokens to.
   * @param _amount The amount of tokens to mint.
   */
  function mint(address _to, uint256 _amount) external override nonReentrant whenNotPaused onlyRole(TREASURY_ROLE) {
    if (_amount < MIN_MINTING_BURNING_AMOUNT) IDXToken.ErrorMinimumMintingAmount.selector.revertWith();
    if (_to == address(0)) IDXToken.ZeroAddress.selector.revertWith();

    // Check max supply
    if (totalSupply() + _amount > MAX_SUPPLY) IDXToken.ExceedsMaxSupply.selector.revertWith();

    // Update decay factor
    _updateDecayFactor();

    // Compute raw amount
    bytes16 amountInQuad = ABDKMathQuad.div(ABDKMathQuad.fromUInt(_amount * PRECISION), decayFactor);
    uint256 rawAmount = ABDKMathQuad.toUInt(amountInQuad);

    // Update raw balances and total raw supply
    _rawBalances[_to] += rawAmount;
    _totalRawSupply += rawAmount;

    // Record mint timestamp
    mintAt[_to] = block.timestamp;

    emit Transfer(address(0), _to, _amount);
    emit Minted(_to, _amount);
  }

  /**
   * @notice Burns tokens from a specified address.
   * @param _from The address to burn tokens from.
   * @param _amount The amount of tokens to burn.
   */
  function burn(address _from, uint256 _amount) external override nonReentrant whenNotPaused onlyRole(TREASURY_ROLE) {
    if (_amount < MIN_MINTING_BURNING_AMOUNT) IDXToken.ErrorMinimumBurningAmount.selector.revertWith();
    if (_from == address(0)) IDXToken.ZeroAddress.selector.revertWith();

    // Enforce cooling-off period
    if (block.timestamp - mintAt[_from] < coolingOffPeriod) {
      emit CoolingOffPeriodTriggered(_from, block.timestamp);
      IDXToken.CoolingOffPeriodActive.selector.revertWith();
    }

    // Update decay factor
    _updateDecayFactor();

    // Compute raw amount
    bytes16 amountInQuad = ABDKMathQuad.div(ABDKMathQuad.fromUInt(_amount * PRECISION), decayFactor);
    uint256 rawAmount = ABDKMathQuad.toUInt(amountInQuad);

    // Update raw balances and total raw supply
    uint256 rawBalance = _rawBalances[_from];
    if (rawBalance < rawAmount) IDXToken.BurnAmountExceedsBalance.selector.revertWith();

    _rawBalances[_from] = rawBalance - rawAmount;
    _totalRawSupply -= rawAmount;

    emit Transfer(_from, address(0), _amount);
    emit Burned(_from, _amount);
  }

  /**
   * @notice Collects accumulated fees and transfers them to the treasury.
   */
  function collectFees() external override nonReentrant onlyRole(TREASURY_ROLE) {
    // Capture the decayFactor and lastDecayTimestamp before updating
    bytes16 oldDecayFactor = decayFactor;

    // Compute adjusted total supply before updating decayFactor
    bytes16 adjustedTotalSupplyBeforeQuad = ABDKMathQuad.mul(ABDKMathQuad.fromUInt(_totalRawSupply), oldDecayFactor);

    _updateDecayFactor();

    // Compute adjusted total supply after updating decayFactor
    bytes16 adjustedTotalSupplyAfterQuad = ABDKMathQuad.mul(ABDKMathQuad.fromUInt(_totalRawSupply), decayFactor);

    // Use high-precision comparison
    if (ABDKMathQuad.cmp(adjustedTotalSupplyAfterQuad, adjustedTotalSupplyBeforeQuad) >= 0) {
      // No fees to collect, exit gracefully
      return;
    }

    // Compute fees to collect in high precision
    bytes16 feesQuad = ABDKMathQuad.sub(adjustedTotalSupplyBeforeQuad, adjustedTotalSupplyAfterQuad);

    // Convert fees to raw amount
    bytes16 rawFeesQuad = ABDKMathQuad.div(feesQuad, decayFactor);
    uint256 rawFees = ABDKMathQuad.toUInt(rawFeesQuad);

    // Update balances
    _rawBalances[address(treasury)] += rawFees;
    _totalRawSupply += rawFees;

    uint256 feesToCollect = ABDKMathQuad.toUInt(feesQuad) / PRECISION;

    emit FeesCollected(feesToCollect);
  }

  /**
   * @dev Internal transfer function, adjusted for decay and cooling-off period.
   * @param sender The address sending tokens.
   * @param recipient The address receiving tokens.
   * @param amount The amount of tokens to transfer.
   * @dev mints and burns are outside of this function
   */
  function _transfer(address sender, address recipient, uint256 amount) internal override whenNotPaused {
    if (sender == address(0) || recipient == address(0)) IDXToken.ZeroAddress.selector.revertWith();
    // Update decay factor
    _updateDecayFactor();

    // Compute raw amount based on decay
    bytes16 amountInQuad = ABDKMathQuad.div(ABDKMathQuad.fromUInt(amount * PRECISION), decayFactor);
    uint256 rawAmount = ABDKMathQuad.toUInt(amountInQuad);

    // Enforce cooling-off period
    if (block.timestamp - mintAt[sender] < coolingOffPeriod) {
      emit CoolingOffPeriodTriggered(sender, block.timestamp);
      IDXToken.CoolingOffPeriodActive.selector.revertWith();
    }

    // Check if sender has enough balance
    uint256 senderRawBalance = _rawBalances[sender];
    if (senderRawBalance < rawAmount) IDXToken.TransferAmountExceedsBalance.selector.revertWith();

    // Adjust balances
    _rawBalances[sender] = senderRawBalance - rawAmount;
    _rawBalances[recipient] += rawAmount;

    emit Transfer(sender, recipient, amount);
  }

  /**
   * @notice Updates the associated group ID.
   * @param groupId The new group ID to associate with.
   */
  function setGroup(bytes32 groupId) external onlyRole(DEFAULT_ADMIN_ROLE) {
    if (groupId == bytes32(0)) IDXToken.ZeroAddress.selector.revertWith();
    associatedGroupId = GroupId.wrap(groupId);
    emit UpdateGroup(groupId);
  }

  /**
   * @notice Updates the treasury address.
   * @param _newTreasury The new treasury address.
   */
  function updateTreasury(address _newTreasury) external onlyRole(DEFAULT_ADMIN_ROLE) {
    if (_newTreasury == address(0)) IDXToken.ZeroAddress.selector.revertWith();

    address oldTreasury = address(treasury);

    treasury = ITreasury(_newTreasury);
    grantRole(TREASURY_ROLE, _newTreasury);

    revokeRole(TREASURY_ROLE, oldTreasury);
    emit UpdateTreasuryAddress(oldTreasury, _newTreasury);
  }

  /**
   * @notice Updates the cooling-off period.
   * @param newCoolingOffPeriod The new cooling-off period in seconds.
   */
  function updateCoolingOffPeriod(uint256 newCoolingOffPeriod) external onlyRole(DEFAULT_ADMIN_ROLE) {
    if (newCoolingOffPeriod > MAX_COOLING_OFF_PERIOD) IDXToken.CoolingOffPeriodTooLarge.selector.revertWith();
    if (newCoolingOffPeriod == coolingOffPeriod) IDXToken.ParameterUnchanged.selector.revertWith();

    emit UpdateCoolingOffPeriod(coolingOffPeriod, newCoolingOffPeriod);
    coolingOffPeriod = newCoolingOffPeriod;
  }

  /**
   * @notice Updates the management fee rate.
   * @param newRate The new management fee rate in basis points.
   */
  function updateManagementFeeRate(uint256 newRate) external onlyRole(DEFAULT_ADMIN_ROLE) {
    if (feeModel != FeeModel.MANAGEMENT_FEE) IDXToken.InvalidFeeModel.selector.revertWith();
    if (newRate == 0 || newRate > MAX_RATIO) IDXToken.InvalidManagementFee.selector.revertWith();
    if (newRate == managementFeeRate) IDXToken.ParameterUnchanged.selector.revertWith();

    emit UpdateManagementFeeRate(managementFeeRate, newRate);
    managementFeeRate = newRate;
  }

  /**
   * @notice Updates the funding fee rate.
   * @param newRate The new funding fee rate in basis points.
   */
  function updateFundingFeeRate(uint256 newRate) external onlyRole(DEFAULT_ADMIN_ROLE) {
    if (feeModel != FeeModel.FIXED_FUNDING_FEE) IDXToken.InvalidFeeModel.selector.revertWith();
    if (newRate == 0 || newRate > MAX_RATIO) IDXToken.InvalidFundingFeeRate.selector.revertWith();
    if (newRate == annualFundingFeeRate) IDXToken.ParameterUnchanged.selector.revertWith();

    emit UpdateFundingFeeRate(annualFundingFeeRate, newRate);
    annualFundingFeeRate = newRate;
  }

  /**
   * @notice Update the variable funding fee amount
   * @param newAmount The new variable funding fee amount
   */
  function updateVariableFundingFeeRate(uint256 newAmount) external onlyRole(DEFAULT_ADMIN_ROLE) {
    if (feeModel != FeeModel.VARIABLE_FUNDING_FEE) IDXToken.InvalidFeeModel.selector.revertWith();
    if (newAmount == 0 || newAmount > MAX_VARIABLE_FEE) IDXToken.InvalidFixedYieldAmount.selector.revertWith();
    if (newAmount == fixedYieldAmount) IDXToken.ParameterUnchanged.selector.revertWith();

    emit UpdateVariableFundingFeeRate(fixedYieldAmount, newAmount);
    fixedYieldAmount = newAmount;
  }

  /**
   * @notice Updates the max supply
   * @param newSupply The new max supply
   */
  function updateMaxSupply(uint256 newSupply) external onlyRole(DEFAULT_ADMIN_ROLE) {
    if (newSupply == MAX_SUPPLY) IDXToken.ParameterUnchanged.selector.revertWith();
    if (newSupply < totalSupply()) IDXToken.NotPermitted.selector.revertWith();

    emit UpdateMaxSupply(MAX_SUPPLY, newSupply);
    MAX_SUPPLY = newSupply;
  }

  /**
   * @notice Returns the current fee model.
   * @return The fee model in use.
   */
  function getFeeModel() external view override returns (FeeModel) {
    return feeModel;
  }

  /**
   * @notice Pauses the contract, disabling certain functions.
   */
  function pause() external onlyRole(PAUSER_ROLE) {
    _pause();
  }

  /**
   * @notice Unpauses the contract, enabling certain functions.
   */
  function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
    _unpause();
  }

  /**
   * @notice Recovers ERC20 tokens mistakenly sent to this contract.
   * @param tokenAddress The address of the ERC20 token.
   * @param tokenAmount The amount of tokens to recover.
   */
  function recoverERC20(address tokenAddress, uint256 tokenAmount) external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE) {
    if (tokenAddress == address(this)) IDXToken.CannotRecoverToken.selector.revertWith();
    IERC20Upgradeable(tokenAddress).safeTransfer(_msgSender(), tokenAmount);
  }

  /**
   * @notice Overrides the decimals function to return the token's decimals.
   * @return The number of decimals used by the token.
   */
  function decimals() public view override returns (uint8) {
    return _decimals;
  }

  /**
   * @notice Checks if the contract supports a specific interface.
   * @param interfaceId The interface identifier.
   * @return True if the interface is supported, false otherwise.
   */
  function supportsInterface(bytes4 interfaceId) public view override(AccessControlUpgradeable) returns (bool) {
    return interfaceId == type(IDXToken).interfaceId || super.supportsInterface(interfaceId);
  }

  /**
   * @notice Prevents renouncing roles for security
   * @dev Overrides the default renounceRole function to prevent accidental role removal
   */
  function renounceRole(bytes32, address) public virtual override {
    revert("Roles can't be renounced");
  }

  // slither-disable-next-line unused-state
  uint256[50] private __gap; // Storage gap for upgradeability
}

File 2 of 54 : draft-ERC20PermitUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/draft-ERC20Permit.sol)

pragma solidity ^0.8.0;

// EIP-2612 is Final as of 2022-11-01. This file is deprecated.

import "./ERC20PermitUpgradeable.sol";

File 3 of 54 : AccessControlUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)

pragma solidity ^0.8.0;

import "./IAccessControlUpgradeable.sol";
import "../utils/ContextUpgradeable.sol";
import "../utils/StringsUpgradeable.sol";
import "../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
 * to enforce additional security measures for this role.
 */
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
    struct RoleData {
        mapping(address => bool) members;
        bytes32 adminRole;
    }

    mapping(bytes32 => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with a standardized message including the required role.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     *
     * _Available since v4.1._
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    function __AccessControl_init() internal onlyInitializing {
    }

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

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
        return _roles[role].members[account];
    }

    /**
     * @dev Revert with a standard message if `_msgSender()` is missing `role`.
     * Overriding this function changes the behavior of the {onlyRole} modifier.
     *
     * Format of the revert message is described in {_checkRole}.
     *
     * _Available since v4.6._
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Revert with a standard message if `account` is missing `role`.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert(
                string(
                    abi.encodePacked(
                        "AccessControl: account ",
                        StringsUpgradeable.toHexString(account),
                        " is missing role ",
                        StringsUpgradeable.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address account) public virtual override {
        require(account == _msgSender(), "AccessControl: can only renounce roles for self");

        _revokeRole(role, account);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event. Note that unlike {grantRole}, this function doesn't perform any
     * checks on the calling account.
     *
     * May emit a {RoleGranted} event.
     *
     * [WARNING]
     * ====
     * This function should only be called from the constructor when setting
     * up the initial roles for the system.
     *
     * Using this function in any other way is effectively circumventing the admin
     * system imposed by {AccessControl}.
     * ====
     *
     * NOTE: This function is deprecated in favor of {_grantRole}.
     */
    function _setupRole(bytes32 role, address account) internal virtual {
        _grantRole(role, account);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual {
        if (!hasRole(role, account)) {
            _roles[role].members[account] = true;
            emit RoleGranted(role, account, _msgSender());
        }
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual {
        if (hasRole(role, account)) {
            _roles[role].members[account] = false;
            emit RoleRevoked(role, account, _msgSender());
        }
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}

File 4 of 54 : PausableUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

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

    bool private _paused;

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

    function __Pausable_init_unchained() internal onlyInitializing {
        _paused = false;
    }

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

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

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

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        require(!paused(), "Pausable: paused");
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        require(paused(), "Pausable: not paused");
    }

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

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}

File 5 of 54 : ReentrancyGuardUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

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

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

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

    uint256 private _status;

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

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        _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
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

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

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}

File 6 of 54 : SafeERC20Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20Upgradeable.sol";
import "../extensions/IERC20PermitUpgradeable.sol";
import "../../../utils/AddressUpgradeable.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 SafeERC20Upgradeable {
    using AddressUpgradeable for address;

    /**
     * @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(IERC20Upgradeable token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, 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(IERC20Upgradeable token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20Upgradeable token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 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(IERC20Upgradeable token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @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(IERC20Upgradeable token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

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

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20PermitUpgradeable token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @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(IERC20Upgradeable 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, "SafeERC20: low-level call failed");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }

    /**
     * @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(IERC20Upgradeable 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))) && AddressUpgradeable.isContract(address(token));
    }
}

File 7 of 54 : CustomRevert.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { GroupId } from "../types/GroupId.sol";

// solhint-disable
/// @title Library for reverting with custom errors efficiently
/// @notice Contains functions for reverting with custom errors with different argument types efficiently
/// @dev To use this library, declare `using CustomRevert for bytes4;` and replace `revert CustomError()` with
/// `CustomError.selector.revertWith()`
/// @dev The functions may tamper with the free memory pointer but it is fine since the call context is exited immediately
library CustomRevert {
  /// @dev Reverts with the selector of a custom error in the scratch space
  function revertWith(bytes4 selector) internal pure {
    assembly("memory-safe") {
      mstore(0, selector)
      revert(0, 0x04)
    }
  }

  /// @dev Reverts with a custom error with an address argument in the scratch space
  function revertWith(bytes4 selector, address addr) internal pure {
    assembly("memory-safe") {
      mstore(0, selector)
      mstore(0x04, and(addr, 0xffffffffffffffffffffffffffffffffffffffff))
      revert(0, 0x24)
    }
  }

  /// @dev Reverts with a custom error with an int24 argument in the scratch space
  function revertWith(bytes4 selector, int24 value) internal pure {
    assembly("memory-safe") {
      mstore(0, selector)
      mstore(0x04, signextend(2, value))
      revert(0, 0x24)
    }
  }

  /// @dev Reverts with a custom error with a uint160 argument in the scratch space
  function revertWith(bytes4 selector, uint160 value) internal pure {
    assembly("memory-safe") {
      mstore(0, selector)
      mstore(0x04, and(value, 0xffffffffffffffffffffffffffffffffffffffff))
      revert(0, 0x24)
    }
  }

  /// @dev Reverts with a custom error with two int24 arguments
  function revertWith(bytes4 selector, int24 value1, int24 value2) internal pure {
    assembly("memory-safe") {
      let fmp := mload(0x40)
      mstore(fmp, selector)
      mstore(add(fmp, 0x04), signextend(2, value1))
      mstore(add(fmp, 0x24), signextend(2, value2))
      revert(fmp, 0x44)
    }
  }

  /// @dev Reverts with a custom error with two uint160 arguments
  function revertWith(bytes4 selector, uint160 value1, uint160 value2) internal pure {
    assembly("memory-safe") {
      let fmp := mload(0x40)
      mstore(fmp, selector)
      mstore(add(fmp, 0x04), and(value1, 0xffffffffffffffffffffffffffffffffffffffff))
      mstore(add(fmp, 0x24), and(value2, 0xffffffffffffffffffffffffffffffffffffffff))
      revert(fmp, 0x44)
    }
  }

  /// @dev Reverts with a custom error with two address arguments
  function revertWith(bytes4 selector, address value1, address value2) internal pure {
    assembly("memory-safe") {
      mstore(0, selector)
      mstore(0x04, and(value1, 0xffffffffffffffffffffffffffffffffffffffff))
      mstore(0x24, and(value2, 0xffffffffffffffffffffffffffffffffffffffff))
      revert(0, 0x44)
    }
  }

  /// @dev Reverts with a custom error with a bytes32 argument in the scratch space
  function revertWith(bytes4 selector, bytes32 value) internal pure {
    assembly("memory-safe") {
      mstore(0, selector)
      mstore(0x04, value)
      revert(0, 0x24)
    }
  }

  /// @dev Reverts with a custom error with a bytes32 argument in the scratch space
  function revertWith(bytes4 selector, GroupId value) internal pure {
    bytes32 valueBytes = GroupId.unwrap(value);
    assembly("memory-safe") {
      mstore(0, selector)
      mstore(0x04, valueBytes)
      revert(0, 0x24)
    }
  }

  /// @dev Reverts with a custom error with a bytes32 and an address argument
  function revertWith(bytes4 selector, GroupId value, address addr) internal pure {
    bytes32 valueBytes = GroupId.unwrap(value);
    assembly("memory-safe") {
      mstore(0x00, selector)
      mstore(0x04, valueBytes)
      mstore(0x24, and(addr, 0xffffffffffffffffffffffffffffffffffffffff))
      revert(0x00, 0x44)
    }
  }

  /// @dev Reverts with a custom error with a bytes32, address, and uint256 arguments
  function revertWith(bytes4 selector, GroupId value, address addr, uint256 amount) internal pure {
    bytes32 valueBytes = GroupId.unwrap(value);
    assembly("memory-safe") {
      mstore(0x00, selector)
      mstore(0x04, valueBytes)
      mstore(0x24, and(addr, 0xffffffffffffffffffffffffffffffffffffffff))
      mstore(0x44, amount)
      revert(0x00, 0x64)
    }
  }

  /// @notice bubble up the revert message returned by a call and revert with the selector provided
  /// @dev this function should only be used with custom errors of the type `CustomError(address target, bytes revertReason)`
  function bubbleUpAndRevertWith(bytes4 selector, address addr) internal pure {
    assembly("memory-safe") {
      let size := returndatasize()
      let fmp := mload(0x40)

      // Encode selector, address, offset, size, data
      mstore(fmp, selector)
      mstore(add(fmp, 0x04), addr)
      mstore(add(fmp, 0x24), 0x40)
      mstore(add(fmp, 0x44), size)
      returndatacopy(add(fmp, 0x64), 0, size)

      // Ensure the size is a multiple of 32 bytes
      let encodedSize := add(0x64, mul(div(add(size, 31), 32), 32))
      revert(fmp, encodedSize)
    }
  }
}

File 8 of 54 : ABDKMathQuad.sol
// SPDX-License-Identifier: BSD-4-Clause
/*
 * ABDK Math Quad Smart Contract Library.  Copyright © 2019 by ABDK Consulting.
 * Author: Mikhail Vladimirov <[email protected]>
 */
pragma solidity ^0.8.0;

/**
 * Smart contract library of mathematical functions operating with IEEE 754
 * quadruple-precision binary floating-point numbers (quadruple precision
 * numbers).  As long as quadruple precision numbers are 16-bytes long, they are
 * represented by bytes16 type.
 */
library ABDKMathQuad {
  /*
   * 0.
   */
  bytes16 private constant POSITIVE_ZERO = 0x00000000000000000000000000000000;

  /*
   * -0.
   */
  bytes16 private constant NEGATIVE_ZERO = 0x80000000000000000000000000000000;

  /*
   * +Infinity.
   */
  bytes16 private constant POSITIVE_INFINITY = 0x7FFF0000000000000000000000000000;

  /*
   * -Infinity.
   */
  bytes16 private constant NEGATIVE_INFINITY = 0xFFFF0000000000000000000000000000;

  /*
   * Canonical NaN value.
   */
  bytes16 private constant NaN = 0x7FFF8000000000000000000000000000;

  /**
   * Convert signed 256-bit integer number into quadruple precision number.
   *
   * @param x signed 256-bit integer number
   * @return quadruple precision number
   */
  function fromInt (int256 x) internal pure returns (bytes16) {
    unchecked {
      if (x == 0) return bytes16 (0);
      else {
        // We rely on overflow behavior here
        uint256 result = uint256 (x > 0 ? x : -x);

        uint256 msb = mostSignificantBit (result);
        if (msb < 112) result <<= 112 - msb;
        else if (msb > 112) result >>= msb - 112;

        result = result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 16383 + msb << 112;
        if (x < 0) result |= 0x80000000000000000000000000000000;

        return bytes16 (uint128 (result));
      }
    }
  }

  /**
   * Convert quadruple precision number into signed 256-bit integer number
   * rounding towards zero.  Revert on overflow.
   *
   * @param x quadruple precision number
   * @return signed 256-bit integer number
   */
  function toInt (bytes16 x) internal pure returns (int256) {
    unchecked {
      uint256 exponent = uint128 (x) >> 112 & 0x7FFF;

      require (exponent <= 16638); // Overflow
      if (exponent < 16383) return 0; // Underflow

      uint256 result = uint256 (uint128 (x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
        0x10000000000000000000000000000;

      if (exponent < 16495) result >>= 16495 - exponent;
      else if (exponent > 16495) result <<= exponent - 16495;

      if (uint128 (x) >= 0x80000000000000000000000000000000) { // Negative
        require (result <= 0x8000000000000000000000000000000000000000000000000000000000000000);
        return -int256 (result); // We rely on overflow behavior here
      } else {
        require (result <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
        return int256 (result);
      }
    }
  }

  /**
   * Convert unsigned 256-bit integer number into quadruple precision number.
   *
   * @param x unsigned 256-bit integer number
   * @return quadruple precision number
   */
  function fromUInt (uint256 x) internal pure returns (bytes16) {
    unchecked {
      if (x == 0) return bytes16 (0);
      else {
        uint256 result = x;

        uint256 msb = mostSignificantBit (result);
        if (msb < 112) result <<= 112 - msb;
        else if (msb > 112) result >>= msb - 112;

        result = result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 16383 + msb << 112;

        return bytes16 (uint128 (result));
      }
    }
  }

  /**
   * Convert quadruple precision number into unsigned 256-bit integer number
   * rounding towards zero.  Revert on underflow.  Note, that negative floating
   * point numbers in range (-1.0 .. 0.0) may be converted to unsigned integer
   * without error, because they are rounded to zero.
   *
   * @param x quadruple precision number
   * @return unsigned 256-bit integer number
   */
  function toUInt (bytes16 x) internal pure returns (uint256) {
    unchecked {
      uint256 exponent = uint128 (x) >> 112 & 0x7FFF;

      if (exponent < 16383) return 0; // Underflow

      require (uint128 (x) < 0x80000000000000000000000000000000); // Negative

      require (exponent <= 16638); // Overflow
      uint256 result = uint256 (uint128 (x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
        0x10000000000000000000000000000;

      if (exponent < 16495) result >>= 16495 - exponent;
      else if (exponent > 16495) result <<= exponent - 16495;

      return result;
    }
  }

  /**
   * Convert signed 128.128 bit fixed point number into quadruple precision
   * number.
   *
   * @param x signed 128.128 bit fixed point number
   * @return quadruple precision number
   */
  function from128x128 (int256 x) internal pure returns (bytes16) {
    unchecked {
      if (x == 0) return bytes16 (0);
      else {
        // We rely on overflow behavior here
        uint256 result = uint256 (x > 0 ? x : -x);

        uint256 msb = mostSignificantBit (result);
        if (msb < 112) result <<= 112 - msb;
        else if (msb > 112) result >>= msb - 112;

        result = result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 16255 + msb << 112;
        if (x < 0) result |= 0x80000000000000000000000000000000;

        return bytes16 (uint128 (result));
      }
    }
  }

  /**
   * Convert quadruple precision number into signed 128.128 bit fixed point
   * number.  Revert on overflow.
   *
   * @param x quadruple precision number
   * @return signed 128.128 bit fixed point number
   */
  function to128x128 (bytes16 x) internal pure returns (int256) {
    unchecked {
      uint256 exponent = uint128 (x) >> 112 & 0x7FFF;

      require (exponent <= 16510); // Overflow
      if (exponent < 16255) return 0; // Underflow

      uint256 result = uint256 (uint128 (x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
        0x10000000000000000000000000000;

      if (exponent < 16367) result >>= 16367 - exponent;
      else if (exponent > 16367) result <<= exponent - 16367;

      if (uint128 (x) >= 0x80000000000000000000000000000000) { // Negative
        require (result <= 0x8000000000000000000000000000000000000000000000000000000000000000);
        return -int256 (result); // We rely on overflow behavior here
      } else {
        require (result <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
        return int256 (result);
      }
    }
  }

  /**
   * Convert signed 64.64 bit fixed point number into quadruple precision
   * number.
   *
   * @param x signed 64.64 bit fixed point number
   * @return quadruple precision number
   */
  function from64x64 (int128 x) internal pure returns (bytes16) {
    unchecked {
      if (x == 0) return bytes16 (0);
      else {
        // We rely on overflow behavior here
        uint256 result = uint128 (x > 0 ? x : -x);

        uint256 msb = mostSignificantBit (result);
        if (msb < 112) result <<= 112 - msb;
        else if (msb > 112) result >>= msb - 112;

        result = result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 16319 + msb << 112;
        if (x < 0) result |= 0x80000000000000000000000000000000;

        return bytes16 (uint128 (result));
      }
    }
  }

  /**
   * Convert quadruple precision number into signed 64.64 bit fixed point
   * number.  Revert on overflow.
   *
   * @param x quadruple precision number
   * @return signed 64.64 bit fixed point number
   */
  function to64x64 (bytes16 x) internal pure returns (int128) {
    unchecked {
      uint256 exponent = uint128 (x) >> 112 & 0x7FFF;

      require (exponent <= 16446); // Overflow
      if (exponent < 16319) return 0; // Underflow

      uint256 result = uint256 (uint128 (x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
        0x10000000000000000000000000000;

      if (exponent < 16431) result >>= 16431 - exponent;
      else if (exponent > 16431) result <<= exponent - 16431;

      if (uint128 (x) >= 0x80000000000000000000000000000000) { // Negative
        require (result <= 0x80000000000000000000000000000000);
        return -int128 (int256 (result)); // We rely on overflow behavior here
      } else {
        require (result <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
        return int128 (int256 (result));
      }
    }
  }

  /**
   * Convert octuple precision number into quadruple precision number.
   *
   * @param x octuple precision number
   * @return quadruple precision number
   */
  function fromOctuple (bytes32 x) internal pure returns (bytes16) {
    unchecked {
      bool negative = x & 0x8000000000000000000000000000000000000000000000000000000000000000 > 0;

      uint256 exponent = uint256 (x) >> 236 & 0x7FFFF;
      uint256 significand = uint256 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

      if (exponent == 0x7FFFF) {
        if (significand > 0) return NaN;
        else return negative ? NEGATIVE_INFINITY : POSITIVE_INFINITY;
      }

      if (exponent > 278526)
        return negative ? NEGATIVE_INFINITY : POSITIVE_INFINITY;
      else if (exponent < 245649)
        return negative ? NEGATIVE_ZERO : POSITIVE_ZERO;
      else if (exponent < 245761) {
        significand = (significand | 0x100000000000000000000000000000000000000000000000000000000000) >> 245885 - exponent;
        exponent = 0;
      } else {
        significand >>= 124;
        exponent -= 245760;
      }

      uint128 result = uint128 (significand | exponent << 112);
      if (negative) result |= 0x80000000000000000000000000000000;

      return bytes16 (result);
    }
  }

  /**
   * Convert quadruple precision number into octuple precision number.
   *
   * @param x quadruple precision number
   * @return octuple precision number
   */
  function toOctuple (bytes16 x) internal pure returns (bytes32) {
    unchecked {
      uint256 exponent = uint128 (x) >> 112 & 0x7FFF;

      uint256 result = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

      if (exponent == 0x7FFF) exponent = 0x7FFFF; // Infinity or NaN
      else if (exponent == 0) {
        if (result > 0) {
          uint256 msb = mostSignificantBit (result);
          result = result << 236 - msb & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
          exponent = 245649 + msb;
        }
      } else {
        result <<= 124;
        exponent += 245760;
      }

      result |= exponent << 236;
      if (uint128 (x) >= 0x80000000000000000000000000000000)
        result |= 0x8000000000000000000000000000000000000000000000000000000000000000;

      return bytes32 (result);
    }
  }

  /**
   * Convert double precision number into quadruple precision number.
   *
   * @param x double precision number
   * @return quadruple precision number
   */
  function fromDouble (bytes8 x) internal pure returns (bytes16) {
    unchecked {
      uint256 exponent = uint64 (x) >> 52 & 0x7FF;

      uint256 result = uint64 (x) & 0xFFFFFFFFFFFFF;

      if (exponent == 0x7FF) exponent = 0x7FFF; // Infinity or NaN
      else if (exponent == 0) {
        if (result > 0) {
          uint256 msb = mostSignificantBit (result);
          result = result << 112 - msb & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
          exponent = 15309 + msb;
        }
      } else {
        result <<= 60;
        exponent += 15360;
      }

      result |= exponent << 112;
      if (x & 0x8000000000000000 > 0)
        result |= 0x80000000000000000000000000000000;

      return bytes16 (uint128 (result));
    }
  }

  /**
   * Convert quadruple precision number into double precision number.
   *
   * @param x quadruple precision number
   * @return double precision number
   */
  function toDouble (bytes16 x) internal pure returns (bytes8) {
    unchecked {
      bool negative = uint128 (x) >= 0x80000000000000000000000000000000;

      uint256 exponent = uint128 (x) >> 112 & 0x7FFF;
      uint256 significand = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

      if (exponent == 0x7FFF) {
        if (significand > 0) return 0x7FF8000000000000; // NaN
        else return negative ?
            bytes8 (0xFFF0000000000000) : // -Infinity
            bytes8 (0x7FF0000000000000); // Infinity
      }

      if (exponent > 17406)
        return negative ?
            bytes8 (0xFFF0000000000000) : // -Infinity
            bytes8 (0x7FF0000000000000); // Infinity
      else if (exponent < 15309)
        return negative ?
            bytes8 (0x8000000000000000) : // -0
            bytes8 (0x0000000000000000); // 0
      else if (exponent < 15361) {
        significand = (significand | 0x10000000000000000000000000000) >> 15421 - exponent;
        exponent = 0;
      } else {
        significand >>= 60;
        exponent -= 15360;
      }

      uint64 result = uint64 (significand | exponent << 52);
      if (negative) result |= 0x8000000000000000;

      return bytes8 (result);
    }
  }

  /**
   * Test whether given quadruple precision number is NaN.
   *
   * @param x quadruple precision number
   * @return true if x is NaN, false otherwise
   */
  function isNaN (bytes16 x) internal pure returns (bool) {
    unchecked {
      return uint128 (x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF >
        0x7FFF0000000000000000000000000000;
    }
  }

  /**
   * Test whether given quadruple precision number is positive or negative
   * infinity.
   *
   * @param x quadruple precision number
   * @return true if x is positive or negative infinity, false otherwise
   */
  function isInfinity (bytes16 x) internal pure returns (bool) {
    unchecked {
      return uint128 (x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ==
        0x7FFF0000000000000000000000000000;
    }
  }

  /**
   * Calculate sign of x, i.e. -1 if x is negative, 0 if x if zero, and 1 if x
   * is positive.  Note that sign (-0) is zero.  Revert if x is NaN. 
   *
   * @param x quadruple precision number
   * @return sign of x
   */
  function sign (bytes16 x) internal pure returns (int8) {
    unchecked {
      uint128 absoluteX = uint128 (x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

      require (absoluteX <= 0x7FFF0000000000000000000000000000); // Not NaN

      if (absoluteX == 0) return 0;
      else if (uint128 (x) >= 0x80000000000000000000000000000000) return -1;
      else return 1;
    }
  }

  /**
   * Calculate sign (x - y).  Revert if either argument is NaN, or both
   * arguments are infinities of the same sign. 
   *
   * @param x quadruple precision number
   * @param y quadruple precision number
   * @return sign (x - y)
   */
  function cmp (bytes16 x, bytes16 y) internal pure returns (int8) {
    unchecked {
      uint128 absoluteX = uint128 (x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

      require (absoluteX <= 0x7FFF0000000000000000000000000000); // Not NaN

      uint128 absoluteY = uint128 (y) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

      require (absoluteY <= 0x7FFF0000000000000000000000000000); // Not NaN

      // Not infinities of the same sign
      require (x != y || absoluteX < 0x7FFF0000000000000000000000000000);

      if (x == y) return 0;
      else {
        bool negativeX = uint128 (x) >= 0x80000000000000000000000000000000;
        bool negativeY = uint128 (y) >= 0x80000000000000000000000000000000;

        if (negativeX) {
          if (negativeY) return absoluteX > absoluteY ? -1 : int8 (1);
          else return -1; 
        } else {
          if (negativeY) return 1;
          else return absoluteX > absoluteY ? int8 (1) : -1;
        }
      }
    }
  }

  /**
   * Test whether x equals y.  NaN, infinity, and -infinity are not equal to
   * anything. 
   *
   * @param x quadruple precision number
   * @param y quadruple precision number
   * @return true if x equals to y, false otherwise
   */
  function eq (bytes16 x, bytes16 y) internal pure returns (bool) {
    unchecked {
      if (x == y) {
        return uint128 (x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF <
          0x7FFF0000000000000000000000000000;
      } else return false;
    }
  }

  /**
   * Calculate x + y.  Special values behave in the following way:
   *
   * NaN + x = NaN for any x.
   * Infinity + x = Infinity for any finite x.
   * -Infinity + x = -Infinity for any finite x.
   * Infinity + Infinity = Infinity.
   * -Infinity + -Infinity = -Infinity.
   * Infinity + -Infinity = -Infinity + Infinity = NaN.
   *
   * @param x quadruple precision number
   * @param y quadruple precision number
   * @return quadruple precision number
   */
  function add (bytes16 x, bytes16 y) internal pure returns (bytes16) {
    unchecked {
      uint256 xExponent = uint128 (x) >> 112 & 0x7FFF;
      uint256 yExponent = uint128 (y) >> 112 & 0x7FFF;

      if (xExponent == 0x7FFF) {
        if (yExponent == 0x7FFF) { 
          if (x == y) return x;
          else return NaN;
        } else return x; 
      } else if (yExponent == 0x7FFF) return y;
      else {
        bool xSign = uint128 (x) >= 0x80000000000000000000000000000000;
        uint256 xSignifier = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        if (xExponent == 0) xExponent = 1;
        else xSignifier |= 0x10000000000000000000000000000;

        bool ySign = uint128 (y) >= 0x80000000000000000000000000000000;
        uint256 ySignifier = uint128 (y) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        if (yExponent == 0) yExponent = 1;
        else ySignifier |= 0x10000000000000000000000000000;

        if (xSignifier == 0) return y == NEGATIVE_ZERO ? POSITIVE_ZERO : y;
        else if (ySignifier == 0) return x == NEGATIVE_ZERO ? POSITIVE_ZERO : x;
        else {
          int256 delta = int256 (xExponent) - int256 (yExponent);
  
          if (xSign == ySign) {
            if (delta > 112) return x;
            else if (delta > 0) ySignifier >>= uint256 (delta);
            else if (delta < -112) return y;
            else if (delta < 0) {
              xSignifier >>= uint256 (-delta);
              xExponent = yExponent;
            }
  
            xSignifier += ySignifier;
  
            if (xSignifier >= 0x20000000000000000000000000000) {
              xSignifier >>= 1;
              xExponent += 1;
            }
  
            if (xExponent == 0x7FFF)
              return xSign ? NEGATIVE_INFINITY : POSITIVE_INFINITY;
            else {
              if (xSignifier < 0x10000000000000000000000000000) xExponent = 0;
              else xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
  
              return bytes16 (uint128 (
                  (xSign ? 0x80000000000000000000000000000000 : 0) |
                  (xExponent << 112) |
                  xSignifier)); 
            }
          } else {
            if (delta > 0) {
              xSignifier <<= 1;
              xExponent -= 1;
            } else if (delta < 0) {
              ySignifier <<= 1;
              xExponent = yExponent - 1;
            }

            if (delta > 112) ySignifier = 1;
            else if (delta > 1) ySignifier = (ySignifier - 1 >> uint256 (delta - 1)) + 1;
            else if (delta < -112) xSignifier = 1;
            else if (delta < -1) xSignifier = (xSignifier - 1 >> uint256 (-delta - 1)) + 1;

            if (xSignifier >= ySignifier) xSignifier -= ySignifier;
            else {
              xSignifier = ySignifier - xSignifier;
              xSign = ySign;
            }

            if (xSignifier == 0)
              return POSITIVE_ZERO;

            uint256 msb = mostSignificantBit (xSignifier);

            if (msb == 113) {
              xSignifier = xSignifier >> 1 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
              xExponent += 1;
            } else if (msb < 112) {
              uint256 shift = 112 - msb;
              if (xExponent > shift) {
                xSignifier = xSignifier << shift & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
                xExponent -= shift;
              } else {
                xSignifier <<= xExponent - 1;
                xExponent = 0;
              }
            } else xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

            if (xExponent == 0x7FFF)
              return xSign ? NEGATIVE_INFINITY : POSITIVE_INFINITY;
            else return bytes16 (uint128 (
                (xSign ? 0x80000000000000000000000000000000 : 0) |
                (xExponent << 112) |
                xSignifier));
          }
        }
      }
    }
  }

  /**
   * Calculate x - y.  Special values behave in the following way:
   *
   * NaN - x = NaN for any x.
   * Infinity - x = Infinity for any finite x.
   * -Infinity - x = -Infinity for any finite x.
   * Infinity - -Infinity = Infinity.
   * -Infinity - Infinity = -Infinity.
   * Infinity - Infinity = -Infinity - -Infinity = NaN.
   *
   * @param x quadruple precision number
   * @param y quadruple precision number
   * @return quadruple precision number
   */
  function sub (bytes16 x, bytes16 y) internal pure returns (bytes16) {
    unchecked {
      return add (x, y ^ 0x80000000000000000000000000000000);
    }
  }

  /**
   * Calculate x * y.  Special values behave in the following way:
   *
   * NaN * x = NaN for any x.
   * Infinity * x = Infinity for any finite positive x.
   * Infinity * x = -Infinity for any finite negative x.
   * -Infinity * x = -Infinity for any finite positive x.
   * -Infinity * x = Infinity for any finite negative x.
   * Infinity * 0 = NaN.
   * -Infinity * 0 = NaN.
   * Infinity * Infinity = Infinity.
   * Infinity * -Infinity = -Infinity.
   * -Infinity * Infinity = -Infinity.
   * -Infinity * -Infinity = Infinity.
   *
   * @param x quadruple precision number
   * @param y quadruple precision number
   * @return quadruple precision number
   */
  function mul (bytes16 x, bytes16 y) internal pure returns (bytes16) {
    unchecked {
      uint256 xExponent = uint128 (x) >> 112 & 0x7FFF;
      uint256 yExponent = uint128 (y) >> 112 & 0x7FFF;

      if (xExponent == 0x7FFF) {
        if (yExponent == 0x7FFF) {
          if (x == y) return x ^ y & 0x80000000000000000000000000000000;
          else if (x ^ y == 0x80000000000000000000000000000000) return x | y;
          else return NaN;
        } else {
          if (y & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0) return NaN;
          else return x ^ y & 0x80000000000000000000000000000000;
        }
      } else if (yExponent == 0x7FFF) {
          if (x & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0) return NaN;
          else return y ^ x & 0x80000000000000000000000000000000;
      } else {
        uint256 xSignifier = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        if (xExponent == 0) xExponent = 1;
        else xSignifier |= 0x10000000000000000000000000000;

        uint256 ySignifier = uint128 (y) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        if (yExponent == 0) yExponent = 1;
        else ySignifier |= 0x10000000000000000000000000000;

        xSignifier *= ySignifier;
        if (xSignifier == 0)
          return (x ^ y) & 0x80000000000000000000000000000000 > 0 ?
              NEGATIVE_ZERO : POSITIVE_ZERO;

        xExponent += yExponent;

        uint256 msb =
          xSignifier >= 0x200000000000000000000000000000000000000000000000000000000 ? 225 :
          xSignifier >= 0x100000000000000000000000000000000000000000000000000000000 ? 224 :
          mostSignificantBit (xSignifier);

        if (xExponent + msb < 16496) { // Underflow
          xExponent = 0;
          xSignifier = 0;
        } else if (xExponent + msb < 16608) { // Subnormal
          if (xExponent < 16496)
            xSignifier >>= 16496 - xExponent;
          else if (xExponent > 16496)
            xSignifier <<= xExponent - 16496;
          xExponent = 0;
        } else if (xExponent + msb > 49373) {
          xExponent = 0x7FFF;
          xSignifier = 0;
        } else {
          if (msb > 112)
            xSignifier >>= msb - 112;
          else if (msb < 112)
            xSignifier <<= 112 - msb;

          xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

          xExponent = xExponent + msb - 16607;
        }

        return bytes16 (uint128 (uint128 ((x ^ y) & 0x80000000000000000000000000000000) |
            xExponent << 112 | xSignifier));
      }
    }
  }

  /**
   * Calculate x / y.  Special values behave in the following way:
   *
   * NaN / x = NaN for any x.
   * x / NaN = NaN for any x.
   * Infinity / x = Infinity for any finite non-negative x.
   * Infinity / x = -Infinity for any finite negative x including -0.
   * -Infinity / x = -Infinity for any finite non-negative x.
   * -Infinity / x = Infinity for any finite negative x including -0.
   * x / Infinity = 0 for any finite non-negative x.
   * x / -Infinity = -0 for any finite non-negative x.
   * x / Infinity = -0 for any finite non-negative x including -0.
   * x / -Infinity = 0 for any finite non-negative x including -0.
   * 
   * Infinity / Infinity = NaN.
   * Infinity / -Infinity = -NaN.
   * -Infinity / Infinity = -NaN.
   * -Infinity / -Infinity = NaN.
   *
   * Division by zero behaves in the following way:
   *
   * x / 0 = Infinity for any finite positive x.
   * x / -0 = -Infinity for any finite positive x.
   * x / 0 = -Infinity for any finite negative x.
   * x / -0 = Infinity for any finite negative x.
   * 0 / 0 = NaN.
   * 0 / -0 = NaN.
   * -0 / 0 = NaN.
   * -0 / -0 = NaN.
   *
   * @param x quadruple precision number
   * @param y quadruple precision number
   * @return quadruple precision number
   */
  function div (bytes16 x, bytes16 y) internal pure returns (bytes16) {
    unchecked {
      uint256 xExponent = uint128 (x) >> 112 & 0x7FFF;
      uint256 yExponent = uint128 (y) >> 112 & 0x7FFF;

      if (xExponent == 0x7FFF) {
        if (yExponent == 0x7FFF) return NaN;
        else return x ^ y & 0x80000000000000000000000000000000;
      } else if (yExponent == 0x7FFF) {
        if (y & 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFF != 0) return NaN;
        else return POSITIVE_ZERO | (x ^ y) & 0x80000000000000000000000000000000;
      } else if (y & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0) {
        if (x & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0) return NaN;
        else return POSITIVE_INFINITY | (x ^ y) & 0x80000000000000000000000000000000;
      } else {
        uint256 ySignifier = uint128 (y) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        if (yExponent == 0) yExponent = 1;
        else ySignifier |= 0x10000000000000000000000000000;

        uint256 xSignifier = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        if (xExponent == 0) {
          if (xSignifier != 0) {
            uint shift = 226 - mostSignificantBit (xSignifier);

            xSignifier <<= shift;

            xExponent = 1;
            yExponent += shift - 114;
          }
        }
        else {
          xSignifier = (xSignifier | 0x10000000000000000000000000000) << 114;
        }

        xSignifier = xSignifier / ySignifier;
        if (xSignifier == 0)
          return (x ^ y) & 0x80000000000000000000000000000000 > 0 ?
              NEGATIVE_ZERO : POSITIVE_ZERO;

        assert (xSignifier >= 0x1000000000000000000000000000);

        uint256 msb =
          xSignifier >= 0x80000000000000000000000000000 ? mostSignificantBit (xSignifier) :
          xSignifier >= 0x40000000000000000000000000000 ? 114 :
          xSignifier >= 0x20000000000000000000000000000 ? 113 : 112;

        if (xExponent + msb > yExponent + 16497) { // Overflow
          xExponent = 0x7FFF;
          xSignifier = 0;
        } else if (xExponent + msb + 16380  < yExponent) { // Underflow
          xExponent = 0;
          xSignifier = 0;
        } else if (xExponent + msb + 16268  < yExponent) { // Subnormal
          if (xExponent + 16380 > yExponent)
            xSignifier <<= xExponent + 16380 - yExponent;
          else if (xExponent + 16380 < yExponent)
            xSignifier >>= yExponent - xExponent - 16380;

          xExponent = 0;
        } else { // Normal
          if (msb > 112)
            xSignifier >>= msb - 112;

          xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

          xExponent = xExponent + msb + 16269 - yExponent;
        }

        return bytes16 (uint128 (uint128 ((x ^ y) & 0x80000000000000000000000000000000) |
            xExponent << 112 | xSignifier));
      }
    }
  }

  /**
   * Calculate -x.
   *
   * @param x quadruple precision number
   * @return quadruple precision number
   */
  function neg (bytes16 x) internal pure returns (bytes16) {
    unchecked {
      return x ^ 0x80000000000000000000000000000000;
    }
  }

  /**
   * Calculate |x|.
   *
   * @param x quadruple precision number
   * @return quadruple precision number
   */
  function abs (bytes16 x) internal pure returns (bytes16) {
    unchecked {
      return x & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
    }
  }

  /**
   * Calculate square root of x.  Return NaN on negative x excluding -0.
   *
   * @param x quadruple precision number
   * @return quadruple precision number
   */
  function sqrt (bytes16 x) internal pure returns (bytes16) {
    unchecked {
      if (uint128 (x) >  0x80000000000000000000000000000000) return NaN;
      else {
        uint256 xExponent = uint128 (x) >> 112 & 0x7FFF;
        if (xExponent == 0x7FFF) return x;
        else {
          uint256 xSignifier = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
          if (xExponent == 0) xExponent = 1;
          else xSignifier |= 0x10000000000000000000000000000;

          if (xSignifier == 0) return POSITIVE_ZERO;

          bool oddExponent = xExponent & 0x1 == 0;
          xExponent = xExponent + 16383 >> 1;

          if (oddExponent) {
            if (xSignifier >= 0x10000000000000000000000000000)
              xSignifier <<= 113;
            else {
              uint256 msb = mostSignificantBit (xSignifier);
              uint256 shift = (226 - msb) & 0xFE;
              xSignifier <<= shift;
              xExponent -= shift - 112 >> 1;
            }
          } else {
            if (xSignifier >= 0x10000000000000000000000000000)
              xSignifier <<= 112;
            else {
              uint256 msb = mostSignificantBit (xSignifier);
              uint256 shift = (225 - msb) & 0xFE;
              xSignifier <<= shift;
              xExponent -= shift - 112 >> 1;
            }
          }

          uint256 r = 0x10000000000000000000000000000;
          r = (r + xSignifier / r) >> 1;
          r = (r + xSignifier / r) >> 1;
          r = (r + xSignifier / r) >> 1;
          r = (r + xSignifier / r) >> 1;
          r = (r + xSignifier / r) >> 1;
          r = (r + xSignifier / r) >> 1;
          r = (r + xSignifier / r) >> 1; // Seven iterations should be enough
          uint256 r1 = xSignifier / r;
          if (r1 < r) r = r1;

          return bytes16 (uint128 (xExponent << 112 | r & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF));
        }
      }
    }
  }

  /**
   * Calculate binary logarithm of x.  Return NaN on negative x excluding -0.
   *
   * @param x quadruple precision number
   * @return quadruple precision number
   */
  function log_2 (bytes16 x) internal pure returns (bytes16) {
    unchecked {
      if (uint128 (x) > 0x80000000000000000000000000000000) return NaN;
      else if (x == 0x3FFF0000000000000000000000000000) return POSITIVE_ZERO; 
      else {
        uint256 xExponent = uint128 (x) >> 112 & 0x7FFF;
        if (xExponent == 0x7FFF) return x;
        else {
          uint256 xSignifier = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
          if (xExponent == 0) xExponent = 1;
          else xSignifier |= 0x10000000000000000000000000000;

          if (xSignifier == 0) return NEGATIVE_INFINITY;

          bool resultNegative;
          uint256 resultExponent = 16495;
          uint256 resultSignifier;

          if (xExponent >= 0x3FFF) {
            resultNegative = false;
            resultSignifier = xExponent - 0x3FFF;
            xSignifier <<= 15;
          } else {
            resultNegative = true;
            if (xSignifier >= 0x10000000000000000000000000000) {
              resultSignifier = 0x3FFE - xExponent;
              xSignifier <<= 15;
            } else {
              uint256 msb = mostSignificantBit (xSignifier);
              resultSignifier = 16493 - msb;
              xSignifier <<= 127 - msb;
            }
          }

          if (xSignifier == 0x80000000000000000000000000000000) {
            if (resultNegative) resultSignifier += 1;
            uint256 shift = 112 - mostSignificantBit (resultSignifier);
            resultSignifier <<= shift;
            resultExponent -= shift;
          } else {
            uint256 bb = resultNegative ? 1 : 0;
            while (resultSignifier < 0x10000000000000000000000000000) {
              resultSignifier <<= 1;
              resultExponent -= 1;
  
              xSignifier *= xSignifier;
              uint256 b = xSignifier >> 255;
              resultSignifier += b ^ bb;
              xSignifier >>= 127 + b;
            }
          }

          return bytes16 (uint128 ((resultNegative ? 0x80000000000000000000000000000000 : 0) |
              resultExponent << 112 | resultSignifier & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF));
        }
      }
    }
  }

  /**
   * Calculate natural logarithm of x.  Return NaN on negative x excluding -0.
   *
   * @param x quadruple precision number
   * @return quadruple precision number
   */
  function ln (bytes16 x) internal pure returns (bytes16) {
    unchecked {
      return mul (log_2 (x), 0x3FFE62E42FEFA39EF35793C7673007E5);
    }
  }

  /**
   * Calculate 2^x.
   *
   * @param x quadruple precision number
   * @return quadruple precision number
   */
  function pow_2 (bytes16 x) internal pure returns (bytes16) {
    unchecked {
      bool xNegative = uint128 (x) > 0x80000000000000000000000000000000;
      uint256 xExponent = uint128 (x) >> 112 & 0x7FFF;
      uint256 xSignifier = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

      if (xExponent == 0x7FFF && xSignifier != 0) return NaN;
      else if (xExponent > 16397)
        return xNegative ? POSITIVE_ZERO : POSITIVE_INFINITY;
      else if (xExponent < 16255)
        return 0x3FFF0000000000000000000000000000;
      else {
        if (xExponent == 0) xExponent = 1;
        else xSignifier |= 0x10000000000000000000000000000;

        if (xExponent > 16367)
          xSignifier <<= xExponent - 16367;
        else if (xExponent < 16367)
          xSignifier >>= 16367 - xExponent;

        if (xNegative && xSignifier > 0x406E00000000000000000000000000000000)
          return POSITIVE_ZERO;

        if (!xNegative && xSignifier > 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
          return POSITIVE_INFINITY;

        uint256 resultExponent = xSignifier >> 128;
        xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        if (xNegative && xSignifier != 0) {
          xSignifier = ~xSignifier;
          resultExponent += 1;
        }

        uint256 resultSignifier = 0x80000000000000000000000000000000;
        if (xSignifier & 0x80000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x16A09E667F3BCC908B2FB1366EA957D3E >> 128;
        if (xSignifier & 0x40000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1306FE0A31B7152DE8D5A46305C85EDEC >> 128;
        if (xSignifier & 0x20000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1172B83C7D517ADCDF7C8C50EB14A791F >> 128;
        if (xSignifier & 0x10000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10B5586CF9890F6298B92B71842A98363 >> 128;
        if (xSignifier & 0x8000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1059B0D31585743AE7C548EB68CA417FD >> 128;
        if (xSignifier & 0x4000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x102C9A3E778060EE6F7CACA4F7A29BDE8 >> 128;
        if (xSignifier & 0x2000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10163DA9FB33356D84A66AE336DCDFA3F >> 128;
        if (xSignifier & 0x1000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100B1AFA5ABCBED6129AB13EC11DC9543 >> 128;
        if (xSignifier & 0x800000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10058C86DA1C09EA1FF19D294CF2F679B >> 128;
        if (xSignifier & 0x400000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1002C605E2E8CEC506D21BFC89A23A00F >> 128;
        if (xSignifier & 0x200000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100162F3904051FA128BCA9C55C31E5DF >> 128;
        if (xSignifier & 0x100000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000B175EFFDC76BA38E31671CA939725 >> 128;
        if (xSignifier & 0x80000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100058BA01FB9F96D6CACD4B180917C3D >> 128;
        if (xSignifier & 0x40000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10002C5CC37DA9491D0985C348C68E7B3 >> 128;
        if (xSignifier & 0x20000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000162E525EE054754457D5995292026 >> 128;
        if (xSignifier & 0x10000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000B17255775C040618BF4A4ADE83FC >> 128;
        if (xSignifier & 0x8000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000058B91B5BC9AE2EED81E9B7D4CFAB >> 128;
        if (xSignifier & 0x4000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100002C5C89D5EC6CA4D7C8ACC017B7C9 >> 128;
        if (xSignifier & 0x2000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000162E43F4F831060E02D839A9D16D >> 128;
        if (xSignifier & 0x1000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000B1721BCFC99D9F890EA06911763 >> 128;
        if (xSignifier & 0x800000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000058B90CF1E6D97F9CA14DBCC1628 >> 128;
        if (xSignifier & 0x400000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000002C5C863B73F016468F6BAC5CA2B >> 128;
        if (xSignifier & 0x200000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000162E430E5A18F6119E3C02282A5 >> 128;
        if (xSignifier & 0x100000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000B1721835514B86E6D96EFD1BFE >> 128;
        if (xSignifier & 0x80000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000058B90C0B48C6BE5DF846C5B2EF >> 128;
        if (xSignifier & 0x40000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000002C5C8601CC6B9E94213C72737A >> 128;
        if (xSignifier & 0x20000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000162E42FFF037DF38AA2B219F06 >> 128;
        if (xSignifier & 0x10000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000B17217FBA9C739AA5819F44F9 >> 128;
        if (xSignifier & 0x8000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000058B90BFCDEE5ACD3C1CEDC823 >> 128;
        if (xSignifier & 0x4000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000002C5C85FE31F35A6A30DA1BE50 >> 128;
        if (xSignifier & 0x2000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000162E42FF0999CE3541B9FFFCF >> 128;
        if (xSignifier & 0x1000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000B17217F80F4EF5AADDA45554 >> 128;
        if (xSignifier & 0x800000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000058B90BFBF8479BD5A81B51AD >> 128;
        if (xSignifier & 0x400000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000002C5C85FDF84BD62AE30A74CC >> 128;
        if (xSignifier & 0x200000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000162E42FEFB2FED257559BDAA >> 128;
        if (xSignifier & 0x100000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000B17217F7D5A7716BBA4A9AE >> 128;
        if (xSignifier & 0x80000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000058B90BFBE9DDBAC5E109CCE >> 128;
        if (xSignifier & 0x40000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000002C5C85FDF4B15DE6F17EB0D >> 128;
        if (xSignifier & 0x20000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000162E42FEFA494F1478FDE05 >> 128;
        if (xSignifier & 0x10000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000B17217F7D20CF927C8E94C >> 128;
        if (xSignifier & 0x8000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000058B90BFBE8F71CB4E4B33D >> 128;
        if (xSignifier & 0x4000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000002C5C85FDF477B662B26945 >> 128;
        if (xSignifier & 0x2000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000162E42FEFA3AE53369388C >> 128;
        if (xSignifier & 0x1000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000B17217F7D1D351A389D40 >> 128;
        if (xSignifier & 0x800000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000058B90BFBE8E8B2D3D4EDE >> 128;
        if (xSignifier & 0x400000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000002C5C85FDF4741BEA6E77E >> 128;
        if (xSignifier & 0x200000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000162E42FEFA39FE95583C2 >> 128;
        if (xSignifier & 0x100000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000B17217F7D1CFB72B45E1 >> 128;
        if (xSignifier & 0x80000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000058B90BFBE8E7CC35C3F0 >> 128;
        if (xSignifier & 0x40000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000002C5C85FDF473E242EA38 >> 128;
        if (xSignifier & 0x20000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000162E42FEFA39F02B772C >> 128;
        if (xSignifier & 0x10000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000B17217F7D1CF7D83C1A >> 128;
        if (xSignifier & 0x8000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000058B90BFBE8E7BDCBE2E >> 128;
        if (xSignifier & 0x4000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000002C5C85FDF473DEA871F >> 128;
        if (xSignifier & 0x2000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000162E42FEFA39EF44D91 >> 128;
        if (xSignifier & 0x1000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000B17217F7D1CF79E949 >> 128;
        if (xSignifier & 0x800000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000058B90BFBE8E7BCE544 >> 128;
        if (xSignifier & 0x400000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000002C5C85FDF473DE6ECA >> 128;
        if (xSignifier & 0x200000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000162E42FEFA39EF366F >> 128;
        if (xSignifier & 0x100000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000B17217F7D1CF79AFA >> 128;
        if (xSignifier & 0x80000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000058B90BFBE8E7BCD6D >> 128;
        if (xSignifier & 0x40000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000002C5C85FDF473DE6B2 >> 128;
        if (xSignifier & 0x20000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000162E42FEFA39EF358 >> 128;
        if (xSignifier & 0x10000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000B17217F7D1CF79AB >> 128;
        if (xSignifier & 0x8000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000058B90BFBE8E7BCD5 >> 128;
        if (xSignifier & 0x4000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000002C5C85FDF473DE6A >> 128;
        if (xSignifier & 0x2000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000162E42FEFA39EF34 >> 128;
        if (xSignifier & 0x1000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000B17217F7D1CF799 >> 128;
        if (xSignifier & 0x800000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000058B90BFBE8E7BCC >> 128;
        if (xSignifier & 0x400000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000002C5C85FDF473DE5 >> 128;
        if (xSignifier & 0x200000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000162E42FEFA39EF2 >> 128;
        if (xSignifier & 0x100000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000B17217F7D1CF78 >> 128;
        if (xSignifier & 0x80000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000058B90BFBE8E7BB >> 128;
        if (xSignifier & 0x40000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000002C5C85FDF473DD >> 128;
        if (xSignifier & 0x20000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000162E42FEFA39EE >> 128;
        if (xSignifier & 0x10000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000B17217F7D1CF6 >> 128;
        if (xSignifier & 0x8000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000058B90BFBE8E7A >> 128;
        if (xSignifier & 0x4000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000002C5C85FDF473C >> 128;
        if (xSignifier & 0x2000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000162E42FEFA39D >> 128;
        if (xSignifier & 0x1000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000B17217F7D1CE >> 128;
        if (xSignifier & 0x800000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000058B90BFBE8E6 >> 128;
        if (xSignifier & 0x400000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000002C5C85FDF472 >> 128;
        if (xSignifier & 0x200000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000162E42FEFA38 >> 128;
        if (xSignifier & 0x100000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000B17217F7D1B >> 128;
        if (xSignifier & 0x80000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000058B90BFBE8D >> 128;
        if (xSignifier & 0x40000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000002C5C85FDF46 >> 128;
        if (xSignifier & 0x20000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000162E42FEFA2 >> 128;
        if (xSignifier & 0x10000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000B17217F7D0 >> 128;
        if (xSignifier & 0x8000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000058B90BFBE7 >> 128;
        if (xSignifier & 0x4000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000002C5C85FDF3 >> 128;
        if (xSignifier & 0x2000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000162E42FEF9 >> 128;
        if (xSignifier & 0x1000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000B17217F7C >> 128;
        if (xSignifier & 0x800000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000058B90BFBD >> 128;
        if (xSignifier & 0x400000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000002C5C85FDE >> 128;
        if (xSignifier & 0x200000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000162E42FEE >> 128;
        if (xSignifier & 0x100000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000B17217F6 >> 128;
        if (xSignifier & 0x80000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000058B90BFA >> 128;
        if (xSignifier & 0x40000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000002C5C85FC >> 128;
        if (xSignifier & 0x20000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000162E42FD >> 128;
        if (xSignifier & 0x10000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000B17217E >> 128;
        if (xSignifier & 0x8000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000058B90BE >> 128;
        if (xSignifier & 0x4000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000002C5C85E >> 128;
        if (xSignifier & 0x2000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000162E42E >> 128;
        if (xSignifier & 0x1000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000B17216 >> 128;
        if (xSignifier & 0x800000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000058B90A >> 128;
        if (xSignifier & 0x400000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000002C5C84 >> 128;
        if (xSignifier & 0x200000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000162E41 >> 128;
        if (xSignifier & 0x100000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000000B1720 >> 128;
        if (xSignifier & 0x80000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000058B8F >> 128;
        if (xSignifier & 0x40000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000002C5C7 >> 128;
        if (xSignifier & 0x20000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000000162E3 >> 128;
        if (xSignifier & 0x10000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000000B171 >> 128;
        if (xSignifier & 0x8000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000000058B8 >> 128;
        if (xSignifier & 0x4000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000002C5B >> 128;
        if (xSignifier & 0x2000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000000162D >> 128;
        if (xSignifier & 0x1000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000000B16 >> 128;
        if (xSignifier & 0x800 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000000058A >> 128;
        if (xSignifier & 0x400 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000000002C4 >> 128;
        if (xSignifier & 0x200 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000000161 >> 128;
        if (xSignifier & 0x100 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000000000B0 >> 128;
        if (xSignifier & 0x80 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000000057 >> 128;
        if (xSignifier & 0x40 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000000002B >> 128;
        if (xSignifier & 0x20 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000000015 >> 128;
        if (xSignifier & 0x10 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000000000A >> 128;
        if (xSignifier & 0x8 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000000004 >> 128;
        if (xSignifier & 0x4 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000000001 >> 128;

        if (!xNegative) {
          resultSignifier = resultSignifier >> 15 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
          resultExponent += 0x3FFF;
        } else if (resultExponent <= 0x3FFE) {
          resultSignifier = resultSignifier >> 15 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
          resultExponent = 0x3FFF - resultExponent;
        } else {
          resultSignifier = resultSignifier >> resultExponent - 16367;
          resultExponent = 0;
        }

        return bytes16 (uint128 (resultExponent << 112 | resultSignifier));
      }
    }
  }

  /**
   * Calculate e^x.
   *
   * @param x quadruple precision number
   * @return quadruple precision number
   */
  function exp (bytes16 x) internal pure returns (bytes16) {
    unchecked {
      return pow_2 (mul (x, 0x3FFF71547652B82FE1777D0FFDA0D23A));
    }
  }

  /**
   * Get index of the most significant non-zero bit in binary representation of
   * x.  Reverts if x is zero.
   *
   * @return index of the most significant non-zero bit in binary representation
   *         of x
   */
  function mostSignificantBit (uint256 x) private pure returns (uint256) {
    unchecked {
      require (x > 0);

      uint256 result = 0;

      if (x >= 0x100000000000000000000000000000000) { x >>= 128; result += 128; }
      if (x >= 0x10000000000000000) { x >>= 64; result += 64; }
      if (x >= 0x100000000) { x >>= 32; result += 32; }
      if (x >= 0x10000) { x >>= 16; result += 16; }
      if (x >= 0x100) { x >>= 8; result += 8; }
      if (x >= 0x10) { x >>= 4; result += 4; }
      if (x >= 0x4) { x >>= 2; result += 2; }
      if (x >= 0x2) result += 1; // No need to shift x anymore

      return result;
    }
  }
}

File 9 of 54 : IDXToken.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { FeeModel } from "../types/CommonTypes.sol";

interface IDXToken is IERC20Upgradeable {
  /**********
   * Events *
   **********/

  /// @notice Emitted when the cooling-off period is updated.
  /// @param oldValue The value of the previous cooling-off period.
  /// @param newValue The value of the current cooling-off period.
  event UpdateCoolingOffPeriod(uint256 indexed oldValue, uint256 indexed newValue);

  /// @notice Emitted when the management fee rate is updated.
  /// @param oldRate The previous management fee rate.
  /// @param newRate The new management fee rate.
  event UpdateManagementFeeRate(uint256 indexed oldRate, uint256 indexed newRate);

  /// @notice Emitted when the funding fee rate is updated.
  /// @param oldRate The previous funding fee rate.
  /// @param newRate The new funding fee rate.
  event UpdateFundingFeeRate(uint256 indexed oldRate, uint256 indexed newRate);

  /// @notice Emitted when the fixed yield amount is updated.
  /// @param oldRate The previous fixed yield amount.
  /// @param newRate The new fixed yield amount.
  event UpdateVariableFundingFeeRate(uint256 indexed oldRate, uint256 indexed newRate);

  /// @notice Emitted when fees are collected.
  /// @param amount The amount of fees collected.
  event FeesCollected(uint256 indexed amount);

  /// @notice Emitted when tokens are minted.
  /// @param to The address receiving the minted tokens.
  /// @param amount The amount of tokens minted.
  event Minted(address indexed to, uint256 indexed amount);

  /// @notice Emitted when tokens are burned.
  /// @param from The address whose tokens are burned.
  /// @param amount The amount of tokens burned.
  event Burned(address indexed from, uint256 indexed amount);

  /// @notice Emitted when the cooling-off period is triggered.
  /// @param account The account triggering the cooling-off period.
  /// @param timestamp The timestamp when triggered.
  event CoolingOffPeriodTriggered(address indexed account, uint256 indexed timestamp);

  /// @notice Emitted when the associated group is updated.
  /// @param groupId The new associated group ID.
  event UpdateGroup(bytes32 indexed groupId);

  /// @notice Emitted when the treasury address is updated.
  /// @param oldTreasury The old treasury address.
  /// @param newTreasury The new treasury address.
  event UpdateTreasuryAddress(address indexed oldTreasury, address indexed newTreasury);

  /// @notice Emitted when the max supply is updated.
  /// @param oldMaxSupply The old max supply.
  /// @param newMaxSupply The new max supply.
  event UpdateMaxSupply(uint256 indexed oldMaxSupply, uint256 indexed newMaxSupply);

  /**********
   * Errors *
   **********/

  /// @dev Thrown when an address is zero.
  error ZeroAddress();

  /// @dev Thrown when decimals are invalid.
  error InvalidDecimals();

  /// @dev Thrown when the cooling-off period is too large.
  error CoolingOffPeriodTooLarge();

  /// @dev Thrown when trying to mint or burn an amount below the minimum.
  error ErrorMinimumMintingAmount();

  /// @dev Thrown when trying to burn an amount below the minimum.
  error ErrorMinimumBurningAmount();

  /// @dev Thrown when minting exceeds the max supply.
  error ExceedsMaxSupply();

  /// @dev Thrown when cooling-off period is active.
  error CoolingOffPeriodActive();

  /// @dev Thrown when parameter is unchanged.
  error ParameterUnchanged();

  /// @dev Thrown when management fee is invalid.
  error InvalidManagementFee();

  /// @dev Thrown when funding fee rate is invalid.
  error InvalidFundingFeeRate();

  /// @dev Thrown when fixed yield amount is invalid.
  error InvalidFixedYieldAmount();

  /// @dev Thrown when fee model is invalid.
  error InvalidFeeModel();

  /// @dev Thrown when base supply is zero.
  error BaseSupplyZero();

  /// @dev Thrown when transfer amount exceeds balance.
  error TransferAmountExceedsBalance();

  /// @dev Thrown when burn amount exceeds balance.
  error BurnAmountExceedsBalance();

  /// @dev Thrown cannot recover token.
  error CannotRecoverToken();

  /// @dev Thrown when invalid base token price.
  error InvalidBaseTokenPrice();

  /// @dev Thrown when an operation is not permitted.
  error NotPermitted();

  /*************************
   * Public View Functions *
   *************************/

  /// @notice Returns the NAV of the token.
  function nav() external view returns (uint256);

  /// @notice Converts DXToken amount to base token amount.
  function dxTokenToBaseToken(uint256 dxTokenAmount) external view returns (uint256 baseTokenAmount);

  /// @notice Returns the fee model.
  function getFeeModel() external view returns (FeeModel);

  /****************************
   * Public Mutated Functions *
   ****************************/

  /// @notice Mints tokens to a specified address.
  function mint(address _to, uint256 _amount) external;

  /// @notice Burns tokens from a specified address.
  function burn(address _from, uint256 _amount) external;

  /// @notice Collects accumulated fees.
  function collectFees() external;
}

File 10 of 54 : GroupId.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

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

type GroupId is bytes32;

// @notice library for computing the ID of a group
library GroupIdLibrary {
  using GroupIdLibrary for GroupId;
  function toId(GroupKey memory groupKey) internal pure returns (GroupId groupId) {
    groupId = GroupId.wrap(keccak256(abi.encode(groupKey)));
  }
  
}

File 11 of 54 : DDXToken.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { GroupId } from "../types/GroupId.sol";
import { FeeModel } from "../types/CommonTypes.sol";

library DDXToken {
  struct TokenParams {
    string name;
    string symbol;
    
    address aTokenAddress;
    address treasuryAddress;

    GroupId groupId;

    uint256 maxSupply;
    uint256 initialCoolingOffPeriod;

    uint8 decimals;
  }

  struct FeeParams {
    FeeModel feeModel; 
    uint256 managementFeeRate;
    uint256 fixedYieldAmount;
    uint256 annualFundingFeeRate;
  }
}

File 12 of 54 : CommonTypes.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { Currency } from "./Currency.sol";
import { DTokenRegistry } from "../declarations/DTokenRegistry.sol";
import { Address } from "../types/Address.sol";

// Enums representing different fee models that can be applied.
enum OperationTypes {
  OP_TYPE_MINT_VT,
  OP_TYPE_MINT_YT,
  OP_TYPE_REDEEM_VT,
  OP_TYPE_REDEEM_YT
}

enum FeeModel {
  NONE, // No fees.
  MANAGEMENT_FEE, // Management fee model.
  VARIABLE_FUNDING_FEE, // Fee that varies based on funding.
  FIXED_FUNDING_FEE, // Fixed fee for funding.
  CURATED_PAIRS_FEE, // Curated pairs fee model.
  BLANK_FEE // Blank fee model.
}

// Information about acceptable collaterals
struct CollateralInfo {
  Currency token; // Token address for collateral
  uint8 decimals; // Decimals for the collateral token
  uint256 minAmount; // Minimum amount for both usage minting and redeeming (as desired token)
  uint256 maxAmount; // Maximum amount for both usage minting and redeeming (as desired token)
}

// DefaultFeeParams defines the basic fee structure with base, min, and max fees.
struct DefaultFeeParams {
  uint24 baseFee; // The base fee applied when no flags are present.
  uint24 minFee; // The minimum fee allowed for dynamic fee models.
  uint24 maxFee; // The maximum fee allowed.
}

// FeeParams defines specific fees for different token types and operations.
struct FeeParams {
  uint24 mintFeeVT; // Minting fee for volatile tokens (VT).
  uint24 redeemFeeVT; // Redemption fee for volatile tokens (VT).
  uint24 mintFeeYT; // Minting fee for yield tokens (YT).
  uint24 redeemFeeYT; // Redemption fee for yield tokens (YT).
  uint24 stabilityMintFeeVT; // Stability minting fee for volatile tokens (VT).
  uint24 stabilityMintFeeYT; // Stability minting fee for yield tokens (YT).
  uint24 stabilityRedeemFeeVT; // Stability redemption fee for volatile tokens (VT).
  uint24 stabilityRedeemFeeYT; // Stability redemption fee for yield tokens (YT).
  uint24 yieldFeeVT; // Yield fee for volatile tokens (VT).
  uint24 yieldFeeYT; // Yield fee for yield tokens (YT).
  uint24 protocolFee; // Protocol fee.
}

// FeePermissions define the flexibility of the fee model (dynamic fees, delegation).
struct FeePermissions {
  bool isDynamic; // Whether the fee model is dynamic.
  bool allowDelegation; // Whether fee delegation is allowed.
}

struct GroupState {
  DTokenRegistry.GroupCore core;
  DTokenRegistry.GroupExtended extended;
  bytes32 feesPacked;
  CollateralInfo[] acceptableCollaterals;
  Address hookContract;
  bytes32 groupSettings;
}

File 13 of 54 : ITreasury.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { GroupId } from "../types/GroupId.sol";
import { DTokenRegistry } from "../declarations/DTokenRegistry.sol";
import { DTreasury } from "../declarations/DTreasury.sol";
import { TreasuryHarvestLibrary } from "../libs/TreasuryHarvestLibrary.sol";

interface ITreasury {
  // Events
  event HarvestYield(GroupId indexed groupId, uint256 indexed yieldBaseTokens);
  event HarvestFees(GroupId indexed groupId, uint256 indexed xTokenAmount, uint256 indexed aTokenAmount);
  event FeesDistributed(GroupId indexed groupId, uint256 indexed xTokenAmount, uint256 indexed baseTokenAmount);
  event UpdateBaseTokenCap(GroupId indexed groupId, uint256 indexed oldBaseTokenCap, uint256 indexed newBaseTokenCap);
  event Settle(GroupId indexed groupId, uint256 indexed oldPrice, uint256 indexed newPrice);
  event UpdateFeeCollector(address indexed oldFeeCollector, address indexed newFeeCollector);
  event GroupInitialized(GroupId indexed groupId, uint256 indexed baseTokenPrice);

  event RebalanceUpPerformed(GroupId indexed groupId, uint256 indexed newCollateralRatio);
  event RebalanceDownPerformed(GroupId indexed groupId, uint256 indexed newCollateralRatio);
  event TransferToStrategy(GroupId indexed groupId, uint256 indexed amount);

  // Custom Errors
  error ZeroAddress();
  error ZeroAmount();
  error OnlyStrategy();
  error GroupAlreadyInitialized(GroupId groupId);
  error ErrorUnderCollateral(GroupId groupId);
  error ErrorExceedTotalCap(GroupId groupId);
  error ErrorInvalidTwapPrice(GroupId groupId);
  error NotPermitted();
  error InvalidGroupConfiguration(GroupId groupId);
  error InvalidRate(GroupId groupId, address rateProvider);
  error InvalidRatio(GroupId groupId);
  error InvalidPrice(GroupId groupId);
  error InvalidBaseTokenCap(GroupId groupId);
  error ErrorInsufficientBalance(GroupId groupId, address token);
  error ErrorSwapFailed();
  error ErrorDistributingYield(GroupId groupId);
  error ErrorWithdrawFromStrategy();
  error ErrorCollateralRatioTooSmall(GroupId groupId);
  error MaximumAmountExceeded(GroupId groupId, uint256 maxAmount);
  error StrategyUnderflow(GroupId groupId);
  error InvalidTokenType();
  error RenounceRoleProhibited();

  // View Functions
  function collateralRatio(GroupId groupId) external view returns (uint256);

  function isUnderCollateral(GroupId groupId) external view returns (bool);

  function leverageRatio(GroupId groupId) external view returns (uint256);

  function currentBaseTokenPrice(GroupId groupId) external view returns (uint256);

  // function isBaseTokenPriceValid(GroupId groupId) external view returns (bool _isValid);
  function totalBaseToken(GroupId groupId) external view returns (uint256);

  function maxMintableAToken(
    GroupId groupId,
    uint256 _newCollateralRatio
  ) external view returns (uint256 _maxBaseIn, uint256 _maxATokenMintable);

  function maxRedeemableAToken(
    GroupId groupId,
    uint256 _newCollateralRatio
  ) external view returns (uint256 _maxBaseOut, uint256 _maxATokenRedeemable);

  // State-Changing Functions
  function mintAToken(GroupId groupId, uint256 _baseIn, address _recipient) external returns (uint256 _aTokenOut);

  function mintXToken(GroupId groupId, uint256 _baseIn, address _recipient) external returns (uint256 _xTokenOut);

  function redeem(GroupId groupId, uint256 _aTokenIn, uint256 _xTokenIn, address _owner) external returns (uint256 _baseOut);

  function transferToStrategy(GroupId groupId, uint256 _amount) external;

  // Harvest Functions
  function harvestYield(GroupId groupId, TreasuryHarvestLibrary.HarvestParams memory params) external;

  function harvestFees(GroupId groupId, TreasuryHarvestLibrary.HarvestParams memory params) external;

  // Management Functions  
  function updateBaseTokenCap(GroupId groupId, uint256 _baseTokenCap) external;

  function updateFeeCollector(address _newFeeCollector) external;

  function forceUpdateGroupCache(address tokenRegistry, GroupId groupId) external;

  function getTreasuryState(GroupId groupId) external view returns (DTreasury.TreasuryState memory);
}

File 14 of 54 : IAToken.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { GroupId } from "../types/GroupId.sol";

/**
 * @title IAToken
 * @notice Interface for AToken, an ERC20-compatible token with minting, burning, and
 *         additional functionality for treasury and rebalance management.
 */
interface IAToken is IERC20Upgradeable {
  /**
   * @dev Emitted when the treasury address is updated.
   * @param oldTreasury The previous treasury address.
   * @param newTreasury The new treasury address.
   */
  event UpdateTreasuryAddress(address indexed oldTreasury, address indexed newTreasury);

  /**
   * @dev Emitted when the rebalance pool address is updated.
   * @param oldRebalancePool The previous rebalance pool address.
   * @param newRebalancePool The new rebalance pool address.
   */
  event UpdateRebalancePool(address indexed oldRebalancePool, address indexed newRebalancePool);

  /**
   * @dev Emitted when tokens are minted to an address.
   * @param to The address receiving the minted tokens.
   * @param amount The amount of tokens minted.
   */
  event Minted(address indexed to, uint256 indexed amount);

  /**
   * @dev Emitted when tokens are burned from an address.
   * @param from The address from which tokens are burned.
   * @param amount The amount of tokens burned.
   */
  event Burned(address indexed from, uint256 indexed amount);

  /**
   * @dev Emitted when a group is updated.
   * @param groupId The identifier of the updated group.
   */
  event UpdateGroup(GroupId indexed groupId);

  /**
   * @dev Emitted when the max supply is updated.
   * @param oldMaxSupply The previous max supply.
   * @param newMaxSupply The new max supply.
   */
  event UpdateMaxSupply(uint256 indexed oldMaxSupply, uint256 indexed newMaxSupply);

  // Custom Errors
  error InvalidDecimals();
  error ErrorNotPermitted();
  error ErrorZeroAddress();
  error ErrorZeroAmount();
  error ErrorExceedsMaxSupply();
  error ErrorCannotRecoverToken();
  error ErrorParameterUnchanged();

  /**
   * @notice Returns the beta status of the token.
   */
  function beta() external view returns (bool);
  
  /**
   * @notice Returns the Net Asset Value (NAV) of the token.
   * @return The current NAV as an unsigned integer.
   */
  function nav() external view returns (uint256);

  /**
   * @notice Returns the number of decimals used by the token.
   * @return The number of decimals.
   */
  function decimals() external view returns (uint8);

  /**
   * @notice Mints tokens to a specified address.
   * @dev Emits a {Minted} event.
   * @param _to The address to receive the minted tokens.
   * @param _amount The amount of tokens to mint.
   */
  function mint(address _to, uint256 _amount) external;

  /**
   * @notice Burns tokens from a specified address.
   * @dev Emits a {Burned} event.
   * @param _from The address from which tokens are burned.
   * @param _amount The amount of tokens to burn.
   */
  function burn(address _from, uint256 _amount) external;
}

File 15 of 54 : ERC20PermitUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/ERC20Permit.sol)

pragma solidity ^0.8.0;

import "./IERC20PermitUpgradeable.sol";
import "../ERC20Upgradeable.sol";
import "../../../utils/cryptography/ECDSAUpgradeable.sol";
import "../../../utils/cryptography/EIP712Upgradeable.sol";
import "../../../utils/CountersUpgradeable.sol";
import {Initializable} from "../../../proxy/utils/Initializable.sol";

/**
 * @dev Implementation 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.
 *
 * _Available since v3.4._
 *
 * @custom:storage-size 51
 */
abstract contract ERC20PermitUpgradeable is Initializable, ERC20Upgradeable, IERC20PermitUpgradeable, EIP712Upgradeable {
    using CountersUpgradeable for CountersUpgradeable.Counter;

    mapping(address => CountersUpgradeable.Counter) private _nonces;

    // solhint-disable-next-line var-name-mixedcase
    bytes32 private constant _PERMIT_TYPEHASH =
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    /**
     * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.
     * However, to ensure consistency with the upgradeable transpiler, we will continue
     * to reserve a slot.
     * @custom:oz-renamed-from _PERMIT_TYPEHASH
     */
    // solhint-disable-next-line var-name-mixedcase
    bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;

    /**
     * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
     *
     * It's a good idea to use the same `name` that is defined as the ERC20 token name.
     */
    function __ERC20Permit_init(string memory name) internal onlyInitializing {
        __EIP712_init_unchained(name, "1");
    }

    function __ERC20Permit_init_unchained(string memory) internal onlyInitializing {}

    /**
     * @inheritdoc IERC20PermitUpgradeable
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual override {
        require(block.timestamp <= deadline, "ERC20Permit: expired deadline");

        bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSAUpgradeable.recover(hash, v, r, s);
        require(signer == owner, "ERC20Permit: invalid signature");

        _approve(owner, spender, value);
    }

    /**
     * @inheritdoc IERC20PermitUpgradeable
     */
    function nonces(address owner) public view virtual override returns (uint256) {
        return _nonces[owner].current();
    }

    /**
     * @inheritdoc IERC20PermitUpgradeable
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view override returns (bytes32) {
        return _domainSeparatorV4();
    }

    /**
     * @dev "Consume a nonce": return the current value and increment.
     *
     * _Available since v4.1._
     */
    function _useNonce(address owner) internal virtual returns (uint256 current) {
        CountersUpgradeable.Counter storage nonce = _nonces[owner];
        current = nonce.current();
        nonce.increment();
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}

File 16 of 54 : IAccessControlUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity ^0.8.0;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControlUpgradeable {
    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) external;
}

File 17 of 54 : ContextUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

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

/**
 * @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 ContextUpgradeable is Initializable {
    function __Context_init() internal onlyInitializing {
    }

    function __Context_init_unchained() internal onlyInitializing {
    }
    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;
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[50] private __gap;
}

File 18 of 54 : StringsUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/MathUpgradeable.sol";
import "./math/SignedMathUpgradeable.sol";

/**
 * @dev String operations.
 */
library StringsUpgradeable {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = MathUpgradeable.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toString(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMathUpgradeable.abs(value))));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, MathUpgradeable.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

File 19 of 54 : ERC165Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable {
    function __ERC165_init() internal onlyInitializing {
    }

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

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[50] private __gap;
}

File 20 of 54 : Initializable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.2;

import "../../utils/AddressUpgradeable.sol";

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     * @custom:oz-retyped-from bool
     */
    uint8 private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint8 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
     * constructor.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        bool isTopLevelCall = !_initializing;
        require(
            (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
            "Initializable: contract is already initialized"
        );
        _initialized = 1;
        if (isTopLevelCall) {
            _initializing = true;
        }
        _;
        if (isTopLevelCall) {
            _initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: setting the version to 255 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint8 version) {
        require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
        _initialized = version;
        _initializing = true;
        _;
        _initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        require(_initializing, "Initializable: contract is not initializing");
        _;
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        require(!_initializing, "Initializable: contract is initializing");
        if (_initialized != type(uint8).max) {
            _initialized = type(uint8).max;
            emit Initialized(type(uint8).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint8) {
        return _initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _initializing;
    }
}

File 21 of 54 : IERC20Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20Upgradeable {
    /**
     * @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 amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` 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 amount) external returns (bool);
}

File 22 of 54 : IERC20PermitUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @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 IERC20PermitUpgradeable {
    /**
     * @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);
}

File 23 of 54 : AddressUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library AddressUpgradeable {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - 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
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @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.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @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, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * 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.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @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`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

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

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

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

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) 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(errorMessage);
        }
    }
}

File 24 of 54 : GroupKey.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { DTokenRegistry } from "../declarations/DTokenRegistry.sol";

/**
 * @title GroupKey
 * @dev Struct representing the core group key information.
 */
struct GroupKey {
    /**
     * @dev The core group information from the DTokenRegistry.
     */
    DTokenRegistry.GroupCore core;
}

File 25 of 54 : Currency.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

/// @notice https://forum.openzeppelin.com/t/safeerc20-vs-safeerc20upgradeable/17326
import { SafeERC20Upgradeable, IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { CustomRevert } from "../libs/CustomRevert.sol";

// Custom type `Currency` to represent either an ERC20 token or the native currency (e.g., ETH)
type Currency is address;

/**
 * @title CurrencyLibrary
 * @dev A library for handling operations related to currencies, supporting both ERC20 tokens and native currency.
 *      Provides utility functions for balance checks, transfers, approvals, and comparisons.
 */
library CurrencyLibrary {
  // Use SafeERC20Upgradeable for IERC20Upgradeable token operations to ensure safety
  using SafeERC20Upgradeable for IERC20Upgradeable;

  // Use CustomRevert for standardized error handling via selectors
  using CustomRevert for bytes4;

  // Define a constant representing the native currency (address(0))
  Currency public constant NATIVE = Currency.wrap(address(0));

  // Custom error definitions for various invalid operations involving native currency
  error NativeCurrencyTransfersNotAllowed();
  error NativeCurrencyTransferFromNotAllowed();
  error NativeCurrencyApprovalNotAllowed();
  error NativeCurrencyDoesNotHaveTotalSupply();
  error NativeCurrencyIncreaseAllowanceNotAllowed();
  error NativeCurrencyDecreaseAllowanceNotAllowed();
  error ArbitraryTransfersNotAllowed();

  /**
   * @notice Checks if two currencies are equal.
   * @param currency The first currency to compare.
   * @param other The second currency to compare.
   * @return True if both currencies are the same, false otherwise.
   */
  function equals(Currency currency, Currency other) internal pure returns (bool) {
    return Currency.unwrap(currency) == Currency.unwrap(other);
  }

  /**
   * @notice Retrieves the balance of the specified owner for the given currency.
   * @param currency The currency to check (ERC20 token or native).
   * @param owner The address of the owner whose balance is queried.
   * @return The balance of the owner in the specified currency.
   */
  function balanceOf(Currency currency, address owner) internal view returns (uint256) {
    if (isNative(currency)) {
      return owner.balance; // For native currency, return the ETH balance
    } else {
      return IERC20Upgradeable(Currency.unwrap(currency)).balanceOf(owner); // For ERC20 tokens, use balanceOf
    }
  }

  /**
   * @notice Safely transfers a specified amount of the currency to a recipient.
   * @param currency The currency to transfer (must be an ERC20 token).
   * @param to The recipient address.
   * @param amount The amount to transfer.
   * @dev Native currency transfers are not allowed and will revert.
   */
  function safeTransfer(Currency currency, address to, uint256 amount) internal {
    if (isNative(currency)) {
      // Revert if attempting to transfer native currency using ERC20 methods
      NativeCurrencyTransfersNotAllowed.selector.revertWith();
    } else {
      IERC20Upgradeable(Currency.unwrap(currency)).safeTransfer(to, amount);
    }
  }

  /**
   * @notice Safely transfers a specified amount of the currency from one address to another.
   * @param currency The currency to transfer (must be an ERC20 token).
   * @param safeFrom The address to transfer from.
   * @param to The recipient address.
   * @param amount The amount to transfer.
   * @dev Native currency transfers are not allowed and will revert.
   * @dev Arbitrary transfers (i.e., not initiated by the sender) are also not allowed and will revert.
   * @notice Slither false positive. The function is internal and only used within the library
   */
  //slither-disable-next-line arbitrary-send-erc20
  function safeTransferFrom(Currency currency, address safeFrom, address to, uint256 amount) internal {
    if (isNative(currency)) {
      // Revert if attempting to transfer ERC20 tokens from an address other than the sender
      // This is to prevent arbitrary transfers, which are not allowed in the context of this library
      // This logic has the priority, so overrides any other inhereted logic
      NativeCurrencyTransferFromNotAllowed.selector.revertWith();
    } else {
      if (safeFrom != msg.sender) ArbitraryTransfersNotAllowed.selector.revertWith();
      IERC20Upgradeable(Currency.unwrap(currency)).safeTransferFrom(safeFrom, to, amount);
    }
  }

  /**
   * @notice Retrieves the allowance of a spender for the given owner's currency.
   * @param currency The currency to check (must be an ERC20 token).
   * @param owner The address of the owner of the currency.
   * @param spender The address of the spender.
   * @return The allowance of the spender for the owner's currency.
   */
  function allowance(Currency currency, address owner, address spender) internal view returns (uint256) {
    if (isNative(currency)) {
      return 0; // For native currency, return 0 as allowance is not applicable
    } else {
      return IERC20Upgradeable(Currency.unwrap(currency)).allowance(owner, spender); // For ERC20 tokens, use allowance
    }
  }

  /**
   * @notice Safely approves a spender to spend a specified amount of the currency.
   * @param currency The currency to approve (must be an ERC20 token).
   * @param spender The address authorized to spend the tokens.
   * @param amount The amount to approve.
   * @dev Approving native currency is not allowed and will revert.
   */
  function safeApprove(Currency currency, address spender, uint256 amount) internal {
    if (!isNative(currency)) {
      IERC20Upgradeable(Currency.unwrap(currency)).safeApprove(spender, amount);
    } else {
      // Revert if attempting to approve native currency
      NativeCurrencyApprovalNotAllowed.selector.revertWith();
    }
  }

  /**
   * @notice Safely increases the allowance of a spender for the currency.
   * @param currency The currency to modify allowance for (must be an ERC20 token).
   * @param spender The address authorized to spend the tokens.
   * @param addedValue The amount to increase the allowance by.
   * @dev Increasing allowance for native currency is not allowed and will revert.
   */
  function safeIncreaseAllowance(Currency currency, address spender, uint256 addedValue) internal {
    if (!isNative(currency)) {
      IERC20Upgradeable(Currency.unwrap(currency)).safeIncreaseAllowance(spender, addedValue);
    } else {
      // Revert if attempting to increase allowance for native currency
      NativeCurrencyIncreaseAllowanceNotAllowed.selector.revertWith();
    }
  }

  /**
   * @notice Checks if the given currency is the native currency.
   * @param currency The currency to check.
   * @return True if `currency` is the native currency, false otherwise.
   */
  function isNative(Currency currency) internal pure returns (bool) {
    return Currency.unwrap(currency) == Currency.unwrap(NATIVE);
  }

  /**
   * @notice Checks if the given currency is the zero address.
   * @param currency The currency to check.
   * @return True if `currency` is the zero address, false otherwise.
   */
  function isZero(Currency currency) internal pure returns (bool) {
    return Currency.unwrap(currency) == address(0);
  }

  /**
   * @notice Converts the currency address to a unique identifier.
   * @param currency The currency to convert.
   * @return The uint256 representation of the currency's address.
   */
  function toId(Currency currency) internal pure returns (uint256) {
    return uint160(Currency.unwrap(currency));
  }

  /**
   * @notice Unwraps the `Currency` type to retrieve the underlying address.
   * @param currency The currency to unwrap.
   * @return The underlying address of the currency.
   */
  function toAddress(Currency currency) internal pure returns (address) {
    return Currency.unwrap(currency);
  }
}

File 26 of 54 : DTokenRegistry.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { Currency } from "../types/Currency.sol";
import { Address } from "../types/Address.sol";
import { DefaultFeeParams, FeeParams, FeePermissions, CollateralInfo } from "../types/CommonTypes.sol";

library DTokenRegistry {
  // Core tokens of the group (immutable)
  struct GroupCore {
    Currency aToken; // Yield-bearing token
    Currency xToken; // Leverage token
    Currency baseToken; // Base token for the group - wrapped Avax
    Currency yieldBearingToken; // Example: staked AVAX (immutable)
    Currency wethToken; // WETH token address for the router
  }

  // Decimals for each core token
  struct GroupDecimals {
    uint8 aTokenDecimals;
    uint8 xTokenDecimals;
    uint8 baseTokenDecimals;
    uint8 yieldBearingTokenDecimals;
  }

  // Extended group settings (mutable)
  struct GroupExtended {
    Address priceOracle; // Price Oracle address
    Address rateProvider; // Rate provider address
    Address swapRouter; // Swap Router
    Address treasury; // Treasury address
    Address feeCollector; // Fee collector address
    Address strategy; // Strategy contract address
    Currency rebalancePool; // Rebalance pool address
  }

  // Metadata for the group (partially mutable)
  struct GroupMeta {
    uint96 stabilityRatio; // Mutable stability ratio (this is 2^96-1 and we have max 5e18)
    uint96 stabilityConditionsTriggeringRate; // Mutable stability fee trigger (this is 2^96-1 and we have max 5e18)
    uint8 feeModel; // Immutable fee model used for this group
    bool isWrappingRequired; // Immutable wrapping requirement flag
  }

  // Struct representing full group setup during creation
  struct GroupSetup {
    GroupCore core;
    GroupDecimals decimals;
    GroupExtended extended;
    GroupMeta meta;
    FeeParams fees;
    DefaultFeeParams defaultFees;
    FeePermissions feePermissions;
    CollateralInfo[] acceptableCollaterals;
  }

  // Data used for updating mutable parts of the group
  struct GroupUpdate {
    GroupExtended extended;
    GroupMeta meta;
    CollateralInfo[] acceptableCollaterals;
    FeeParams feeParams;
    DefaultFeeParams defaultFees;
  }
}

File 27 of 54 : Address.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

// Import the CustomRevert library for standardized error handling
import { CustomRevert } from "../libs/CustomRevert.sol";

/**
 * @title Address
 * @dev Defines a user-defined value type `Address` that wraps the built-in `address` type.
 */
type Address is address;

/**
 * @title AddressLibrary
 * @dev A library for performing various operations on the `Address` type.
 */
library AddressLibrary {
  // Apply the library functions to the `Address` type
  using AddressLibrary for Address;

  // Use the CustomRevert library for standardized error handling via selectors
  using CustomRevert for bytes4;

  // Custom error definitions for more descriptive revert reasons
  error ZeroAddress();
  error FailedCall(string reason);
  error NonContractAddress();
  error UnableToSendValue();

  /**
   * @notice Checks if the given `Address` is the zero address.
   * @param addr The `Address` to check.
   * @return True if `addr` is the zero address, false otherwise.
   */
  function isZero(Address addr) internal pure returns (bool) {
    return Address.unwrap(addr) == address(0);
  }

  /**
   * @notice Determines if the given `Address` is a contract.
   * @param addr The `Address` to check.
   * @return True if `addr` is a contract, false otherwise.
   *
   * @dev This method relies on the fact that contracts have non-zero code size.
   *      It returns false for contracts in construction, since the code is only stored at the end of the constructor execution.
   */
  function isContract(Address addr) internal view returns (bool) {
    return Address.unwrap(addr).code.length > 0;
  }

  /**
   * @notice Compares two `Address` instances for equality.
   * @param a The first `Address`.
   * @param b The second `Address`.
   * @return True if both addresses are equal, false otherwise.
   */
  function equals(Address a, Address b) internal pure returns (bool) {
    address addrA = Address.unwrap(a);
    address addrB = Address.unwrap(b);
    return addrA == addrB;
  }

  /**
   * @notice Converts an `Address` to a `uint160`.
   * @param addr The `Address` to convert.
   * @return The `uint160` representation of the address.
   */
  function toUint160(Address addr) internal pure returns (uint160) {
    return uint160(Address.unwrap(addr));
  }

  /**
   * @notice Creates an `Address` from a `uint160`.
   * @param addr The `uint160` value to convert.
   * @return A new `Address` instance.
   */
  function fromUint160(uint160 addr) internal pure returns (Address) {
    return Address.wrap(address(addr));
  }

  /**
   * @notice Unwraps the `Address` type to retrieve the underlying address.
   * @param addr The `Address` to unwrap.
   * @return The underlying `address`.
   */
  function toAddress(Address addr) internal pure returns (address) {
    return Address.unwrap(addr);
  }
}

File 28 of 54 : DTreasury.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { ExponentialMovingAverageV8 } from "../libs/math/ExponentialMovingAverageV8.sol";

library DTreasury {
  using ExponentialMovingAverageV8 for ExponentialMovingAverageV8.EMAStorage;

  struct GroupUpdateParams {
    uint256 baseTokenCaps;
    uint256 baseIn;
  }

  struct TreasuryState {
    ExponentialMovingAverageV8.EMAStorage emaLeverageRatio;
    uint256 totalBaseTokens;
    uint256 baseTokenCaps;

    uint256 lastSettlementTimestamp;
    uint256 baseTokenPrice;
    bool inited;
    uint256 strategyUnderlying;
  }
}

File 29 of 54 : TreasuryHarvestLibrary.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { Currency, CurrencyLibrary } from "../types/Currency.sol";
import { GroupStateHelper, GroupSettings } from "../types/GroupStateHelper.sol";
import { GroupState, FeeModel } from "../types/CommonTypes.sol";
import { GroupId } from "../types/GroupId.sol";
import { DTreasury } from "../declarations/DTreasury.sol";
import { TreasuryStateLibrary } from "./TreasuryStateLibrary.sol";
import { IDXToken } from "../interfaces/IDXToken.sol";
import { IAToken } from "../interfaces/IAToken.sol";
import { IWToken } from "../interfaces/IWToken.sol";
import { IRebalancePool } from "../interfaces/IRebalancePool.sol";
import { CustomRevert } from "../libs/CustomRevert.sol";
import { FullMath } from "../libs/math/FullMath.sol";
import { IProtocolMinimum as IProtocol } from "../interfaces/IProtocolMinimum.sol";

/**
 * @title TreasuryHarvestLibrary
 * @notice This library orchestrates fee and yield harvesting for a specific group.
 * @dev It handles collecting fees, distributing base tokens, minting aTokens, and managing 
 *      yield fees. The library relies on external calls to tokens and Rebalance Pools, and 
 *      carefully handles potential revert scenarios via `try/catch` and bubble-up reverts.
 */
library TreasuryHarvestLibrary {
    using CurrencyLibrary for Currency;
    using GroupStateHelper for GroupSettings;
    using TreasuryStateLibrary for *;
    using CustomRevert for bytes4;

    // =========================
    // ======== Errors =========
    // =========================
    error ZeroAmount();
    error WrongFeeModel(GroupId groupId);
    error ErrorDistributingYield();
    error ErrorFeeCollectionFailed(GroupId groupId);
    error ErrorDistributingTokens(GroupId groupId);

    // =========================
    // ======== Events =========
    // =========================

    event FeesCollected(GroupId indexed groupId, uint256 amount);
    event FeesHarvested(GroupId indexed groupId, uint256 dxTokenBalance, address feeCollector);
    event YieldHarvested(GroupId indexed groupId, uint256 yieldAmount, address feeCollector);
    event ATokensMintedToPool(GroupId indexed groupId, uint256 aTokenAmount, address recipient);
    event TokensSwappedToPool(GroupId indexed groupId, address fromToken, address toToken, uint256 amountIn, uint256 amountOut);

    // =========================
    // ======== Structs ========
    // =========================

    /**
     * @notice Parameters used to harvest yield or fees.
     * @param sendTokens If true, will swap base tokens into stablecoins 
     *        instead of attempting to mint aTokens (when mint capacity is exceeded).
     * @param swapRouter Address of the DEX/router used to swap base tokens for stablecoins.
     * @param stablecoin The address of the stablecoin into which base tokens may be swapped.
     * @param minAmountOut The minimum amount of stablecoins that must be received from the swap 
     *        for the transaction not to revert.
     */
    struct HarvestParams {
        bool sendTokens;
        address swapRouter;
        address stablecoin;
        uint256 minAmountOut;
    }

    /**
     * @notice Collects any accrued fees from dxToken. 
     * @dev Calls `collectFees` on the dxToken. Reverts if no fees were collected.
     * @param groupState The current group state, which includes the dxToken address.
     * @return The amount of dxToken fees collected.
     */
    function collectFees(GroupState memory groupState, GroupId groupId) internal returns (uint256) {
        IDXToken dxToken = IDXToken(groupState.core.xToken.toAddress());
        uint256 dxTokenBalanceBefore = dxToken.balanceOf(address(this));
        
        // Attempt to collect fees from dxToken
        try dxToken.collectFees() {
            // Get the balance of dxToken after fees have been collected
            uint256 dxTokenBalanceAfter = dxToken.balanceOf(address(this));
            uint256 collectedAmount = dxTokenBalanceAfter - dxTokenBalanceBefore;

            // Revert if no fees were actually collected
            if (collectedAmount == 0) ZeroAmount.selector.revertWith();

            // Emit an event to log the fees collected
            emit FeesCollected(groupId, collectedAmount);

            return collectedAmount;
        } catch {
           CustomRevert.bubbleUpAndRevertWith(dxToken.collectFees.selector, address(dxToken));
        }
    }

    /**
     * @notice Harvests fees (not yield) based on the group's fee model.
     * @dev For management fees, the dxTokens are simply transferred to the feeCollector.
     *      For funding fees, dxTokens are burned and then aTokens are minted (or stablecoins are swapped).
     * @param groupId The ID of the group for which fees are harvested.
     * @param groupState The current group state data.
     * @param treasuryState The TreasuryState storage reference for updating internal state.
     * @param harvestParams Parameters controlling how the harvested tokens might be swapped or minted.
     * @param dxTokenBalance The amount of dxTokens available for fee harvesting.
     * @param feeCollector The address to which management fees should be sent.
     * @param protocol The Protocol contract address (for calculating stability ratio, etc).
     */
    function harvestFees(
        GroupId groupId,
        GroupState memory groupState,
        DTreasury.TreasuryState storage treasuryState,
        HarvestParams memory harvestParams,
        uint256 dxTokenBalance, 
        address feeCollector, 
        address protocol
    ) internal {
        IDXToken dxToken = IDXToken(groupState.core.xToken.toAddress());
        
        // Determine the fee model for this group
        FeeModel feeModel = dxToken.getFeeModel();

        if (feeModel == FeeModel.MANAGEMENT_FEE) {
            // Simply transfer dxTokens to fee collector
            groupState.core.xToken.safeTransfer(feeCollector, dxTokenBalance);
            return;
        }
        
        if (feeModel == FeeModel.VARIABLE_FUNDING_FEE || feeModel == FeeModel.FIXED_FUNDING_FEE) {
            // For funding fees, burn dxTokens
            try dxToken.burn(address(this), dxTokenBalance) {} catch {
                CustomRevert.bubbleUpAndRevertWith(dxToken.burn.selector, address(dxToken));
            }

            // Calculate baseTokenAmount from dxTokens
            uint256 baseTokenAmount = dxToken.dxTokenToBaseToken(dxTokenBalance);
            uint8 baseTokenDecimals = GroupSettings.wrap(groupState.groupSettings).getBaseTokenDecimals();

            // apply yield and get net harvestable amount
            (baseTokenAmount, ) = _applyYield(groupState, baseTokenAmount, feeCollector);
            
            // Normalize baseTokenAmount to an 18-decimal scale (for internal accounting)
            uint256 baseTokenAmountNormalized = TreasuryStateLibrary.normalizeDecimals(
                baseTokenAmount, 
                baseTokenDecimals
            );

            // Distribute base tokens either as aToken or stablecoin, depending on mint capacity
            _distributeBaseTokens(
                groupState,
                groupId,
                treasuryState,
                baseTokenAmount,
                baseTokenAmountNormalized,
                harvestParams,
                protocol
            );

            emit FeesHarvested(groupId, dxTokenBalance, feeCollector);
            return;
        }

        // If none of the recognized fee models applies, revert.
        WrongFeeModel.selector.revertWith(groupId);
    }

    /**
     * @notice Harvests yield for a group, applies any yield fees, and distributes the net amount.
     * @dev A portion of the yield is sent to the feeCollector as a yield fee (if applicable).
     * @param groupId The ID of the group for which yield is being harvested.
     * @param groupState The current group state data.
     * @param treasuryState The TreasuryState storage reference for updating internal state.
     * @param harvestParams Parameters controlling how the base tokens might be swapped or minted.
     * @param harvestableAmount The total base token amount of yield harvested.
     * @param feeCollector The address to which the yield fee is sent.
     * @param protocol The Protocol contract address (for calculating stability ratio, etc).
     */
    function harvestYield(
        GroupId groupId,
        GroupState memory groupState,
        DTreasury.TreasuryState storage treasuryState,
        HarvestParams memory harvestParams,
        uint256 harvestableAmount, 
        address feeCollector, 
        address protocol
    ) internal {
        // Apply yield fees and get net harvestable amount
        (harvestableAmount, ) = _applyYield(groupState, harvestableAmount, feeCollector);

        // Normalize the base token amount for internal accounting
        uint8 baseTokenDecimals = GroupSettings.wrap(groupState.groupSettings).getBaseTokenDecimals();
        uint256 baseTokenAmountNormalized = TreasuryStateLibrary.normalizeDecimals(
            harvestableAmount, 
            baseTokenDecimals
        );

        // Distribute the net base tokens (either as stablecoin or aTokens)
        _distributeBaseTokens(
            groupState,
            groupId,
            treasuryState,
            harvestableAmount,
            baseTokenAmountNormalized,
            harvestParams,
            protocol
        );

        emit YieldHarvested(groupId, harvestableAmount, feeCollector);
    }

    /**
     * @notice Calculates how many aTokens should be minted given a certain amount of base tokens,
     *         and also how many aTokens can be minted at maximum without breaching collateral targets.
     * @param groupState The current group state data.
     * @param groupId The ID of the group for which aTokens will be minted.
     * @param baseTokenAmountNormalized The normalized amount of base tokens to convert into aTokens.
     * @param aToken The IAToken contract reference (whose NAV is used to compute minted supply).
     * @param protocol The Protocol contract address (for stability ratio checks).
     * @return aTokenToMint How many aTokens should be minted given the current NAV.
     * @return aTokenMintableMax The maximum number of aTokens that can be minted without breaching collateral.
     */
    function calculateMintingParams(
        DTreasury.TreasuryState storage treasuryState,
        GroupState memory groupState,
        GroupId groupId,
        uint256 baseTokenAmountNormalized,
        IAToken aToken,
        address protocol
    ) internal view returns (uint256 aTokenToMint, uint256 aTokenMintableMax) {
        // Fetch the current base token price from an oracle or similar feed
        (, uint256 newPrice) = TreasuryStateLibrary.fetchBaseTokenPrice(treasuryState, groupState);
        
        // NAV of the aToken is used to determine how many aTokens 1 base token is worth
        uint256 aTokenNav = aToken.nav();
        
        // aTokenToMint = (baseTokenAmountNormalized * newPrice) / aTokenNav
        aTokenToMint = FullMath.mulDiv(baseTokenAmountNormalized, newPrice, aTokenNav);

        // Check how many aTokens we can mint before breaching collateral/stability constraints
        (, aTokenMintableMax) = TreasuryStateLibrary.maxMintableAToken(
            treasuryState,
            groupState, 
            uint256(IProtocol(protocol).stabilityRatio(groupId))
        );
    }

    /**
     * @notice Distributes base tokens to the Rebalance Pool either by minting aTokens or swapping 
     *         them into stablecoins, depending on the mint capacity and user preference.
     * @dev If the aToken mint capacity is insufficient and `sendTokens == true`, base tokens 
     *      are swapped into stablecoins. Otherwise, the function reverts.
     * @param groupState The current group state data.
     * @param groupId The ID of the group for which distribution is happening.
     * @param treasuryState The TreasuryState storage reference for updating internal state.
     * @param baseTokenAmount The actual (denormalized) amount of base tokens.
     * @param baseTokenAmountNormalized The normalized base token amount (scaled to 18 decimals internally).
     * @param harvestParams Struct containing swap and stablecoin parameters.
     * @param protocol The Protocol contract address (for stability ratio checks).
     */
    function _distributeBaseTokens(
        GroupState memory groupState,
        GroupId groupId,
        DTreasury.TreasuryState storage treasuryState,
        uint256 baseTokenAmount,
        uint256 baseTokenAmountNormalized,
        HarvestParams memory harvestParams,
        address protocol
    ) private {
        IAToken aToken = IAToken(groupState.core.aToken.toAddress());
        Currency stableCoinCurrency = Currency.wrap(harvestParams.stablecoin);

        // Compute how many aTokens we *need* to mint and how many are *allowed* to mint
        (uint256 aTokenToMint, uint256 aTokenMintableMax) = calculateMintingParams(
            treasuryState,
            groupState, 
            groupId, 
            baseTokenAmountNormalized, 
            aToken, 
            protocol
        );

        // If we cannot mint the entire required amount of aTokens
        if (aTokenMintableMax < aTokenToMint) {
            // If we are NOT allowed to send stablecoins, revert
            if (!harvestParams.sendTokens) ErrorDistributingTokens.selector.revertWith(groupId);

            // Decrease the treasury's totalBaseTokens because we'll unwrap and swap them
            treasuryState.totalBaseTokens -= baseTokenAmountNormalized;

            // 1. Unwrap the wrapped base tokens into their underlying (e.g., WETH -> ETH or WMATIC -> MATIC).
            uint256 unwrappedBaseTokens = IWToken(groupState.core.baseToken.toAddress()).unwrap(baseTokenAmount);

            // 2. Swap from base tokens -> stablecoins
            uint256 stablecoinAmount = TreasuryStateLibrary.swapTokens(
                address(this),
                harvestParams.swapRouter,
                // We should swap from the *yield-bearing token* (not the base token)
                groupState.core.yieldBearingToken.toAddress(),
                stableCoinCurrency.toAddress(),
                unwrappedBaseTokens,
                harvestParams.minAmountOut
            );

            // 3. Transfer the stablecoins to the Rebalance Pool
            stableCoinCurrency.safeTransfer(
                groupState.extended.rebalancePool.toAddress(), 
                stablecoinAmount
            );

            emit TokensSwappedToPool(groupId, groupState.core.baseToken.toAddress(), harvestParams.stablecoin, unwrappedBaseTokens, stablecoinAmount);

        } else {
            // We can mint the full aToken amount from the base tokens
            // Increase the treasury's totalBaseTokens accordingly
            treasuryState.totalBaseTokens += baseTokenAmountNormalized;

            // Convert aTokenToMint from normalized (18 decimals) to the actual aToken decimals
            uint8 aTokenDecimals = GroupSettings.wrap(groupState.groupSettings).getATokenDecimals();
            uint256 aTokenToMintDenorm = TreasuryStateLibrary.denormalizeDecimals(aTokenToMint, aTokenDecimals);

            // Mint aTokens directly to the Rebalance Pool
            aToken.mint(groupState.extended.rebalancePool.toAddress(), aTokenToMintDenorm);

            // Update the Rebalance Pool’s NAV to reflect the newly minted aTokens
            IRebalancePool(groupState.extended.rebalancePool.toAddress()).updateNAV();

            emit ATokensMintedToPool(groupId, aTokenToMintDenorm, groupState.extended.rebalancePool.toAddress());
        }
    }

    /**
     * @notice Applies a yield fee to the harvestable amount (if any) and transfers that fee 
     *         portion to the feeCollector. Returns the net amount after fee.
     * @param groupState The current group state data (for accessing baseToken).
     * @param harvestableAmount The total yield (in base tokens) to be distributed.
     * @param feeCollector The address to which yield fees should be sent.
     * @return _harvestableAmount The net (post-fee) amount of base tokens.
     * @return _feeAmount The fee amount that was deducted and sent to the feeCollector.
     */
    function _applyYield(
        GroupState memory groupState, 
        uint256 harvestableAmount, 
        address feeCollector
    ) 
        private 
        returns (uint256 _harvestableAmount, uint256 _feeAmount) 
    {
        IRebalancePool rebalancePool = IRebalancePool(groupState.extended.rebalancePool.toAddress());
        uint256 yieldFeePercentage = rebalancePool.yieldFeePercentage();
        uint256 BASE_POINTS = 10_000;

        // If there's a yield fee, calculate and transfer it
        if (yieldFeePercentage > 0) {
            _feeAmount = (harvestableAmount * yieldFeePercentage) / BASE_POINTS;
            _harvestableAmount = harvestableAmount - _feeAmount;

            if (_feeAmount > 0) {
                groupState.core.baseToken.safeTransfer(feeCollector, _feeAmount);
            }
        } else {
            // If no yield fee, the entire amount is harvestable
            _harvestableAmount = harvestableAmount;
        }

        return (_harvestableAmount, _feeAmount);
    }
}

File 30 of 54 : ERC20Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {
        __ERC20_init_unchained(name_, symbol_);
    }

    function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _balances[account] += amount;
        }
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
            // Overflow not possible: amount <= accountBalance <= totalSupply.
            _totalSupply -= amount;
        }

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance amount in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[45] private __gap;
}

File 31 of 54 : ECDSAUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.0;

import "../StringsUpgradeable.sol";

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSAUpgradeable {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS,
        InvalidSignatureV // Deprecated in v4.8
    }

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature` or error string. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, signature);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
        bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
        uint8 v = uint8((uint256(vs) >> 255) + 27);
        return tryRecover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     *
     * _Available since v4.2._
     */
    function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, r, vs);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature);
        }

        return (signer, RecoverError.NoError);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, "\x19Ethereum Signed Message:\n32")
            mstore(0x1c, hash)
            message := keccak256(0x00, 0x3c)
        }
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from `s`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", StringsUpgradeable.toString(s.length), s));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, "\x19\x01")
            mstore(add(ptr, 0x02), domainSeparator)
            mstore(add(ptr, 0x22), structHash)
            data := keccak256(ptr, 0x42)
        }
    }

    /**
     * @dev Returns an Ethereum Signed Data with intended validator, created from a
     * `validator` and `data` according to the version 0 of EIP-191.
     *
     * See {recover}.
     */
    function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x00", validator, data));
    }
}

File 32 of 54 : EIP712Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol)

pragma solidity ^0.8.8;

import "./ECDSAUpgradeable.sol";
import "../../interfaces/IERC5267Upgradeable.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";

/**
 * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
 *
 * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
 * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
 * they need in their contracts using a combination of `abi.encode` and `keccak256`.
 *
 * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
 * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
 * ({_hashTypedDataV4}).
 *
 * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
 * the chain id to protect against replay attacks on an eventual fork of the chain.
 *
 * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
 * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
 *
 * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
 * separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the
 * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
 *
 * _Available since v3.4._
 *
 * @custom:storage-size 52
 */
abstract contract EIP712Upgradeable is Initializable, IERC5267Upgradeable {
    bytes32 private constant _TYPE_HASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");

    /// @custom:oz-renamed-from _HASHED_NAME
    bytes32 private _hashedName;
    /// @custom:oz-renamed-from _HASHED_VERSION
    bytes32 private _hashedVersion;

    string private _name;
    string private _version;

    /**
     * @dev Initializes the domain separator and parameter caches.
     *
     * The meaning of `name` and `version` is specified in
     * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
     *
     * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
     * - `version`: the current major version of the signing domain.
     *
     * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
     * contract upgrade].
     */
    function __EIP712_init(string memory name, string memory version) internal onlyInitializing {
        __EIP712_init_unchained(name, version);
    }

    function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing {
        _name = name;
        _version = version;

        // Reset prior values in storage if upgrading
        _hashedName = 0;
        _hashedVersion = 0;
    }

    /**
     * @dev Returns the domain separator for the current chain.
     */
    function _domainSeparatorV4() internal view returns (bytes32) {
        return _buildDomainSeparator();
    }

    function _buildDomainSeparator() private view returns (bytes32) {
        return keccak256(abi.encode(_TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this)));
    }

    /**
     * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
     * function returns the hash of the fully encoded EIP712 message for this domain.
     *
     * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
     *
     * ```solidity
     * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
     *     keccak256("Mail(address to,string contents)"),
     *     mailTo,
     *     keccak256(bytes(mailContents))
     * )));
     * address signer = ECDSA.recover(digest, signature);
     * ```
     */
    function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
        return ECDSAUpgradeable.toTypedDataHash(_domainSeparatorV4(), structHash);
    }

    /**
     * @dev See {EIP-5267}.
     *
     * _Available since v4.9._
     */
    function eip712Domain()
        public
        view
        virtual
        override
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        )
    {
        // If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized
        // and the EIP712 domain is not reliable, as it will be missing name and version.
        require(_hashedName == 0 && _hashedVersion == 0, "EIP712: Uninitialized");

        return (
            hex"0f", // 01111
            _EIP712Name(),
            _EIP712Version(),
            block.chainid,
            address(this),
            bytes32(0),
            new uint256[](0)
        );
    }

    /**
     * @dev The name parameter for the EIP712 domain.
     *
     * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
     * are a concern.
     */
    function _EIP712Name() internal virtual view returns (string memory) {
        return _name;
    }

    /**
     * @dev The version parameter for the EIP712 domain.
     *
     * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
     * are a concern.
     */
    function _EIP712Version() internal virtual view returns (string memory) {
        return _version;
    }

    /**
     * @dev The hash of the name parameter for the EIP712 domain.
     *
     * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead.
     */
    function _EIP712NameHash() internal view returns (bytes32) {
        string memory name = _EIP712Name();
        if (bytes(name).length > 0) {
            return keccak256(bytes(name));
        } else {
            // If the name is empty, the contract may have been upgraded without initializing the new storage.
            // We return the name hash in storage if non-zero, otherwise we assume the name is empty by design.
            bytes32 hashedName = _hashedName;
            if (hashedName != 0) {
                return hashedName;
            } else {
                return keccak256("");
            }
        }
    }

    /**
     * @dev The hash of the version parameter for the EIP712 domain.
     *
     * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead.
     */
    function _EIP712VersionHash() internal view returns (bytes32) {
        string memory version = _EIP712Version();
        if (bytes(version).length > 0) {
            return keccak256(bytes(version));
        } else {
            // If the version is empty, the contract may have been upgraded without initializing the new storage.
            // We return the version hash in storage if non-zero, otherwise we assume the version is empty by design.
            bytes32 hashedVersion = _hashedVersion;
            if (hashedVersion != 0) {
                return hashedVersion;
            } else {
                return keccak256("");
            }
        }
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[48] private __gap;
}

File 33 of 54 : CountersUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)

pragma solidity ^0.8.0;

/**
 * @title Counters
 * @author Matt Condon (@shrugs)
 * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
 * of elements in a mapping, issuing ERC721 ids, or counting request ids.
 *
 * Include with `using Counters for Counters.Counter;`
 */
library CountersUpgradeable {
    struct Counter {
        // This variable should never be directly accessed by users of the library: interactions must be restricted to
        // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
        // this feature: see https://github.com/ethereum/solidity/issues/4637
        uint256 _value; // default: 0
    }

    function current(Counter storage counter) internal view returns (uint256) {
        return counter._value;
    }

    function increment(Counter storage counter) internal {
        unchecked {
            counter._value += 1;
        }
    }

    function decrement(Counter storage counter) internal {
        uint256 value = counter._value;
        require(value > 0, "Counter: decrement overflow");
        unchecked {
            counter._value = value - 1;
        }
    }

    function reset(Counter storage counter) internal {
        counter._value = 0;
    }
}

File 34 of 54 : MathUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library MathUpgradeable {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

File 35 of 54 : SignedMathUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMathUpgradeable {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

File 36 of 54 : IERC165Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165Upgradeable {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

File 37 of 54 : ExponentialMovingAverageV8.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.28;

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

// solhint-disable not-rely-on-time

/// @dev See https://en.wikipedia.org/wiki/Exponential_smoothing
/// It is the same as `ExponentialMovingAverageV7` with `unchecked` scope.
library ExponentialMovingAverageV8 {
  /*************
   * Constants *
   *************/

  /// @dev The precision used to compute EMA.
  uint256 private constant PRECISION = 1e18;

  /***********
   * Structs *
   ***********/

  /// @dev Compiler will pack this into single `uint256`.
  /// @param lastTime The last timestamp when the storage is updated.
  /// @param sampleInterval The sampling time interval used in the EMA.
  /// @param lastValue The last value in the data sequence, with precision 1e18.
  /// @param lastEmaValue The last EMA value computed, with precision 1e18.
  struct EMAStorage {
    uint40 lastTime;
    uint24 sampleInterval;
    uint96 lastValue;
    uint96 lastEmaValue;
  }

  /// @dev Save value of EMA storage.
  /// @param s The EMA storage.
  /// @param value The new value, with precision 1e18.
  function saveValue(EMAStorage storage s, uint96 value) internal {
    s.lastEmaValue = uint96(emaValue(s));
    s.lastValue = value;
    s.lastTime = uint40(block.timestamp);
  }

  /// @dev Return the current ema value.
  /// @param s The EMA storage.
  function emaValue(EMAStorage storage s) internal view returns (uint256) {
    unchecked {
      if (uint256(s.lastTime) < block.timestamp) {
        uint256 dt = block.timestamp - uint256(s.lastTime);
        uint256 e = (dt * PRECISION) / s.sampleInterval;
        if (e > 41e18) {
          return s.lastValue;
        } else {
          uint256 alpha = uint256(LogExpMathV8.exp(-int256(e)));
          return (s.lastValue * (PRECISION - alpha) + s.lastEmaValue * alpha) / PRECISION;
        }
      } else {
        return s.lastEmaValue;
      }
    }
  }
}

File 38 of 54 : GroupStateHelper.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { DTokenRegistry } from "../declarations/DTokenRegistry.sol";
import { WordCodec } from "../libs/WordCodec.sol";
import { Address, AddressLibrary } from "../types/Address.sol";
import { Currency, CurrencyLibrary } from "../types/Currency.sol";
import { DefaultFeeParams, FeeParams, FeePermissions, CollateralInfo, GroupState } from "../types/CommonTypes.sol";
import { ITokenRegistry } from "../interfaces/ITokenRegistry.sol";

/**
 * @title GroupStateHelper
 * @notice Library for managing group settings and fee parameters in a space-efficient manner using bit packing
 * @dev Uses WordCodec for bit manipulation operations to store and retrieve various parameters
 */
type GroupSettings is bytes32;

library GroupStateHelper {
  using WordCodec for bytes32;
  using CurrencyLibrary for Currency;
  using AddressLibrary for address;

  // Group Settings bit layout (total 256 bits):
  uint256 private constant A_TOKEN_DECIMALS_OFFSET = 0; // [0-7]:    aToken decimals (8 bits)
  uint256 private constant X_TOKEN_DECIMALS_OFFSET = 8; // [8-15]:   xToken decimals (8 bits)
  uint256 private constant BASE_TOKEN_DECIMALS_OFFSET = 16; // [16-23]:  baseToken decimals (8 bits)
  uint256 private constant YIELD_BEARING_TOKEN_DECIMALS_OFFSET = 24; // [24-31]:  yieldBearingToken decimals (8 bits)
  uint256 private constant HOOK_PERMISSIONS_OFFSET = 32; // [32-47]:  hook permissions (16 bits)
  uint256 private constant FEE_PERMISSIONS_OFFSET = 48; // [48-49]:  fee permissions (2 bits)
  uint256 private constant FEE_MODEL_OFFSET = 50; // [50-57]:  fee model (8 bits)
  uint256 private constant WRAPPING_REQUIRED_OFFSET = 58; // [58]:     wrapping required (1 bit)
  uint256 private constant STABILITY_RATIO_OFFSET = 59; // [59-154]: stability ratio (96 bits)
  uint256 private constant STABILITY_TRIGGER_RATIO_OFFSET = 155; // [155-250]: stability triggering ratio (96 bits)

  // Fee Parameters bit layout (total 256 bits):
  uint256 private constant MAX_FEE_OFFSET = 0; // [0-15]:   max fee (16 bits)
  uint256 private constant MIN_FEE_OFFSET = 16; // [16-31]:  min fee (16 bits)
  uint256 private constant BASE_FEE_OFFSET = 32; // [32-47]:  base fee (16 bits)
  uint256 private constant YIELD_FEE_VT_OFFSET = 48; // [48-63]:  yield fee VT (16 bits)
  uint256 private constant YIELD_FEE_YT_OFFSET = 64; // [64-79]:  yield fee YT (16 bits)
  uint256 private constant REDEEM_FEE_YT_OFFSET = 80; // [80-103]: redeem fee YT (24 bits)
  uint256 private constant MINT_FEE_YT_OFFSET = 104; // [104-127]: mint fee YT (24 bits)
  uint256 private constant REDEEM_FEE_VT_OFFSET = 128; // [128-151]: redeem fee VT (24 bits)
  uint256 private constant MINT_FEE_VT_OFFSET = 152; // [152-175]: mint fee VT (24 bits)
  uint256 private constant STABILITY_MINT_FEE_VT_OFFSET = 176; // [176-191]: stability mint fee VT (16 bits)
  uint256 private constant STABILITY_MINT_FEE_YT_OFFSET = 192; // [192-207]: stability mint fee YT (16 bits)
  uint256 private constant STABILITY_REDEEM_FEE_VT_OFFSET = 208; // [208-223]: stability redeem fee VT (16 bits)
  uint256 private constant STABILITY_REDEEM_FEE_YT_OFFSET = 224; // [224-239]: stability redeem fee YT (16 bits)
  uint256 private constant PROTOCOL_FEE_OFFSET = 240; // [240-255]: protocol fee (16 bits)

  // Common bit lengths
  uint256 private constant LENGTH_1BIT = 1;
  uint256 private constant LENGTH_2BITS = 2;
  uint256 private constant LENGTH_8BITS = 8;
  uint256 private constant LENGTH_16BITS = 16;
  uint256 private constant LENGTH_24BITS = 24;
  uint256 private constant LENGTH_96BITS = 96;

  /**
   * @notice Retrieves the aToken decimals from group settings
   * @param groupSettings The packed group settings
   * @return The aToken decimals
   */
  function getATokenDecimals(GroupSettings groupSettings) internal pure returns (uint8) {
    return uint8(GroupSettings.unwrap(groupSettings).decodeUint(A_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS));
  }

  /**
   * @notice Retrieves the xToken decimals from group settings
   * @param groupSettings The packed group settings
   * @return The xToken decimals
   */
  function getXTokenDecimals(GroupSettings groupSettings) internal pure returns (uint8) {
    return uint8(GroupSettings.unwrap(groupSettings).decodeUint(X_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS));
  }

  /**
   * @notice Retrieves the baseToken decimals from group settings
   * @param groupSettings The packed group settings
   * @return The baseToken decimals
   */
  function getBaseTokenDecimals(GroupSettings groupSettings) internal pure returns (uint8) {
    return uint8(GroupSettings.unwrap(groupSettings).decodeUint(BASE_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS));
  }

  /**
   * @notice Retrieves the yieldBearingToken decimals from group settings
   * @param groupSettings The packed group settings
   * @return The yieldBearingToken decimals
   */
  function getYieldBearingTokenDecimals(GroupSettings groupSettings) internal pure returns (uint8) {
    return uint8(GroupSettings.unwrap(groupSettings).decodeUint(YIELD_BEARING_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS));
  }

  /**
   * @notice Retrieves the hook permissions from group settings
   * @param groupSettings The packed group settings
   * @return The hook permissions
   */
  function getHookPermissions(GroupSettings groupSettings) internal pure returns (uint16) {
    return uint16(GroupSettings.unwrap(groupSettings).decodeUint(HOOK_PERMISSIONS_OFFSET, LENGTH_16BITS));
  }

  /**
   * @notice Retrieves the fee permissions from group settings
   * @param groupSettings The packed group settings
   * @return FeePermissions struct containing permission flags
   */
  function getFeePermissions(GroupSettings groupSettings) internal pure returns (FeePermissions memory) {
    bytes32 raw = GroupSettings.unwrap(groupSettings);
    return
      FeePermissions({ isDynamic: raw.decodeBool(FEE_PERMISSIONS_OFFSET + 1), allowDelegation: raw.decodeBool(FEE_PERMISSIONS_OFFSET) });
  }

  /**
   * @notice Retrieves the fee model from group settings
   * @param groupSettings The packed group settings
   * @return The fee model
   */
  function getFeeModel(GroupSettings groupSettings) internal pure returns (uint8) {
    return uint8(GroupSettings.unwrap(groupSettings).decodeUint(FEE_MODEL_OFFSET, LENGTH_8BITS));
  }

  /**
   * @notice Checks if wrapping is required from group settings
   * @param groupSettings The packed group settings
   * @return True if wrapping is required, false otherwise
   */
  function isWrappingRequired(GroupSettings groupSettings) internal pure returns (bool) {
    return GroupSettings.unwrap(groupSettings).decodeBool(WRAPPING_REQUIRED_OFFSET);
  }

  /**
   * @notice Retrieves the stability ratio from group settings
   * @param groupSettings The packed group settings
   * @return The stability ratio (scaled by 1e18)
   */
  function getStabilityRatio(GroupSettings groupSettings) internal pure returns (uint96) {
    return uint96(GroupSettings.unwrap(groupSettings).decodeUint(STABILITY_RATIO_OFFSET, LENGTH_96BITS));
  }

  /**
   * @notice Retrieves the stability triggering ratio from group settings
   * @param groupSettings The packed group settings
   * @return The stability triggering ratio (scaled by 1e18)
   */
  function getStabilityTriggeringRatio(GroupSettings groupSettings) internal pure returns (uint96) {
    return uint96(GroupSettings.unwrap(groupSettings).decodeUint(STABILITY_TRIGGER_RATIO_OFFSET, LENGTH_96BITS));
  }

  /**
   * @notice Sets the aToken decimals in group settings
   * @param groupSettings Current group settings
   * @param decimals The new aToken decimals
   * @return Updated group settings
   */
  function setATokenDecimals(GroupSettings groupSettings, uint8 decimals) internal pure returns (GroupSettings) {
    bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(uint256(decimals), A_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS);
    return GroupSettings.wrap(updated);
  }

  /**
   * @notice Sets the xToken decimals in group settings
   * @param groupSettings Current group settings
   * @param decimals The new xToken decimals
   * @return Updated group settings
   */
  function setXTokenDecimals(GroupSettings groupSettings, uint8 decimals) internal pure returns (GroupSettings) {
    bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(uint256(decimals), X_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS);
    return GroupSettings.wrap(updated);
  }

  /**
   * @notice Sets the baseToken decimals in group settings
   * @param groupSettings Current group settings
   * @param decimals The new baseToken decimals
   * @return Updated group settings
   */
  function setBaseTokenDecimals(GroupSettings groupSettings, uint8 decimals) internal pure returns (GroupSettings) {
    bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(uint256(decimals), BASE_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS);
    return GroupSettings.wrap(updated);
  }

  /**
   * @notice Sets the yieldBearingToken decimals in group settings
   * @param groupSettings Current group settings
   * @param decimals The new yieldBearingToken decimals
   * @return Updated group settings
   */
  function setYieldBearingTokenDecimals(GroupSettings groupSettings, uint8 decimals) internal pure returns (GroupSettings) {
    bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(uint256(decimals), YIELD_BEARING_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS);
    return GroupSettings.wrap(updated);
  }

  /**
   * @notice Sets the hook permissions in group settings
   * @param groupSettings Current group settings
   * @param hookPermissions The new hook permissions
   * @return Updated group settings
   */
  function setHookPermissions(GroupSettings groupSettings, uint16 hookPermissions) internal pure returns (GroupSettings) {
    bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(uint256(hookPermissions), HOOK_PERMISSIONS_OFFSET, LENGTH_16BITS);
    return GroupSettings.wrap(updated);
  }

  /**
   * @notice Sets the fee permissions in group settings
   * @param groupSettings Current group settings
   * @param permissions The new fee permissions
   * @return Updated group settings
   */
  function setFeePermissions(GroupSettings groupSettings, FeePermissions memory permissions) internal pure returns (GroupSettings) {
    bytes32 raw = GroupSettings.unwrap(groupSettings);
    raw = raw.insertBool(permissions.allowDelegation, FEE_PERMISSIONS_OFFSET);
    raw = raw.insertBool(permissions.isDynamic, FEE_PERMISSIONS_OFFSET + 1);
    return GroupSettings.wrap(raw);
  }

  /**
   * @notice Sets the fee model in group settings
   * @param groupSettings Current group settings
   * @param feeModel The new fee model
   * @return Updated group settings
   */
  function setFeeModel(GroupSettings groupSettings, uint8 feeModel) internal pure returns (GroupSettings) {
    bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(uint256(feeModel), FEE_MODEL_OFFSET, LENGTH_8BITS);
    return GroupSettings.wrap(updated);
  }

  /**
   * @notice Sets the wrapping required flag in group settings
   * @param groupSettings Current group settings
   * @param wrappingRequired The new wrapping required flag
   * @return Updated group settings
   */
  function setWrappingRequired(GroupSettings groupSettings, bool wrappingRequired) internal pure returns (GroupSettings) {
    bytes32 updated = GroupSettings.unwrap(groupSettings).insertBool(wrappingRequired, WRAPPING_REQUIRED_OFFSET);
    return GroupSettings.wrap(updated);
  }

  /**
   * @notice Sets the stability ratio in group settings
   * @param groupSettings Current group settings
   * @param stabilityRatio The new stability ratio (scaled by 1e18)
   * @return Updated group settings
   */
  function setStabilityRatio(GroupSettings groupSettings, uint96 stabilityRatio) internal pure returns (GroupSettings) {
    bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(uint256(stabilityRatio), STABILITY_RATIO_OFFSET, LENGTH_96BITS);
    return GroupSettings.wrap(updated);
  }

  /**
   * @notice Sets the stability triggering ratio in group settings
   * @param groupSettings Current group settings
   * @param stabilityTriggeringRatio The new stability triggering ratio (scaled by 1e18)
   * @return Updated group settings
   */
  function setStabilityTriggeringRatio(GroupSettings groupSettings, uint96 stabilityTriggeringRatio) internal pure returns (GroupSettings) {
    bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(
      uint256(stabilityTriggeringRatio),
      STABILITY_TRIGGER_RATIO_OFFSET,
      LENGTH_96BITS
    );
    return GroupSettings.wrap(updated);
  }

  /**
   * @notice Retrieves all fee parameters from packed fees
   * @param feesPacked The packed fees
   * @return FeeParams struct containing all fee parameters
   */
  function getFeeParams(bytes32 feesPacked) internal pure returns (FeeParams memory) {
    return
      FeeParams({
        mintFeeVT: uint24(feesPacked.decodeUint(MINT_FEE_VT_OFFSET, LENGTH_24BITS)),
        redeemFeeVT: uint24(feesPacked.decodeUint(REDEEM_FEE_VT_OFFSET, LENGTH_24BITS)),
        mintFeeYT: uint24(feesPacked.decodeUint(MINT_FEE_YT_OFFSET, LENGTH_24BITS)),
        redeemFeeYT: uint24(feesPacked.decodeUint(REDEEM_FEE_YT_OFFSET, LENGTH_24BITS)),
        // Upcast 16-bit stability fees to 24-bit
        stabilityMintFeeVT: uint24(uint16(feesPacked.decodeUint(STABILITY_MINT_FEE_VT_OFFSET, LENGTH_16BITS))),
        stabilityMintFeeYT: uint24(uint16(feesPacked.decodeUint(STABILITY_MINT_FEE_YT_OFFSET, LENGTH_16BITS))),
        stabilityRedeemFeeVT: uint24(uint16(feesPacked.decodeUint(STABILITY_REDEEM_FEE_VT_OFFSET, LENGTH_16BITS))),
        stabilityRedeemFeeYT: uint24(uint16(feesPacked.decodeUint(STABILITY_REDEEM_FEE_YT_OFFSET, LENGTH_16BITS))),
        // Upcast 16-bit yield fees to 24-bit
        yieldFeeVT: uint24(uint16(feesPacked.decodeUint(YIELD_FEE_VT_OFFSET, LENGTH_16BITS))),
        yieldFeeYT: uint24(uint16(feesPacked.decodeUint(YIELD_FEE_YT_OFFSET, LENGTH_16BITS))),
        protocolFee: uint16(feesPacked.decodeUint(PROTOCOL_FEE_OFFSET, LENGTH_16BITS))
      });
  }

  /**
   * @notice Retrieves the default fee parameters from packed fees
   * @param feesPacked The packed fees
   * @return DefaultFeeParams struct containing min, max and base fees
   */
  function getDefaultFeeParams(bytes32 feesPacked) internal pure returns (DefaultFeeParams memory) {
    return
      DefaultFeeParams({
        baseFee: uint24(feesPacked.decodeUint(BASE_FEE_OFFSET, LENGTH_16BITS)),
        maxFee: uint16(feesPacked.decodeUint(MAX_FEE_OFFSET, LENGTH_16BITS)),
        minFee: uint16(feesPacked.decodeUint(MIN_FEE_OFFSET, LENGTH_16BITS))
      });
  }

  /**
   * @notice Retrieves the max fee from packed fees
   * @param feesPacked The packed fees
   * @return The max fee value (24-bit)
   */
  function getMaxFee(bytes32 feesPacked) internal pure returns (uint24) {
    return uint24(uint16(feesPacked.decodeUint(MAX_FEE_OFFSET, LENGTH_16BITS)));
  }

  /**
   * @notice Retrieves the min fee from packed fees
   * @param feesPacked The packed fees
   * @return The min fee value (24-bit)
   */
  function getMinFee(bytes32 feesPacked) internal pure returns (uint24) {
    return uint24(uint16(feesPacked.decodeUint(MIN_FEE_OFFSET, LENGTH_16BITS)));
  }

  /**
   * @notice Retrieves the base fee from packed fees
   * @param feesPacked The packed fees
   * @return The base fee value (24-bit)
   */
  function getBaseFee(bytes32 feesPacked) internal pure returns (uint24) {
    return uint24(uint16(feesPacked.decodeUint(BASE_FEE_OFFSET, LENGTH_16BITS)));
  }

  /**
   * @notice Retrieves the yield fee VT from packed fees
   * @param feesPacked The packed fees
   * @return The yield fee VT value (24-bit)
   */
  function getYieldFeeVT(bytes32 feesPacked) internal pure returns (uint24) {
    return uint24(uint16(feesPacked.decodeUint(YIELD_FEE_VT_OFFSET, LENGTH_16BITS)));
  }

  /**
   * @notice Retrieves the yield fee YT from packed fees
   * @param feesPacked The packed fees
   * @return The yield fee YT value (24-bit)
   */
  function getYieldFeeYT(bytes32 feesPacked) internal pure returns (uint24) {
    return uint24(uint16(feesPacked.decodeUint(YIELD_FEE_YT_OFFSET, LENGTH_16BITS)));
  }

  /**
   * @notice Retrieves the mint fee VT from packed fees
   * @param feesPacked The packed fees
   * @return The mint fee VT value (24-bit)
   */
  function getMintFeeVT(bytes32 feesPacked) internal pure returns (uint24) {
    return uint24(feesPacked.decodeUint(MINT_FEE_VT_OFFSET, LENGTH_24BITS));
  }

  /**
   * @notice Retrieves the redeem fee VT from packed fees
   * @param feesPacked The packed fees
   * @return The redeem fee VT value (24-bit)
   */
  function getRedeemFeeVT(bytes32 feesPacked) internal pure returns (uint24) {
    return uint24(feesPacked.decodeUint(REDEEM_FEE_VT_OFFSET, LENGTH_24BITS));
  }

  /**
   * @notice Retrieves the mint fee YT from packed fees
   * @param feesPacked The packed fees
   * @return The mint fee YT value (24-bit)
   */
  function getMintFeeYT(bytes32 feesPacked) internal pure returns (uint24) {
    return uint24(feesPacked.decodeUint(MINT_FEE_YT_OFFSET, LENGTH_24BITS));
  }

  /**
   * @notice Retrieves the redeem fee YT from packed fees
   * @param feesPacked The packed fees
   * @return The redeem fee YT value (24-bit)
   */
  function getRedeemFeeYT(bytes32 feesPacked) internal pure returns (uint24) {
    return uint24(feesPacked.decodeUint(REDEEM_FEE_YT_OFFSET, LENGTH_24BITS));
  }

  /**
   * @notice Retrieves the stability mint fee VT from packed fees
   * @param feesPacked The packed fees
   * @return The stability mint fee VT value (24-bit)
   */
  function getStabilityMintFeeVT(bytes32 feesPacked) internal pure returns (uint24) {
    return uint24(uint16(feesPacked.decodeUint(STABILITY_MINT_FEE_VT_OFFSET, LENGTH_16BITS)));
  }

  /**
   * @notice Retrieves the stability mint fee YT from packed fees
   * @param feesPacked The packed fees
   * @return The stability mint fee YT value (24-bit)
   */
  function getStabilityMintFeeYT(bytes32 feesPacked) internal pure returns (uint24) {
    return uint24(uint16(feesPacked.decodeUint(STABILITY_MINT_FEE_YT_OFFSET, LENGTH_16BITS)));
  }

  /**
   * @notice Retrieves the stability redeem fee VT from packed fees
   * @param feesPacked The packed fees
   * @return The stability redeem fee VT value (24-bit)
   */
  function getStabilityRedeemFeeVT(bytes32 feesPacked) internal pure returns (uint24) {
    return uint24(uint16(feesPacked.decodeUint(STABILITY_REDEEM_FEE_VT_OFFSET, LENGTH_16BITS)));
  }

  /**
   * @notice Retrieves the stability redeem fee YT from packed fees
   * @param feesPacked The packed fees
   * @return The stability redeem fee YT value (24-bit)
   */
  function getStabilityRedeemFeeYT(bytes32 feesPacked) internal pure returns (uint24) {
    return uint24(uint16(feesPacked.decodeUint(STABILITY_REDEEM_FEE_YT_OFFSET, LENGTH_16BITS)));
  }

  /**
   * @notice Retrieves the protocol fee from packed fees
   * @param feesPacked The packed fees
   * @return The protocol fee value (24-bit)
   */
  function getProtocolFee(bytes32 feesPacked) internal pure returns (uint24) {
    return uint24(uint16(feesPacked.decodeUint(PROTOCOL_FEE_OFFSET, LENGTH_16BITS)));
  }

  /**
   * @notice Sets the max fee in packed fees
   * @param feesPacked Current packed fees
   * @param maxFee The new max fee value (truncated to 16-bit)
   * @return Updated packed fees
   */
  function setMaxFee(bytes32 feesPacked, uint24 maxFee) internal pure returns (bytes32) {
    return feesPacked.insertUint(uint256(uint16(maxFee)), MAX_FEE_OFFSET, LENGTH_16BITS);
  }

  /**
   * @notice Sets the min fee in packed fees
   * @param feesPacked Current packed fees
   * @param minFee The new min fee value (truncated to 16-bit)
   * @return Updated packed fees
   */
  function setMinFee(bytes32 feesPacked, uint24 minFee) internal pure returns (bytes32) {
    return feesPacked.insertUint(uint256(uint16(minFee)), MIN_FEE_OFFSET, LENGTH_16BITS);
  }

  /**
   * @notice Sets the base fee in packed fees
   * @param feesPacked Current packed fees
   * @param baseFee The new base fee value (truncated to 16-bit)
   * @return Updated packed fees
   */
  function setBaseFee(bytes32 feesPacked, uint24 baseFee) internal pure returns (bytes32) {
    return feesPacked.insertUint(uint256(uint16(baseFee)), BASE_FEE_OFFSET, LENGTH_16BITS);
  }

  /**
   * @notice Sets the yield fee VT in packed fees
   * @param feesPacked Current packed fees
   * @param yieldFeeVT The new yield fee VT value (truncated to 16-bit)
   * @return Updated packed fees
   */
  function setYieldFeeVT(bytes32 feesPacked, uint24 yieldFeeVT) internal pure returns (bytes32) {
    return feesPacked.insertUint(uint256(uint16(yieldFeeVT)), YIELD_FEE_VT_OFFSET, LENGTH_16BITS);
  }

  /**
   * @notice Sets the yield fee YT in packed fees
   * @param feesPacked Current packed fees
   * @param yieldFeeYT The new yield fee YT value (truncated to 16-bit)
   * @return Updated packed fees
   */
  function setYieldFeeYT(bytes32 feesPacked, uint24 yieldFeeYT) internal pure returns (bytes32) {
    return feesPacked.insertUint(uint256(uint16(yieldFeeYT)), YIELD_FEE_YT_OFFSET, LENGTH_16BITS);
  }

  /**
   * @notice Sets the mint fee VT in packed fees
   * @param feesPacked Current packed fees
   * @param mintFeeVT The new mint fee VT value
   * @return Updated packed fees
   */
  function setMintFeeVT(bytes32 feesPacked, uint24 mintFeeVT) internal pure returns (bytes32) {
    return feesPacked.insertUint(uint256(mintFeeVT), MINT_FEE_VT_OFFSET, LENGTH_24BITS);
  }

  /**
   * @notice Sets the redeem fee VT in packed fees
   * @param feesPacked Current packed fees
   * @param redeemFeeVT The new redeem fee VT value
   * @return Updated packed fees
   */
  function setRedeemFeeVT(bytes32 feesPacked, uint24 redeemFeeVT) internal pure returns (bytes32) {
    return feesPacked.insertUint(uint256(redeemFeeVT), REDEEM_FEE_VT_OFFSET, LENGTH_24BITS);
  }

  /**
   * @notice Sets the mint fee YT in packed fees
   * @param feesPacked Current packed fees
   * @param mintFeeYT The new mint fee YT value
   * @return Updated packed fees
   */
  function setMintFeeYT(bytes32 feesPacked, uint24 mintFeeYT) internal pure returns (bytes32) {
    return feesPacked.insertUint(uint256(mintFeeYT), MINT_FEE_YT_OFFSET, LENGTH_24BITS);
  }

  /**
   * @notice Sets the redeem fee YT in packed fees
   * @param feesPacked Current packed fees
   * @param redeemFeeYT The new redeem fee YT value
   * @return Updated packed fees
   */
  function setRedeemFeeYT(bytes32 feesPacked, uint24 redeemFeeYT) internal pure returns (bytes32) {
    return feesPacked.insertUint(uint256(redeemFeeYT), REDEEM_FEE_YT_OFFSET, LENGTH_24BITS);
  }

  /**
   * @notice Sets the stability mint fee VT in packed fees
   * @param feesPacked Current packed fees
   * @param stabilityMintFeeVT The new stability mint fee VT value (truncated to 16-bit)
   * @return Updated packed fees
   */
  function setStabilityMintFeeVT(bytes32 feesPacked, uint24 stabilityMintFeeVT) internal pure returns (bytes32) {
    return feesPacked.insertUint(uint256(uint16(stabilityMintFeeVT)), STABILITY_MINT_FEE_VT_OFFSET, LENGTH_16BITS);
  }

  /**
   * @notice Sets the stability mint fee YT in packed fees
   * @param feesPacked Current packed fees
   * @param stabilityMintFeeYT The new stability mint fee YT value (truncated to 16-bit)
   * @return Updated packed fees
   */
  function setStabilityMintFeeYT(bytes32 feesPacked, uint24 stabilityMintFeeYT) internal pure returns (bytes32) {
    return feesPacked.insertUint(uint256(uint16(stabilityMintFeeYT)), STABILITY_MINT_FEE_YT_OFFSET, LENGTH_16BITS);
  }

  /**
   * @notice Sets the stability redeem fee VT in packed fees
   * @param feesPacked Current packed fees
   * @param stabilityRedeemFeeVT The new stability redeem fee VT value (truncated to 16-bit)
   * @return Updated packed fees
   */
  function setStabilityRedeemFeeVT(bytes32 feesPacked, uint24 stabilityRedeemFeeVT) internal pure returns (bytes32) {
    return feesPacked.insertUint(uint256(uint16(stabilityRedeemFeeVT)), STABILITY_REDEEM_FEE_VT_OFFSET, LENGTH_16BITS);
  }

  /**
   * @notice Sets the stability redeem fee YT in packed fees
   * @param feesPacked Current packed fees
   * @param stabilityRedeemFeeYT The new stability redeem fee YT value (truncated to 16-bit)
   * @return Updated packed fees
   */
  function setStabilityRedeemFeeYT(bytes32 feesPacked, uint24 stabilityRedeemFeeYT) internal pure returns (bytes32) {
    return feesPacked.insertUint(uint256(uint16(stabilityRedeemFeeYT)), STABILITY_REDEEM_FEE_YT_OFFSET, LENGTH_16BITS);
  }

  /**
   * @notice Sets the protocol fee in packed fees
   * @param feesPacked Current packed fees
   * @param protocolFee The new protocol fee value (truncated to 16-bit)
   * @return Updated packed fees
   */
  function setProtocolFee(bytes32 feesPacked, uint24 protocolFee) internal pure returns (bytes32) {
    return feesPacked.insertUint(uint256(uint16(protocolFee)), PROTOCOL_FEE_OFFSET, LENGTH_16BITS);
  }

  // Group Core Components Getters

  /**
   * @notice Retrieves the hook contract from group state
   * @param groupState The group state
   * @return The hook contract address wrapper
   */
  function getHookContract(GroupState memory groupState) internal pure returns (Address) {
    return groupState.hookContract;
  }
}

File 39 of 54 : TreasuryStateLibrary.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { Currency, CurrencyLibrary } from "../types/Currency.sol";
import { Address, AddressLibrary } from "../types/Address.sol";
import { FxStableMath } from "../libs/math/FxStableMath.sol";
import { ExponentialMovingAverageV8 } from "../libs/math/ExponentialMovingAverageV8.sol";
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { IPriceOracle } from "../interfaces/IPriceOracle.sol";
import { DTreasury } from "../declarations/DTreasury.sol";
import { GroupStateHelper, GroupSettings } from "../types/GroupStateHelper.sol";
import { IAToken } from "../interfaces/IAToken.sol";
import { CustomRevert } from "../libs/CustomRevert.sol";
import { IDEXRouter } from "../interfaces/IDEXRouter.sol";
import { IStrategy } from "../interfaces/IStrategy.sol";
import { GroupState } from "../types/CommonTypes.sol";
import { FullMath } from "../libs/math/FullMath.sol";

/**
 * @title TreasuryStateLibrary
 * @notice Library for managing treasury state and calculations.
 */
library TreasuryStateLibrary {
  using CurrencyLibrary for Currency;
  using AddressLibrary for Address;
  using FxStableMath for FxStableMath.SwapState;
  using ExponentialMovingAverageV8 for ExponentialMovingAverageV8.EMAStorage;
  using GroupStateHelper for GroupSettings;
  using CustomRevert for bytes4;

  error ErrorInvalidTwapPrice();
  error ErrorSwapFailed();
  error ErrorWithdrawFromStrategy();
  error StrategyUnderflow();

  event BaseTokenCapsUpdated(uint256 newCaps);
  event BaseTokenPriceUpdated(uint256 newPrice);
  event SwapExecuted(address indexed fromToken, address indexed toToken, uint256 amountIn, uint256 amountOut);
  event BaseTokenTransferred(address indexed recipient, uint256 amount);
  event StrategyWithdrawal(address indexed strategy, uint256 amount);
  event EMALeverageRatioUpdated(uint256 newEMAValue);
  event GroupEMALeverageRatioInitialized(uint256 initialValue);

  uint256 private constant PRECISION = 1e18;

  /**
   * @notice Updates the base token caps in the treasury state.
   * @param self The treasury state storage.
   * @param newCaps The new base token caps.
   */
  function updateBaseTokenCaps(DTreasury.TreasuryState storage self, uint256 newCaps) internal {
    self.baseTokenCaps = newCaps;
    emit BaseTokenCapsUpdated(newCaps);
  }

  /**
   * @notice Updates the base token price in the treasury state.
   * @param self The treasury state storage.
   * @param newPrice The new base token price.
   */
  function updateBaseTokenPrice(DTreasury.TreasuryState storage self, uint256 newPrice) internal {
    self.baseTokenPrice = newPrice;
    emit BaseTokenPriceUpdated(newPrice);
  }

  /**
   * @notice Loads the swap state with proper precision handling.
   * @param self The treasury state storage.
   * @param group The group state.
   * @return _state The loaded swap state.
   */
  function loadSwapState(
    DTreasury.TreasuryState storage self,
    GroupState memory group
  ) internal view returns (FxStableMath.SwapState memory _state) {
    // Fetch decimals
    uint8 baseTokenDecimals = GroupSettings.wrap(group.groupSettings).getBaseTokenDecimals();
    uint8 aTokenDecimals = GroupSettings.wrap(group.groupSettings).getATokenDecimals();
    uint8 xTokenDecimals = GroupSettings.wrap(group.groupSettings).getXTokenDecimals();

    // Normalize baseSupply to 18 decimals
    _state.baseSupply = normalizeDecimals(self.totalBaseTokens, baseTokenDecimals);

    // Fetch base token price (already in 18 decimals)
    (_state.baseTwapNav, _state.baseNav) = fetchBaseTokenPrice(self, group);

    if (_state.baseSupply == 0) {
      _state.aNav = PRECISION;
      _state.xNav = PRECISION;
    } else {
      // Fetch token supplies and normalize to 18 decimals
      uint256 aSupplyRaw = IERC20Upgradeable(group.core.aToken.toAddress()).totalSupply();
      uint256 xSupplyRaw = IERC20Upgradeable(group.core.xToken.toAddress()).totalSupply();

      _state.aSupply = normalizeDecimals(aSupplyRaw, aTokenDecimals);
      _state.xSupply = normalizeDecimals(xSupplyRaw, xTokenDecimals);

      // Fetch aNav (assuming it's already in 18 decimals)
      _state.beta = IAToken(group.core.aToken.toAddress()).beta();

      if (_state.beta) {
        _state.aNav = IAToken(group.core.aToken.toAddress()).nav();
      } else {
        _state.aNav = PRECISION;
      }
          
      if (_state.xSupply == 0) {
        // No xToken, treat the nav of xToken as 1.0
        _state.xNav = PRECISION;
      } else {
        uint256 _baseVal = _state.baseSupply * _state.baseNav;
        uint256 _aVal = _state.aSupply * _state.aNav;
        if (_baseVal >= _aVal) {
          _state.xNav = (_baseVal - _aVal) / _state.xSupply;
        } else {
          // Under-collateralized
          _state.xNav = 0;
        }
      }
    }
  }

  /**
   * @notice Fetches the base token price from the price oracle.
   * @param group The group state.
   * @return _twap The time-weighted average price.
   * @return _price The selected price
   */
  function fetchBaseTokenPrice(
    DTreasury.TreasuryState storage /*self*/,
    GroupState memory group
  ) internal view returns (uint256 _twap, uint256 _price) {
    bool isValid;
    address priceOracle = group.extended.priceOracle.toAddress();
    (isValid, _price) = IPriceOracle(priceOracle).getPrice(group.core.baseToken.toAddress());

    if (!isValid || _price == 0) ErrorInvalidTwapPrice.selector.revertWith();

    // Prices are already in 18 decimals
    return (_price, _price);
  }

  /**
   * @notice Checks if the treasury is under-collateralized.
   * @param self The treasury state storage.
   * @param group The group state.
   * @return True if under-collateralized, false otherwise.
   */
  function isUnderCollateral(DTreasury.TreasuryState storage self, GroupState memory group) internal view returns (bool) {
    FxStableMath.SwapState memory _state = loadSwapState(self, group);
    return _state.xNav == 0;
  }

  /**
   * @notice Gets the collateral ratio of the treasury.
   * @param self The treasury state storage.
   * @param group The group state.
   * @return The collateral ratio.
   */
  function getCollateralRatio(DTreasury.TreasuryState storage self, GroupState memory group) internal view returns (uint256) {
    FxStableMath.SwapState memory _state = loadSwapState(self, group);

    if (_state.baseSupply == 0) return PRECISION;
    if (_state.aSupply == 0 || _state.aNav == 0) return PRECISION * PRECISION;

    return FullMath.mulDiv(_state.baseSupply * _state.baseNav, PRECISION, _state.aSupply * _state.aNav);
  }

  /**
   * @notice Calculates the maximum mintable AToken based on the new collateral ratio.
   * @param self The treasury state storage.
   * @param group The group state.
   * @param _newCollateralRatio The new desired collateral ratio.
   * @return _maxBaseIn The maximum base tokens that can be input.
   * @return _maxATokenMintable The maximum AToken that can be minted.
   */
  function maxMintableAToken(
    DTreasury.TreasuryState storage self,
    GroupState memory group,
    uint256 _newCollateralRatio
  ) internal view returns (uint256 _maxBaseIn, uint256 _maxATokenMintable) {
    FxStableMath.SwapState memory _state = loadSwapState(self, group);
    (_maxBaseIn, _maxATokenMintable) = _state.maxMintableAToken(_newCollateralRatio);
  }

  /**
   * @notice Calculates the maximum redeemable AToken based on the new collateral ratio.
   * @param self The treasury state storage.
   * @param group The group state.
   * @param _newCollateralRatio The new desired collateral ratio.
   * @return _maxBaseOut The maximum base tokens that can be output.
   * @return _maxATokenRedeemable The maximum AToken that can be redeemed.
   */
  function maxRedeemableAToken(
    DTreasury.TreasuryState storage self,
    GroupState memory group,
    uint256 _newCollateralRatio
  ) internal view returns (uint256 _maxBaseOut, uint256 _maxATokenRedeemable) {
    FxStableMath.SwapState memory _state = loadSwapState(self, group);
    (_maxBaseOut, _maxATokenRedeemable) = _state.maxRedeemableAToken(_newCollateralRatio);
  }
  /**
   * @notice Updates the Exponential Moving Average (EMA) of the leverage ratio.
   * @param self The treasury state storage.
   * @param _state The current swap state.
   */
  function updateEMALeverageRatio(DTreasury.TreasuryState storage self, FxStableMath.SwapState memory _state) internal {
    uint256 _ratio = _state.leverageRatio();
    self.emaLeverageRatio.saveValue(uint96(_ratio));
    emit EMALeverageRatioUpdated(_ratio);
  }

  function getEMAValue(DTreasury.TreasuryState storage self) internal view returns (uint256) {
    return self.emaLeverageRatio.emaValue();
  }

  function initializeGroupEMALeverageRatio(DTreasury.TreasuryState storage self) internal {
    self.emaLeverageRatio.lastTime = uint40(block.timestamp);
    self.emaLeverageRatio.lastValue = uint96(PRECISION * 2);
    self.emaLeverageRatio.lastEmaValue = uint96(PRECISION * 2);
    self.emaLeverageRatio.sampleInterval = 1 days;

    emit GroupEMALeverageRatioInitialized(PRECISION * 2);
  }

  /**
   * @dev Normalizes a token amount to 18 decimals.
   * @param amount The amount to normalize.
   * @param tokenDecimals The decimals of the token.
   * @return The normalized amount.
   */
  function normalizeDecimals(uint256 amount, uint8 tokenDecimals) internal pure returns (uint256) {
    if (tokenDecimals == 18) {
      return amount;
    }

    uint256 factor;
    if (tokenDecimals < 18) {
      factor = 10 ** (18 - tokenDecimals);
      return amount * factor;
    } else {
      factor = 10 ** (tokenDecimals - 18);
      return amount / factor;
    }
  }

  /**
   * @dev Denormalizes a token amount from 18 decimals to the token's decimals.
   * @param amount The amount to denormalize.
   * @param tokenDecimals The decimals of the token.
   * @return The denormalized amount.
   */
  function denormalizeDecimals(uint256 amount, uint8 tokenDecimals) internal pure returns (uint256) {
    if (tokenDecimals == 18) {
      return amount;
    }

    uint256 factor;
    if (tokenDecimals < 18) {
      factor = 10 ** (18 - tokenDecimals);
      return amount / factor;
    } else {
      factor = 10 ** (tokenDecimals - 18);
      return amount * factor;
    }
  }

  /**
   * @notice Transfers base tokens to the recipient, withdrawing from strategy if necessary.
   * @param baseToken The base token.
   * @param strategyAddress The strategy address.
   * @param treasuryState The treasury state.
   * @param amount The amount of base tokens to transfer.
   * @param recipient The recipient address.
   */
  function transferBaseToken(
    address self,
    uint8 baseTokenDecimals,
    Currency baseToken,
    address strategyAddress,
    DTreasury.TreasuryState storage treasuryState,
    uint256 amount,
    address recipient
  ) internal {
    uint256 amountNormalized = normalizeDecimals(amount, baseTokenDecimals);

    uint256 balance = baseToken.balanceOf(self);
    uint256 balanceNormalized = normalizeDecimals(balance, baseTokenDecimals);

    if (balanceNormalized < amountNormalized) {
      uint256 diff = amountNormalized - balanceNormalized;

      if (diff == 0) return;

      IStrategy strategy = IStrategy(strategyAddress);

      bool success = strategy.withdrawToTreasury(diff);

      if (!success) ErrorWithdrawFromStrategy.selector.revertWith();

      if (treasuryState.strategyUnderlying < diff) StrategyUnderflow.selector.revertWith();
      treasuryState.strategyUnderlying -= diff;

      balance = baseToken.balanceOf(self);
      balanceNormalized = normalizeDecimals(balance, baseTokenDecimals);

      if (amountNormalized > balanceNormalized) {
        amountNormalized = balanceNormalized;
        amount = denormalizeDecimals(amountNormalized, baseTokenDecimals);
      }
    }

    treasuryState.totalBaseTokens -= amountNormalized;
    baseToken.safeTransfer(recipient, amount);
    emit BaseTokenTransferred(recipient, amount);
  }

  /**
   * @notice Swaps tokens using the specified router.
   * @param self The address of the contract using this library
   * @param swapRouter The address of the swap router.
   * @param from The address of the token to swap from.
   * @param to The address of the token to swap to.
   * @param amount The amount of tokens to swap.
   * @param minOutAmount The minimum acceptable amount of tokens to receive.
   * @return swappedAmount The amount of tokens received after the swap.
   */
  function swapTokens(
    address self,
    address swapRouter,
    address from,
    address to,
    uint256 amount,
    uint256 minOutAmount
  ) internal returns (uint256 swappedAmount) {
    IDEXRouter router = IDEXRouter(swapRouter);

    uint256 deadline = block.timestamp + 30;

    address[] memory path = new address[](2);
    path[0] = from;
    path[1] = to;

    Currency fromToken = Currency.wrap(from);

    uint256 currentAllowance = fromToken.allowance(self, swapRouter);
    if (currentAllowance < amount) {
      fromToken.safeIncreaseAllowance(swapRouter, amount - currentAllowance);
    }

    swappedAmount = _executeSwap(router, amount, minOutAmount, path, self, deadline);

    if (swappedAmount < minOutAmount) {
      ErrorSwapFailed.selector.revertWith();
    }

    emit SwapExecuted(from, to, amount, swappedAmount);

    return swappedAmount;
  }

  function _executeSwap(
    IDEXRouter router,
    uint256 amount,
    uint256 minOutAmount,
    address[] memory path,
    address to,
    uint256 deadline
  ) private returns (uint256) {
    try router.swapExactTokensForTokens(amount, minOutAmount, path, to, deadline) returns (uint256[] memory amounts) {
      return amounts[amounts.length - 1];
    } catch {
      ErrorSwapFailed.selector.revertWith();
    }
  }
}

File 40 of 54 : IWToken.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { GroupId } from "../types/GroupId.sol";

/**
 * @title IWToken
 * @notice Interface for the WToken contract, enabling wrapping and unwrapping of an underlying token.
 */
interface IWToken is IERC20Upgradeable {
  /**
   * @dev Emitted when tokens are wrapped into WTokens.
   * @param user The address of the user wrapping tokens.
   * @param underlyingAmount The amount of underlying tokens wrapped.
   * @param amount The amount of tokens wrapped.
   */
  event TokenWrapped(address indexed user, uint256 indexed underlyingAmount, uint256 indexed amount);

  /**
   * @dev Emitted when WTokens are unwrapped into underlying tokens.
   * @param user The address of the user unwrapping tokens.
   * @param underlyingAmount The amount of underlying tokens unwrapped.
   * @param amount The amount of WTokens unwrapped.
   */
  event TokenUnwrapped(address indexed user, uint256 indexed underlyingAmount, uint256 indexed amount);

  /**
   * @dev Emitted when the treasury address is updated.
   * @param oldTreasury The address of the previous treasury contract.
   * @param newTreasury The address of the new treasury contract.
   */
  event UpdateTreasuryAddress(address indexed oldTreasury, address indexed newTreasury);

  /**
   * @dev Emitted when the associated group ID is updated.
   * @param groupId New group identifier.
   */
  event UpdateGroup(GroupId indexed groupId);

  event Rebase(uint256 indexed epoch, uint256 indexed newScalar);
  event RateProviderUpdated(address indexed rateProvider);

  // Custom Errors
  error ErrorZeroAddress();
  error InvalidDecimals();
  error ErrorZeroAmount();
  error ErrorNotPermitted();
  error ErrorCannotRecoverToken();
  error ErrorMaxUnderlyingExceeded();

  /**
   * @notice Wraps underlying tokens into WTokens.
   * @param underlyingAmount The amount of underlying tokens to wrap.
   * @return wrappedAmount amount of WTokens minted.
   */
  function wrap(uint256 underlyingAmount) external returns (uint256 wrappedAmount);

  /**
   * @notice Unwraps WTokens back into underlying tokens.
   * @param burnAmount The amount of WTokens to burn.
   * @return amount of underlying tokens returned.
   */
  function unwrap(uint256 burnAmount) external returns (uint256 amount);

  /**
   * @notice rebase the WToken
   */
  function rebase() external;

  function updateRateProvider(address _rateProvider) external;
}

File 41 of 54 : IRebalancePool.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { IERC4626Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";

/**
 * @title IRebalancePool
 * @notice Interface for the RebalancePool contract implementing ERC4626
 */
interface IRebalancePool is IERC4626Upgradeable {
  /// @notice Errors
  error ZeroAddress();
  error ZeroDeposit();
  error InvalidDepositAmount();
  error ZeroRedeem();
  error TokenAlreadyAdded(address token);
  error ZeroShares();
  error InvalidPriceFromOracle();
  error NotPermitted();
  error InvalidYieldFee();
  error RedeemingNotAvailableYet();
  error CannotRecoverToken();
  error UpdatingNotAvailableYet();
  error InvalidCoolingPeriod();
  error CoolingPeriodTriggered();

  /// @notice Events
  event YieldDistributed(uint256 indexed assets, uint256 indexed shares);
  event NAVUpdated(address indexed caller, uint256 indexed totalAssets, uint256 indexed totalSupply);
  event CoolingOffPeriodUpdated(uint256 indexed oldPeriod, uint256 indexed newPeriod);
  event FeeCollectorUpdated(address indexed oldCollector, address indexed newCollector);
  event PriceOracleUpdated(address indexed oldOracle, address indexed newOracle);
  event OtherERC20Withdrawn(address indexed receiver, address indexed token, uint256 indexed amount);
  event DepositMade(address indexed depositor, address indexed receiver, uint256 indexed amount);
  event YieldFeeUpdated(uint256 indexed newFee);

  /// @notice Function to update NAV externally
  function updateNAV() external;

  /// @notice Get NAV per share
  function getNavPerShare() external view returns (uint256);

  /// @notice Override decimals
  function decimals() external view override returns (uint8);

  /// @notice Get cooling off period
  function coolingOffPeriod() external view returns (uint256);

  /// @notice Transfer tokens to treasury
  function transferTokenToTreasury(address token, uint256 amount) external;

  /// @notice Get yieldFeePercentage
  function yieldFeePercentage() external view returns (uint256);

  /// @notice Get base points
  function BASE_POINTS() external view returns (uint256);
}

File 42 of 54 : FullMath.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// solhint-disable
/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
  /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
  /// @param a The multiplicand
  /// @param b The multiplier
  /// @param denominator The divisor
  /// @return result The 256-bit result
  /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
  function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
    unchecked {
      // 512-bit multiply [prod1 prod0] = a * b
      // Compute the product mod 2**256 and mod 2**256 - 1
      // then use the Chinese Remainder Theorem to reconstruct
      // the 512 bit result. The result is stored in two 256
      // variables such that product = prod1 * 2**256 + prod0
      uint256 prod0 = a * b; // Least significant 256 bits of the product
      uint256 prod1; // Most significant 256 bits of the product
      assembly ("memory-safe") {
        let mm := mulmod(a, b, not(0))
        prod1 := sub(sub(mm, prod0), lt(mm, prod0))
      }

      // Make sure the result is less than 2**256.
      // Also prevents denominator == 0
      require(denominator > prod1, "FullMath: denominator too small");

      // Handle non-overflow cases, 256 by 256 division
      if (prod1 == 0) {
        assembly ("memory-safe") {
          result := div(prod0, denominator)
        }
        return result;
      }

      ///////////////////////////////////////////////
      // 512 by 256 division.
      ///////////////////////////////////////////////

      // Make division exact by subtracting the remainder from [prod1 prod0]
      // Compute remainder using mulmod
      uint256 remainder;
      assembly ("memory-safe") {
        remainder := mulmod(a, b, denominator)
      }
      // Subtract 256 bit number from 512 bit number
      assembly ("memory-safe") {
        prod1 := sub(prod1, gt(remainder, prod0))
        prod0 := sub(prod0, remainder)
      }

      // Factor powers of two out of denominator
      // Compute largest power of two divisor of denominator.
      // Always >= 1.
      uint256 twos = (0 - denominator) & denominator;
      // Divide denominator by power of two
      assembly ("memory-safe") {
        denominator := div(denominator, twos)
      }

      // Divide [prod1 prod0] by the factors of two
      assembly ("memory-safe") {
        prod0 := div(prod0, twos)
      }
      // Shift in bits from prod1 into prod0. For this we need
      // to flip `twos` such that it is 2**256 / twos.
      // If twos is zero, then it becomes one
      assembly ("memory-safe") {
        twos := add(div(sub(0, twos), twos), 1)
      }
      prod0 |= prod1 * twos;

      // Invert denominator mod 2**256
      // Now that denominator is an odd number, it has an inverse
      // modulo 2**256 such that denominator * inv = 1 mod 2**256.
      // Compute the inverse by starting with a seed that is correct
      // correct for four bits. That is, denominator * inv = 1 mod 2**4
      uint256 inv = (3 * denominator) ^ 2;
      // Now use Newton-Raphson iteration to improve the precision.
      // Thanks to Hensel's lifting lemma, this also works in modular
      // arithmetic, doubling the correct bits in each step.
      inv *= 2 - denominator * inv; // inverse mod 2**8
      inv *= 2 - denominator * inv; // inverse mod 2**16
      inv *= 2 - denominator * inv; // inverse mod 2**32
      inv *= 2 - denominator * inv; // inverse mod 2**64
      inv *= 2 - denominator * inv; // inverse mod 2**128
      inv *= 2 - denominator * inv; // inverse mod 2**256

      // Because the division is now exact we can divide by multiplying
      // with the modular inverse of denominator. This will give us the
      // correct result modulo 2**256. Since the preconditions guarantee
      // that the outcome is less than 2**256, this is the final result.
      // We don't need to compute the high bits of the result and prod1
      // is no longer required.
      result = prod0 * inv;
      return result;
    }
  }

  /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
  /// @param a The multiplicand
  /// @param b The multiplier
  /// @param denominator The divisor
  /// @return result The 256-bit result
  function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
    unchecked {
      result = mulDiv(a, b, denominator);
      if (mulmod(a, b, denominator) != 0) {
        require(++result > 0, "FullMath: addition overflow");
      }
    }
  }
}

File 43 of 54 : IProtocolMinimum.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { GroupId } from "../types/GroupId.sol";

interface IProtocolMinimum {
    function stabilityRatio(GroupId groupId) external view returns (uint96);
}

File 44 of 54 : IERC20MetadataUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20Upgradeable.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20MetadataUpgradeable is IERC20Upgradeable {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

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

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

File 45 of 54 : IERC5267Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5267.sol)

pragma solidity ^0.8.0;

interface IERC5267Upgradeable {
    /**
     * @dev MAY be emitted to signal that the domain could have changed.
     */
    event EIP712DomainChanged();

    /**
     * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
     * signature.
     */
    function eip712Domain()
        external
        view
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        );
}

File 46 of 54 : LogExpMathV8.sol
// SPDX-License-Identifier: MIT

// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

pragma solidity 0.8.28;

/* solhint-disable */

/**
 * @dev Copied from https://github.com/balancer/balancer-v2-monorepo/blob/master/pkg/solidity-utils/contracts/math/LogExpMath.sol
 *
 * Some modifications are made due to compile error.
 *
 * It is the same as `LogExpMathV8` with `unchecked` scope.
 *
 * @dev Exponentiation and logarithm functions for 18 decimal fixed point numbers (both base and exponent/argument).
 *
 * Exponentiation and logarithm with arbitrary bases (x^y and log_x(y)) are implemented by conversion to natural
 * exponentiation and logarithm (where the base is Euler's number).
 *
 * @author Fernando Martinelli - @fernandomartinelli
 * @author Sergio Yuhjtman - @sergioyuhjtman
 * @author Daniel Fernandez - @dmf7z
 */
library LogExpMathV8 {
  // All fixed point multiplications and divisions are inlined. This means we need to divide by ONE when multiplying
  // two numbers, and multiply by ONE when dividing them.

  // All arguments and return values are 18 decimal fixed point numbers.
  int256 constant ONE_18 = 1e18;

  // Internally, intermediate values are computed with higher precision as 20 decimal fixed point numbers, and in the
  // case of ln36, 36 decimals.
  int256 constant ONE_20 = 1e20;
  int256 constant ONE_36 = 1e36;

  // The domain of natural exponentiation is bound by the word size and number of decimals used.
  //
  // Because internally the result will be stored using 20 decimals, the largest possible result is
  // (2^255 - 1) / 10^20, which makes the largest exponent ln((2^255 - 1) / 10^20) = 130.700829182905140221.
  // The smallest possible result is 10^(-18), which makes largest negative argument
  // ln(10^(-18)) = -41.446531673892822312.
  // We use 130.0 and -41.0 to have some safety margin.
  int256 constant MAX_NATURAL_EXPONENT = 130e18;
  int256 constant MIN_NATURAL_EXPONENT = -41e18;

  // Bounds for ln_36's argument. Both ln(0.9) and ln(1.1) can be represented with 36 decimal places in a fixed point
  // 256 bit integer.
  int256 constant LN_36_LOWER_BOUND = ONE_18 - 1e17;
  int256 constant LN_36_UPPER_BOUND = ONE_18 + 1e17;

  uint256 constant MILD_EXPONENT_BOUND = 2 ** 254 / uint256(ONE_20);

  // 18 decimal constants
  int256 constant x0 = 128000000000000000000; // 2ˆ7
  int256 constant a0 = 38877084059945950922200000000000000000000000000000000000; // eˆ(x0) (no decimals)
  int256 constant x1 = 64000000000000000000; // 2ˆ6
  int256 constant a1 = 6235149080811616882910000000; // eˆ(x1) (no decimals)

  // 20 decimal constants
  int256 constant x2 = 3200000000000000000000; // 2ˆ5
  int256 constant a2 = 7896296018268069516100000000000000; // eˆ(x2)
  int256 constant x3 = 1600000000000000000000; // 2ˆ4
  int256 constant a3 = 888611052050787263676000000; // eˆ(x3)
  int256 constant x4 = 800000000000000000000; // 2ˆ3
  int256 constant a4 = 298095798704172827474000; // eˆ(x4)
  int256 constant x5 = 400000000000000000000; // 2ˆ2
  int256 constant a5 = 5459815003314423907810; // eˆ(x5)
  int256 constant x6 = 200000000000000000000; // 2ˆ1
  int256 constant a6 = 738905609893065022723; // eˆ(x6)
  int256 constant x7 = 100000000000000000000; // 2ˆ0
  int256 constant a7 = 271828182845904523536; // eˆ(x7)
  int256 constant x8 = 50000000000000000000; // 2ˆ-1
  int256 constant a8 = 164872127070012814685; // eˆ(x8)
  int256 constant x9 = 25000000000000000000; // 2ˆ-2
  int256 constant a9 = 128402541668774148407; // eˆ(x9)
  int256 constant x10 = 12500000000000000000; // 2ˆ-3
  int256 constant a10 = 113314845306682631683; // eˆ(x10)
  int256 constant x11 = 6250000000000000000; // 2ˆ-4
  int256 constant a11 = 106449445891785942956; // eˆ(x11)

  /**
   * @dev Exponentiation (x^y) with unsigned 18 decimal fixed point base and exponent.
   *
   * Reverts if ln(x) * y is smaller than `MIN_NATURAL_EXPONENT`, or larger than `MAX_NATURAL_EXPONENT`.
   */
  function pow(uint256 x, uint256 y) internal pure returns (uint256) {
    unchecked {
      if (y == 0) {
        // We solve the 0^0 indetermination by making it equal one.
        return uint256(ONE_18);
      }

      if (x == 0) {
        return 0;
      }

      // Instead of computing x^y directly, we instead rely on the properties of logarithms and exponentiation to
      // arrive at that result. In particular, exp(ln(x)) = x, and ln(x^y) = y * ln(x). This means
      // x^y = exp(y * ln(x)).

      // The ln function takes a signed value, so we need to make sure x fits in the signed 256 bit range.
      require(x >> 255 == 0, "X_OUT_OF_BOUNDS");
      int256 x_int256 = int256(x);

      // We will compute y * ln(x) in a single step. Depending on the value of x, we can either use ln or ln_36. In
      // both cases, we leave the division by ONE_18 (due to fixed point multiplication) to the end.

      // This prevents y * ln(x) from overflowing, and at the same time guarantees y fits in the signed 256 bit range.
      require(y < MILD_EXPONENT_BOUND, "Y_OUT_OF_BOUNDS");
      int256 y_int256 = int256(y);

      int256 logx_times_y;
      if (LN_36_LOWER_BOUND < x_int256 && x_int256 < LN_36_UPPER_BOUND) {
        int256 ln_36_x = _ln_36(x_int256);

        // ln_36_x has 36 decimal places, so multiplying by y_int256 isn't as straightforward, since we can't just
        // bring y_int256 to 36 decimal places, as it might overflow. Instead, we perform two 18 decimal
        // multiplications and add the results: one with the first 18 decimals of ln_36_x, and one with the
        // (downscaled) last 18 decimals.
        logx_times_y = ((ln_36_x / ONE_18) * y_int256 + ((ln_36_x % ONE_18) * y_int256) / ONE_18);
      } else {
        logx_times_y = _ln(x_int256) * y_int256;
      }
      logx_times_y /= ONE_18;

      // Finally, we compute exp(y * ln(x)) to arrive at x^y
      require(MIN_NATURAL_EXPONENT <= logx_times_y && logx_times_y <= MAX_NATURAL_EXPONENT, "PRODUCT_OUT_OF_BOUNDS");

      return uint256(exp(logx_times_y));
    }
  }

  /**
   * @dev Natural exponentiation (e^x) with signed 18 decimal fixed point exponent.
   *
   * Reverts if `x` is smaller than MIN_NATURAL_EXPONENT, or larger than `MAX_NATURAL_EXPONENT`.
   */
  function exp(int256 x) internal pure returns (int256) {
    unchecked {
      require(x >= MIN_NATURAL_EXPONENT && x <= MAX_NATURAL_EXPONENT, "INVALID_EXPONENT");

      if (x < 0) {
        // We only handle positive exponents: e^(-x) is computed as 1 / e^x. We can safely make x positive since it
        // fits in the signed 256 bit range (as it is larger than MIN_NATURAL_EXPONENT).
        // Fixed point division requires multiplying by ONE_18.
        return ((ONE_18 * ONE_18) / exp(-x));
      }

      // First, we use the fact that e^(x+y) = e^x * e^y to decompose x into a sum of powers of two, which we call x_n,
      // where x_n == 2^(7 - n), and e^x_n = a_n has been precomputed. We choose the first x_n, x0, to equal 2^7
      // because all larger powers are larger than MAX_NATURAL_EXPONENT, and therefore not present in the
      // decomposition.
      // At the end of this process we will have the product of all e^x_n = a_n that apply, and the remainder of this
      // decomposition, which will be lower than the smallest x_n.
      // exp(x) = k_0 * a_0 * k_1 * a_1 * ... + k_n * a_n * exp(remainder), where each k_n equals either 0 or 1.
      // We mutate x by subtracting x_n, making it the remainder of the decomposition.

      // The first two a_n (e^(2^7) and e^(2^6)) are too large if stored as 18 decimal numbers, and could cause
      // intermediate overflows. Instead we store them as plain integers, with 0 decimals.
      // Additionally, x0 + x1 is larger than MAX_NATURAL_EXPONENT, which means they will not both be present in the
      // decomposition.

      // For each x_n, we test if that term is present in the decomposition (if x is larger than it), and if so deduct
      // it and compute the accumulated product.

      int256 firstAN;
      if (x >= x0) {
        x -= x0;
        firstAN = a0;
      } else if (x >= x1) {
        x -= x1;
        firstAN = a1;
      } else {
        firstAN = 1; // One with no decimal places
      }

      // We now transform x into a 20 decimal fixed point number, to have enhanced precision when computing the
      // smaller terms.
      x *= 100;

      // `product` is the accumulated product of all a_n (except a0 and a1), which starts at 20 decimal fixed point
      // one. Recall that fixed point multiplication requires dividing by ONE_20.
      int256 product = ONE_20;

      if (x >= x2) {
        x -= x2;
        product = (product * a2) / ONE_20;
      }
      if (x >= x3) {
        x -= x3;
        product = (product * a3) / ONE_20;
      }
      if (x >= x4) {
        x -= x4;
        product = (product * a4) / ONE_20;
      }
      if (x >= x5) {
        x -= x5;
        product = (product * a5) / ONE_20;
      }
      if (x >= x6) {
        x -= x6;
        product = (product * a6) / ONE_20;
      }
      if (x >= x7) {
        x -= x7;
        product = (product * a7) / ONE_20;
      }
      if (x >= x8) {
        x -= x8;
        product = (product * a8) / ONE_20;
      }
      if (x >= x9) {
        x -= x9;
        product = (product * a9) / ONE_20;
      }

      // x10 and x11 are unnecessary here since we have high enough precision already.

      // Now we need to compute e^x, where x is small (in particular, it is smaller than x9). We use the Taylor series
      // expansion for e^x: 1 + x + (x^2 / 2!) + (x^3 / 3!) + ... + (x^n / n!).

      int256 seriesSum = ONE_20; // The initial one in the sum, with 20 decimal places.
      int256 term; // Each term in the sum, where the nth term is (x^n / n!).

      // The first term is simply x.
      term = x;
      seriesSum += term;

      // Each term (x^n / n!) equals the previous one times x, divided by n. Since x is a fixed point number,
      // multiplying by it requires dividing by ONE_20, but dividing by the non-fixed point n values does not.

      term = ((term * x) / ONE_20) / 2;
      seriesSum += term;

      term = ((term * x) / ONE_20) / 3;
      seriesSum += term;

      term = ((term * x) / ONE_20) / 4;
      seriesSum += term;

      term = ((term * x) / ONE_20) / 5;
      seriesSum += term;

      term = ((term * x) / ONE_20) / 6;
      seriesSum += term;

      term = ((term * x) / ONE_20) / 7;
      seriesSum += term;

      term = ((term * x) / ONE_20) / 8;
      seriesSum += term;

      term = ((term * x) / ONE_20) / 9;
      seriesSum += term;

      term = ((term * x) / ONE_20) / 10;
      seriesSum += term;

      term = ((term * x) / ONE_20) / 11;
      seriesSum += term;

      term = ((term * x) / ONE_20) / 12;
      seriesSum += term;

      // 12 Taylor terms are sufficient for 18 decimal precision.

      // We now have the first a_n (with no decimals), and the product of all other a_n present, and the Taylor
      // approximation of the exponentiation of the remainder (both with 20 decimals). All that remains is to multiply
      // all three (one 20 decimal fixed point multiplication, dividing by ONE_20, and one integer multiplication),
      // and then drop two digits to return an 18 decimal value.

      return (((product * seriesSum) / ONE_20) * firstAN) / 100;
    }
  }

  /**
   * @dev Logarithm (log(arg, base), with signed 18 decimal fixed point base and argument.
   */
  function log(int256 arg, int256 base) internal pure returns (int256) {
    unchecked {
      // This performs a simple base change: log(arg, base) = ln(arg) / ln(base).

      // Both logBase and logArg are computed as 36 decimal fixed point numbers, either by using ln_36, or by
      // upscaling.

      int256 logBase;
      if (LN_36_LOWER_BOUND < base && base < LN_36_UPPER_BOUND) {
        logBase = _ln_36(base);
      } else {
        logBase = _ln(base) * ONE_18;
      }

      int256 logArg;
      if (LN_36_LOWER_BOUND < arg && arg < LN_36_UPPER_BOUND) {
        logArg = _ln_36(arg);
      } else {
        logArg = _ln(arg) * ONE_18;
      }

      // When dividing, we multiply by ONE_18 to arrive at a result with 18 decimal places
      return (logArg * ONE_18) / logBase;
    }
  }

  /**
   * @dev Natural logarithm (ln(a)) with signed 18 decimal fixed point argument.
   */
  function ln(int256 a) internal pure returns (int256) {
    unchecked {
      // The real natural logarithm is not defined for negative numbers or zero.
      require(a > 0, "OUT_OF_BOUNDS");
      if (LN_36_LOWER_BOUND < a && a < LN_36_UPPER_BOUND) {
        return _ln_36(a) / ONE_18;
      } else {
        return _ln(a);
      }
    }
  }

  /**
   * @dev Internal natural logarithm (ln(a)) with signed 18 decimal fixed point argument.
   */
  function _ln(int256 a) private pure returns (int256) {
    unchecked {
      if (a < ONE_18) {
        // Since ln(a^k) = k * ln(a), we can compute ln(a) as ln(a) = ln((1/a)^(-1)) = - ln((1/a)). If a is less
        // than one, 1/a will be greater than one, and this if statement will not be entered in the recursive call.
        // Fixed point division requires multiplying by ONE_18.
        return (-_ln((ONE_18 * ONE_18) / a));
      }

      // First, we use the fact that ln^(a * b) = ln(a) + ln(b) to decompose ln(a) into a sum of powers of two, which
      // we call x_n, where x_n == 2^(7 - n), which are the natural logarithm of precomputed quantities a_n (that is,
      // ln(a_n) = x_n). We choose the first x_n, x0, to equal 2^7 because the exponential of all larger powers cannot
      // be represented as 18 fixed point decimal numbers in 256 bits, and are therefore larger than a.
      // At the end of this process we will have the sum of all x_n = ln(a_n) that apply, and the remainder of this
      // decomposition, which will be lower than the smallest a_n.
      // ln(a) = k_0 * x_0 + k_1 * x_1 + ... + k_n * x_n + ln(remainder), where each k_n equals either 0 or 1.
      // We mutate a by subtracting a_n, making it the remainder of the decomposition.

      // For reasons related to how `exp` works, the first two a_n (e^(2^7) and e^(2^6)) are not stored as fixed point
      // numbers with 18 decimals, but instead as plain integers with 0 decimals, so we need to multiply them by
      // ONE_18 to convert them to fixed point.
      // For each a_n, we test if that term is present in the decomposition (if a is larger than it), and if so divide
      // by it and compute the accumulated sum.

      int256 sum = 0;
      if (a >= a0 * ONE_18) {
        a /= a0; // Integer, not fixed point division
        sum += x0;
      }

      if (a >= a1 * ONE_18) {
        a /= a1; // Integer, not fixed point division
        sum += x1;
      }

      // All other a_n and x_n are stored as 20 digit fixed point numbers, so we convert the sum and a to this format.
      sum *= 100;
      a *= 100;

      // Because further a_n are  20 digit fixed point numbers, we multiply by ONE_20 when dividing by them.

      if (a >= a2) {
        a = (a * ONE_20) / a2;
        sum += x2;
      }

      if (a >= a3) {
        a = (a * ONE_20) / a3;
        sum += x3;
      }

      if (a >= a4) {
        a = (a * ONE_20) / a4;
        sum += x4;
      }

      if (a >= a5) {
        a = (a * ONE_20) / a5;
        sum += x5;
      }

      if (a >= a6) {
        a = (a * ONE_20) / a6;
        sum += x6;
      }

      if (a >= a7) {
        a = (a * ONE_20) / a7;
        sum += x7;
      }

      if (a >= a8) {
        a = (a * ONE_20) / a8;
        sum += x8;
      }

      if (a >= a9) {
        a = (a * ONE_20) / a9;
        sum += x9;
      }

      if (a >= a10) {
        a = (a * ONE_20) / a10;
        sum += x10;
      }

      if (a >= a11) {
        a = (a * ONE_20) / a11;
        sum += x11;
      }

      // a is now a small number (smaller than a_11, which roughly equals 1.06). This means we can use a Taylor series
      // that converges rapidly for values of `a` close to one - the same one used in ln_36.
      // Let z = (a - 1) / (a + 1).
      // ln(a) = 2 * (z + z^3 / 3 + z^5 / 5 + z^7 / 7 + ... + z^(2 * n + 1) / (2 * n + 1))

      // Recall that 20 digit fixed point division requires multiplying by ONE_20, and multiplication requires
      // division by ONE_20.
      int256 z = ((a - ONE_20) * ONE_20) / (a + ONE_20);
      int256 z_squared = (z * z) / ONE_20;

      // num is the numerator of the series: the z^(2 * n + 1) term
      int256 num = z;

      // seriesSum holds the accumulated sum of each term in the series, starting with the initial z
      int256 seriesSum = num;

      // In each step, the numerator is multiplied by z^2
      num = (num * z_squared) / ONE_20;
      seriesSum += num / 3;

      num = (num * z_squared) / ONE_20;
      seriesSum += num / 5;

      num = (num * z_squared) / ONE_20;
      seriesSum += num / 7;

      num = (num * z_squared) / ONE_20;
      seriesSum += num / 9;

      num = (num * z_squared) / ONE_20;
      seriesSum += num / 11;

      // 6 Taylor terms are sufficient for 36 decimal precision.

      // Finally, we multiply by 2 (non fixed point) to compute ln(remainder)
      seriesSum *= 2;

      // We now have the sum of all x_n present, and the Taylor approximation of the logarithm of the remainder (both
      // with 20 decimals). All that remains is to sum these two, and then drop two digits to return a 18 decimal
      // value.

      return (sum + seriesSum) / 100;
    }
  }

  /**
   * @dev Intrnal high precision (36 decimal places) natural logarithm (ln(x)) with signed 18 decimal fixed point argument,
   * for x close to one.
   *
   * Should only be used if x is between LN_36_LOWER_BOUND and LN_36_UPPER_BOUND.
   */
  function _ln_36(int256 x) private pure returns (int256) {
    unchecked {
      // Since ln(1) = 0, a value of x close to one will yield a very small result, which makes using 36 digits
      // worthwhile.

      // First, we transform x to a 36 digit fixed point value.
      x *= ONE_18;

      // We will use the following Taylor expansion, which converges very rapidly. Let z = (x - 1) / (x + 1).
      // ln(x) = 2 * (z + z^3 / 3 + z^5 / 5 + z^7 / 7 + ... + z^(2 * n + 1) / (2 * n + 1))

      // Recall that 36 digit fixed point division requires multiplying by ONE_36, and multiplication requires
      // division by ONE_36.
      int256 z = ((x - ONE_36) * ONE_36) / (x + ONE_36);
      int256 z_squared = (z * z) / ONE_36;

      // num is the numerator of the series: the z^(2 * n + 1) term
      int256 num = z;

      // seriesSum holds the accumulated sum of each term in the series, starting with the initial z
      int256 seriesSum = num;

      // In each step, the numerator is multiplied by z^2
      num = (num * z_squared) / ONE_36;
      seriesSum += num / 3;

      num = (num * z_squared) / ONE_36;
      seriesSum += num / 5;

      num = (num * z_squared) / ONE_36;
      seriesSum += num / 7;

      num = (num * z_squared) / ONE_36;
      seriesSum += num / 9;

      num = (num * z_squared) / ONE_36;
      seriesSum += num / 11;

      num = (num * z_squared) / ONE_36;
      seriesSum += num / 13;

      num = (num * z_squared) / ONE_36;
      seriesSum += num / 15;

      // 8 Taylor terms are sufficient for 36 decimal precision.

      // All that remains is multiplying by 2 (non fixed point).
      return seriesSum * 2;
    }
  }
}

File 47 of 54 : WordCodec.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.28;

// solhint-disable no-inline-assembly

/// @dev A subset copied from the following contracts:
///
/// + `balancer-labs/v2-solidity-utils/contracts/helpers/WordCodec.sol`
/// + `balancer-labs/v2-solidity-utils/contracts/helpers/WordCodecHelpers.sol`
library WordCodec {
  /// @dev Inserts an unsigned integer of bitLength, shifted by an offset, into a 256 bit word,
  /// replacing the old value. Returns the new word.
  function insertUint(bytes32 word, uint256 value, uint256 offset, uint256 bitLength) internal pure returns (bytes32 result) {
    // Equivalent to:
    // uint256 mask = (1 << bitLength) - 1;
    // bytes32 clearedWord = bytes32(uint256(word) & ~(mask << offset));
    // result = clearedWord | bytes32(value << offset);
    assembly("memory-safe") {
      let mask := sub(shl(bitLength, 1), 1)
      let clearedWord := and(word, not(shl(offset, mask)))
      result := or(clearedWord, shl(offset, value))
    }
  }

  /// @dev Decodes and returns an unsigned integer with `bitLength` bits, shifted by an offset, from a 256 bit word.
  function decodeUint(bytes32 word, uint256 offset, uint256 bitLength) internal pure returns (uint256 result) {
    // Equivalent to:
    // result = uint256(word >> offset) & ((1 << bitLength) - 1);
    assembly("memory-safe") {
      result := and(shr(offset, word), sub(shl(bitLength, 1), 1))
    }
  }

  /// @dev Inserts a signed integer shifted by an offset into a 256 bit word, replacing the old value. Returns
  /// the new word.
  ///
  function insertInt(bytes32 word, int256 value, uint256 offset, uint256 bitLength) internal pure returns (bytes32) {
    unchecked {
      uint256 mask = (1 << bitLength) - 1;
      bytes32 clearedWord = bytes32(uint256(word) & ~(mask << offset));
      // Integer values need masking to remove the upper bits of negative values.
      return clearedWord | bytes32((uint256(value) & mask) << offset);
    }
  }

  /// @dev Decodes and returns a signed integer with `bitLength` bits, shifted by an offset, from a 256 bit word.
  function decodeInt(bytes32 word, uint256 offset, uint256 bitLength) internal pure returns (int256 result) {
    unchecked {
      int256 maxInt = int256((1 << (bitLength - 1)) - 1);
      uint256 mask = (1 << bitLength) - 1;

      int256 value = int256(uint256(word >> offset) & mask);
      // In case the decoded value is greater than the max positive integer that can be represented with bitLength
      // bits, we know it was originally a negative integer. Therefore, we mask it to restore the sign in the 256 bit
      // representation.
      //
      // Equivalent to:
      // result = value > maxInt ? (value | int256(~mask)) : value;
      assembly {
        result := or(mul(gt(value, maxInt), not(mask)), value)
      }
    }
  }

  /// @dev Decodes and returns a boolean shifted by an offset from a 256 bit word.
  function decodeBool(bytes32 word, uint256 offset) internal pure returns (bool result) {
    // Equivalent to:
    // result = (uint256(word >> offset) & 1) == 1;
    assembly {
      result := and(shr(offset, word), 1)
    }
  }

  /// @dev Inserts a boolean value shifted by an offset into a 256 bit word, replacing the old value. Returns the new
  /// word.
  function insertBool(bytes32 word, bool value, uint256 offset) internal pure returns (bytes32 result) {
    // Equivalent to:
    // bytes32 clearedWord = bytes32(uint256(word) & ~(1 << offset));
    // bytes32 referenceInsertBool = clearedWord | bytes32(uint256(value ? 1 : 0) << offset);
    assembly("memory-safe") {
      let clearedWord := and(word, not(shl(offset, 1)))
      result := or(clearedWord, shl(offset, value))
    }
  }

  function clearWordAtPosition(bytes32 word, uint256 offset, uint256 bitLength) internal pure returns (bytes32 clearedWord) {
    unchecked {
      uint256 mask = (1 << bitLength) - 1;
      clearedWord = bytes32(uint256(word) & ~(mask << offset));
    }
  }

  /// @dev Encodes an address into a 256 bit word at a given offset.
  function insertAddress(bytes32 word, address value, uint256 offset) internal pure returns (bytes32 result) {
    assembly("memory-safe") {
      let clearedWord := and(word, not(shl(offset, 0xffffffffffffffffffffffffffffffffffffffff)))
      result := or(clearedWord, shl(offset, value))
    }
  }

  /// @dev Decodes an address from a 256 bit word at a given offset.
  function decodeAddress(bytes32 word, uint256 offset) internal pure returns (address result) {
    assembly("memory-safe") {
      result := and(shr(offset, word), 0xffffffffffffffffffffffffffffffffffffffff)
    }
  }

  /// @dev Encodes an enum value into a 256 bit word at a given offset.
  function insertEnum(bytes32 word, uint8 value, uint256 offset) internal pure returns (bytes32 result) {
    assembly("memory-safe") {
      let clearedWord := and(word, not(shl(offset, 0xff)))
      result := or(clearedWord, shl(offset, value))
    }
  }

  /// @dev Decodes an enum value from a 256 bit word at a given offset.
  function decodeEnum(bytes32 word, uint256 offset) internal pure returns (uint8 result) {
    assembly("memory-safe") {
      result := and(shr(offset, word), 0xff)
    }
  }
}

File 48 of 54 : ITokenRegistry.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { Currency } from "../types/Currency.sol";
import { GroupId } from "../types/GroupId.sol";
import { GroupKey } from "../types/GroupKey.sol";
import { DTokenRegistry } from "../declarations/DTokenRegistry.sol";
import { GroupState, FeePermissions, FeeParams, DefaultFeeParams, CollateralInfo } from "../types/CommonTypes.sol";

interface ITokenRegistry {
  // Events
  event GroupAdded(GroupId indexed groupId);
  event GroupUpdated(GroupId indexed groupId);
  event CollateralAdded(GroupId indexed groupId, Currency indexed token, uint8 indexed decimals);
  event CollateralRemoved(GroupId indexed groupId, Currency indexed token);
  event HookContractSet(GroupId indexed groupId, address indexed newHookContract, bool indexed isDynamic, bool isDelegated);
  event HookPermissionsSet(GroupId indexed groupId, uint256 indexed newPermissions);

  event UpdateFeePermissions(GroupId indexed groupId, bool indexed isDynamic, bool indexed allowDelegation);

  // Custom Errors
  error AddressNotContract(address addr);
  error NotPermitted();
  error GroupNotFound(GroupId groupId);
  error GroupAlreadyExists(GroupId groupId);
  error InvalidDecimals();
  error InvalidMinMaxCollateralAmount();
  error CollateralAlreadyExists();
  error MultipleNativeTokensNotAllowed();
  error CollateralNotFound();
  error InvalidGroupSetup();
  error ZeroAddress();
  error EmptyCollateralList();
  error MaxCollateralsLimitReached();
  error StabilityRatioTooLarge();
  error stabilityConditionsTriggeringRateTooLarge();

  error InvalidStabilityTriggeringRatio(GroupId groupId);

  error FeesTooHigh();
  error InvalidMaxFee();
  error InvalidMinFee();
  error InvalidBaseFee();
  error InvalidYieldFee();
  error InvalidStabilityFee();
  error InvalidProtocolFee();
  error InvalidFeeFlags();
  error FeesAreNotCompatibleWithDefaultFees();
  error CannotRecoverToken();

  // Group management functions (Manager Role)
  function addGroup(
    DTokenRegistry.GroupSetup calldata setup, // Full group setup
    address hookContract, // Hook contract for the group
    uint256 hookPermissions // Hook permissions
  ) external;

  // Collateral management functions (Manager Role)
  function addCollateral(GroupKey calldata key, CollateralInfo calldata collateral) external;

  function removeCollateral(GroupKey calldata key, Currency token) external;

  // Getter functions for group state
  function getGroup(GroupId groupId) external view returns (GroupState memory);

  function getStabilityRatio(GroupId groupId) external view returns (uint96);

  function getStabilityTriggeringRatio(GroupId groupId) external view returns (uint96);

  function getProtocolFee(GroupId groupId) external view returns (uint24);

  function getFeePermissions(GroupId groupId) external view returns (FeePermissions memory);

  function getFeeParams(GroupId groupId) external view returns (FeeParams memory);

  function getMintFeeVT(GroupId groupId) external view returns (uint24);

  function getRedeemFeeVT(GroupId groupId) external view returns (uint24);

  function getMintFeeYT(GroupId groupId) external view returns (uint24);

  function getRedeemFeeYT(GroupId groupId) external view returns (uint24);

  function getYieldFeeYT(GroupId groupId) external view returns (uint24);

  function getStabilityMintFeeVT(GroupId groupId) external view returns (uint24);

  function getStabilityMintFeeYT(GroupId groupId) external view returns (uint24);

  function getStabilityRedeemFeeVT(GroupId groupId) external view returns (uint24);

  function getStabilityRedeemFeeYT(GroupId groupId) external view returns (uint24);

  function getHookContract(GroupId groupId) external view returns (address);

  function getHookPermissions(GroupId groupId) external view returns (uint256);

  function validateCollateral(GroupId groupId, Currency currency) external view returns (bool);

  function isWrappingRequired(GroupId groupId) external view returns (bool);

  function getFeeModel(GroupId groupId) external view returns (uint8);
}

File 49 of 54 : FxStableMath.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

/// @dev This library implements formulas for minting and redeeming stable coin token (aToken) and leveraged (xToken)
///      tokens in a system where:
///        - There is a base token with a certain Net Asset Value (NAV).
///        - A stable coin token (aToken) with its own NAV.
///        - A leveraged token (xToken) with its own NAV.
///      All values are scaled by 1e18 before being passed to these functions.
///
/// The core equation governing the system is:
///     n * v = nf * vf + nx * vx
///
/// Where:
///   n   = current total base supply
///   v   = baseNav (NAV of base token)
///   nf  = aSupply (supply of stable coin token)
///   vf  = aNav (NAV of stable coin token)
///   nx  = xSupply (supply of leveraged token)
///   vx  = xNav (NAV of leveraged token)
///
/// The "collateral ratio" (cr) or "target collateral ratio" (ncr) is defined as:
///   cr = (total base value) / (stable coin token value) = (n * v) / (nf * vf)
///
/// Operations involve adjusting supplies (minting or redeeming tokens) to reach a new collateral ratio (ncr).
/// We define:
///   dn = change in base supply (how many base tokens are added or removed)
///   df = change in stable coin token supply (how many stable coin tokens are added or removed)
///
/// By setting:
///   ((n + dn) * v) / ((nf + df)*vf) = ncr
///
/// and using the base equation n * v = nf * vf + nx * vx, we can derive formulas for dn and df
/// depending on whether we are minting or redeeming under a desired collateral ratio.

library FxStableMath {
  /*************
   * Constants *
   *************/

  /// @dev The precision (scaling factor) used for calculations.
  uint256 internal constant PRECISION = 1e18;

  /// @dev The maximum leverage ratio allowed.
  uint256 internal constant MAX_LEVERAGE_RATIO = 100e18;

  /***********
   * Structs *
   ***********/

  struct SwapState {
    // Current supply of the base token
    uint256 baseSupply;
    // Current NAV of the base token (scaled by 1e18)
    uint256 baseNav;
    // Current TWAP NAV of the base token (scaled by 1e18), used for stable calculations
    uint256 baseTwapNav;
    // Current supply of the stable coin token
    uint256 aSupply;
    // Current NAV of the stable coin token (scaled by 1e18)
    uint256 aNav;
    // Current supply of the leveraged token
    uint256 xSupply;
    // Current NAV of the leveraged token (scaled by 1e18)
    uint256 xNav;
    // A boolean parameter `beta` that may adjust behavior in some calculations
    bool beta;
  }

  /**
   * @notice Compute how much base token (dn) and stable coin tokens (df) can be minted to achieve a new collateral ratio,
   *         if the current collateral ratio is less than the new target.
   *
   * Variables (for reference):
   *   n: current total base supply
   *   v: baseNav
   *   nf: aSupply
   *   vf: aNav
   *   nx: xSupply
   *   vx: xNav
   *   ncr: newCollateralRatio
   *
   * Core equations:
   *   Initially:
   *       n * v = nf * vf + nx * vx
   *
   *   After adding some amount of base tokens (dn) and stable coin tokens (df):
   *       (n + dn) * v = (nf + df) * vf + nx * vx
   *
   *   Define collateral ratio as:
   *       ((n + dn) * v) / ((nf + df) * vf) = ncr
   *
   *   From the above, solving for df and dn:
   *       df = ((n * v) - (ncr * nf * vf)) / ((ncr - 1) * vf)
   *       dn = ((n * v) - (ncr * nf * vf)) / ((ncr - 1) * v)
   *
   * Here, df and dn tell us how much stable coin token and base token must be added to achieve ncr.
   * If dn > 0, we need to add that many base tokens; if df > 0, we can mint that many stable coin tokens.
   *
   * @dev If the current collateral ratio is already >= ncr, then we return 0 because no minting is needed.
   *
   * @param state Current state of the system.
   * @param _newCollateralRatio The desired new collateral ratio (scaled by 1e18).
   * @return _maxBaseIn The amount of base token required (corresponds to dn).
   * @return _maxATokenMintable The amount of stable coin token (aToken) that can be minted (corresponds to df).
   */
  function maxMintableAToken(
    SwapState memory state,
    uint256 _newCollateralRatio
  ) internal pure returns (uint256 _maxBaseIn, uint256 _maxATokenMintable) {
    // Calculate scaled values
    uint256 _baseVal = state.baseSupply * (state.baseNav) * (PRECISION);
    uint256 _aVal = _newCollateralRatio * (state.aSupply) * state.aNav;

    // If baseVal > aVal, we can mint
    if (_baseVal > _aVal) {
      // Adjust ncr to (ncr - 1)*PRECISION
      _newCollateralRatio = _newCollateralRatio - (PRECISION);
      uint256 _delta = _baseVal - _aVal;

      // dn = delta / ((ncr - 1)*v)
      _maxBaseIn = _delta / (state.baseNav * (_newCollateralRatio));

      // df = delta / ((ncr - 1)*vf)
      _maxATokenMintable = _delta / (state.aNav * (_newCollateralRatio));
    }
  }

  /**
   * @notice Compute how much base token (dn) and leveraged tokens (xToken) can be minted to achieve a new collateral ratio,
   *         if the current collateral ratio is less than the new target.
   *
   * Equations:
   *   n * v = nf * vf + nx * vx
   *
   * After adding dn base tokens and dx leveraged tokens:
   *   (n + dn)*v = nf*vf + (nx + dx)*vx
   *
   * The new collateral ratio condition:
   *   ((n + dn)*v) / (nf*vf) = ncr
   *
   * From this, solving for dn and dx:
   *   dn = (ncr * nf * vf - n * v) / v
   *   dx = (ncr * nf * vf - n * v) / vx
   *
   * @dev If the current collateral ratio >= ncr, we return 0 because no minting is needed.
   *
   * @param state The current state.
   * @param _newCollateralRatio The desired new collateral ratio (scaled by 1e18).
   * @return _maxBaseIn The amount of base token needed (dn).
   * @return _maxXTokenMintable The amount of leveraged token (xToken) that can be minted (dx).
   */
  function maxMintableXToken(
    SwapState memory state,
    uint256 _newCollateralRatio
  ) internal pure returns (uint256 _maxBaseIn, uint256 _maxXTokenMintable) {
    uint256 _baseVal = state.baseSupply * state.baseNav * PRECISION;
    uint256 _aVal = _newCollateralRatio * state.aSupply * state.aNav;

    if (_aVal > _baseVal) {
      uint256 _delta = _aVal - _baseVal;

      // dn = delta / (v * PRECISION)
      _maxBaseIn = _delta / (state.baseNav * (PRECISION));
      // dx = delta / (vx * PRECISION)
      _maxXTokenMintable = _delta / (state.xNav * (PRECISION));
    }
  }

  /**
   * @notice Compute how many stable coin tokens (aToken) can be redeemed and how much base token (dn) is released
   *         to reach the new collateral ratio if the current ratio is greater than the target.
   *
   * Equations:
   *   Initially:
   *       n * v = nf * vf + nx * vx
   *
   * After removing dn base tokens and df stable coin tokens:
   *       (n - dn)*v = (nf - df)*vf + nx*vx
   *
   * The new collateral ratio:
   *       ((n - dn)*v) / ((nf - df)*vf) = ncr
   *
   * Solve these for df and dn:
   *   df = (ncr * nf * vf - n * v) / ((ncr - 1)*vf)
   *   dn = (ncr * nf * vf - n * v) / ((ncr - 1)*v)
   *
   * Here, df and dn now represent how many stable coin tokens and base tokens must be removed (redeemed)
   * to achieve ncr. If df > 0, it means we should redeem that many aTokens; if dn > 0, that many base tokens
   * can be released.
   *
   * @dev If the current collateral ratio <= ncr, no redemption is needed, so return 0.
   *
   * @param state Current state.
   * @param _newCollateralRatio Desired collateral ratio (scaled by 1e18).
   * @return _maxBaseOut The amount of base token released (dn).
   * @return _maxATokenRedeemable The amount of stable coin token that can be redeemed (df).
   */
  function maxRedeemableAToken(
    SwapState memory state,
    uint256 _newCollateralRatio
  ) internal pure returns (uint256 _maxBaseOut, uint256 _maxATokenRedeemable) {
    uint256 _baseVal = state.baseSupply * (state.baseNav) * (PRECISION);
    uint256 _aVal = _newCollateralRatio * (state.aSupply) * (PRECISION);

    if (_aVal > _baseVal) {
      uint256 _delta = _aVal - _baseVal;
      // Adjust ncr to (ncr - 1)*PRECISION
      _newCollateralRatio = _newCollateralRatio - (PRECISION);

      // df = delta / ((ncr - 1)*vf)
      _maxATokenRedeemable = _delta / (_newCollateralRatio * (state.aNav));

      // dn = delta / ((ncr - 1)*v)
      _maxBaseOut = _delta / (_newCollateralRatio * (state.baseNav));
    } else {
      _maxBaseOut = 0;
      _maxATokenRedeemable = 0;
    }
  }

  /**
   * @notice Compute how many leveraged tokens (xToken) can be redeemed and how much base token is released
   *         if the current collateral ratio is greater than the target.
   *
   * Equations:
   *   n * v = nf * vf + nx * vx
   *
   * After removing dn base tokens and dx leveraged tokens:
   *   (n - dn)*v = nf*vf + (nx - dx)*vx
   *
   * The new collateral ratio:
   *   ((n - dn)*v) / (nf*vf) = ncr
   *
   * Solve for dn and dx:
   *   dn = (n * v - ncr * nf * vf) / v
   *   dx = (n * v - ncr * nf * vf) / vx
   *
   * If dn > 0, that means base tokens can be redeemed; if dx > 0, that many xTokens can be redeemed.
   *
   * @dev If current collateral ratio <= ncr, return 0.
   *
   * @param state Current state.
   * @param _newCollateralRatio Desired collateral ratio (scaled by 1e18).
   * @return _maxBaseOut The base token released (dn).
   * @return _maxXTokenRedeemable The leveraged tokens (xToken) redeemable (dx).
   */
  function maxRedeemableXToken(
    SwapState memory state,
    uint256 _newCollateralRatio
  ) internal pure returns (uint256 _maxBaseOut, uint256 _maxXTokenRedeemable) {
    uint256 _baseVal = state.baseSupply * (state.baseNav) * (PRECISION);
    uint256 _aVal = _newCollateralRatio * (state.aSupply) * (state.aNav);

    if (_baseVal > _aVal) {
      uint256 _delta = _baseVal - _aVal;

      // dx = delta / (vx * PRECISION)
      _maxXTokenRedeemable = _delta / (state.xNav * (PRECISION));
      // dn = delta / (v * PRECISION)
      _maxBaseOut = _delta / (state.baseNav * (PRECISION));
    } else {
      _maxBaseOut = 0;
      _maxXTokenRedeemable = 0;
    }
  }

  /**
   * @notice Mint stable coin tokens (aToken) given a certain amount of base tokens (dn).
   *
   * Equations:
   *   n * v = nf * vf + nx * vx
   * After adding dn base and df fraction tokens:
   *   (n + dn)*v = (nf + df)*vf + nx*vx
   *
   * Solve for df given dn:
   *   df = (dn * v) / vf
   *
   * @param state Current state.
   * @param _baseIn Amount of base token supplied.
   * @return _aTokenOut Amount of stable coin token minted (df).
   */
  function mintAToken(SwapState memory state, uint256 _baseIn) internal pure returns (uint256 _aTokenOut) {
    // df = (dn * v) / vf
    _aTokenOut = (_baseIn * state.baseNav) / state.aNav;
  }

  /**
   * @notice Mint leveraged tokens (xToken) given a certain amount of base tokens (dn).
   *
   * Equations:
   *   n * v = nf * vf + nx * vx
   * After adding dn base tokens and dx leveraged tokens:
   *   (n + dn)*v = nf*vf + (nx + dx)*vx
   *
   * Solve for dx:
   *   dx = (dn * v * nx) / (n * v - nf * vf)
   *
   * @param state Current state.
   * @param _baseIn Amount of base token supplied.
   * @return _xTokenOut Amount of leveraged token minted (dx).
   */
  function mintXToken(SwapState memory state, uint256 _baseIn) internal pure returns (uint256 _xTokenOut) {
    // dx = (dn * v * nx) / (n * v - nf * vf)
    _xTokenOut = _baseIn * state.baseNav * state.xSupply;
    _xTokenOut = _xTokenOut / (state.baseSupply * state.baseNav - state.aSupply * state.aNav);
  }

  /**
   * @notice Redeem base tokens by supplying stable coin tokens (aToken) and/or leveraged tokens (xToken).
   *
   * Equations:
   *   n * v = nf * vf + nx * vx
   * After removing df fraction tokens and dx leveraged tokens:
   *   (n - dn)*v = (nf - df)*vf + (nx - dx)*vx
   *
   * The amount of baseOut (dn*v) depends on how many fraction and leveraged tokens are provided.
   *
   * If xSupply = 0 (no leveraged tokens):
   *   baseOut = (aTokenIn * vf) / v
   *
   * If xSupply > 0:
   *   baseOut = [ (aTokenIn * vf) + (xTokenIn * (n*v - nf*vf) / nx ) ] / v
   *
   * @param state Current state.
   * @param _aTokenIn stable coin tokens supplied.
   * @param _xTokenIn Leveraged tokens supplied.
   * @return _baseOut Amount of base token redeemed.
   */
  function redeem(SwapState memory state, uint256 _aTokenIn, uint256 _xTokenIn) internal pure returns (uint256 _baseOut) {
    uint256 _xVal = state.baseSupply * state.baseNav - state.aSupply * state.aNav;

    if (state.xSupply == 0) {
      _baseOut = (_aTokenIn * state.aNav) / state.baseNav;
    } else {
      _baseOut = _aTokenIn * state.aNav;
      _baseOut += (_xTokenIn * _xVal) / state.xSupply;
      _baseOut /= state.baseNav;
    }
  }

  /**
   * @notice Compute the current leverage ratio for the xToken.
   *
   * Define:
   *   rho = (aSupply * aNav) / (baseSupply * baseTwapNav)
   *
   * When beta = false, leverage ratio = 1 / (1 - rho)
   * If under-collateralized (rho >= 1), leverage ratio = MAX_LEVERAGE_RATIO
   *
   * @param state Current state.
   * @return ratio Current leverage ratio.
   */
  function leverageRatio(SwapState memory state) internal pure returns (uint256 ratio) {
    if (state.beta) return PRECISION;

    if(state.baseSupply == 0 || state.baseNav == 0 || state.baseTwapNav == 0) return 0;

    // rho = (aSupply * aNav * PRECISION) / (baseSupply * baseTwapNav)
    uint256 rho = (state.aSupply * state.aNav * PRECISION) / (state.baseSupply * state.baseTwapNav);

    if (rho >= PRECISION) {
      // Under-collateralized
      ratio = MAX_LEVERAGE_RATIO;
    } else {
      // ratio = 1 / (1 - rho)
      ratio = (PRECISION * PRECISION) / (PRECISION - rho);
      if (ratio > MAX_LEVERAGE_RATIO) ratio = MAX_LEVERAGE_RATIO;
    }
  }
}

File 50 of 54 : IPriceOracle.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

/**
 * @title IPriceOracle
 * @notice Interface for the PriceOracle contract, managing and updating token price feeds.
 */
interface IPriceOracle {
  /**
   * @dev Emitted when a price feed is added for a token.
   * @param token The address of the token.
   * @param feed The address of the Chainlink price feed.
   */
  event FeedAdded(address indexed token, address indexed feed);

  /**
   * @dev Emitted when a price feed is removed for a token.
   * @param token The address of the token.
   */
  event FeedRemoved(address indexed token);

  /**
   * @dev Emitted when a price feed is updated for a token.
   * @param token The address of the token.
   * @param oldFeed The address of the old price feed.
   * @param newFeed The address of the new price feed.
   */
  event FeedUpdated(address indexed token, address indexed oldFeed, address indexed newFeed);

  // Custom Errors
  error ZeroAddress();
  error InvalidAddress();
  error InvalidOperation();
  error InvalidAmount();
  error StalePrice();
  error InvalidInput();
  error DuplicateEntry();

  /**
   * @notice Gets the latest price data for a specified token.
   * @param token The address of the token.
   * @return isValid Whether the price is valid.
   * @return price The last updated price (18 decimals).
   */
  function getPrice(address token) external view returns (bool isValid, uint256 price);

  /**
   *  @notice gets the token decimals from the feed
   */
  function getFeedDecimals(address token) external view returns (uint8 decimals);
}

File 51 of 54 : IDEXRouter.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

interface IDEXRouter {
    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapExactETHForTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable returns (uint256[] memory amounts);

    function swapExactTokensForETH(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    // function getAmountsOut(
    //     uint256 amountIn,
    //     address[] calldata path
    // ) external view returns (uint256[] memory amounts);
}

File 52 of 54 : IStrategy.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

interface IStrategy {
    
    error ErrorNotPermitted();
    error ErrorZeroAmount();
    error ErrorZeroAddress();
    error ErrorCannotRecoverToken();
    error ZeroGroupId();

    function withdrawToTreasury(uint256 _diff) external returns (bool);
    function emergencyWithdraw() external returns (bool);
}

File 53 of 54 : ERC4626Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol)

pragma solidity ^0.8.0;

import "../ERC20Upgradeable.sol";
import "../utils/SafeERC20Upgradeable.sol";
import "../../../interfaces/IERC4626Upgradeable.sol";
import "../../../utils/math/MathUpgradeable.sol";
import {Initializable} from "../../../proxy/utils/Initializable.sol";

/**
 * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in
 * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626].
 *
 * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for
 * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends
 * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this
 * contract and not the "assets" token which is an independent contract.
 *
 * [CAUTION]
 * ====
 * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning
 * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation
 * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial
 * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may
 * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by
 * verifying the amount received is as expected, using a wrapper that performs these checks such as
 * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router].
 *
 * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()`
 * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault
 * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself
 * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset
 * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's
 * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more
 * expensive than it is profitable. More details about the underlying math can be found
 * xref:erc4626.adoc#inflation-attack[here].
 *
 * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued
 * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets
 * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience
 * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the
 * `_convertToShares` and `_convertToAssets` functions.
 *
 * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide].
 * ====
 *
 * _Available since v4.7._
 */
abstract contract ERC4626Upgradeable is Initializable, ERC20Upgradeable, IERC4626Upgradeable {
    using MathUpgradeable for uint256;

    IERC20Upgradeable private _asset;
    uint8 private _underlyingDecimals;

    /**
     * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777).
     */
    function __ERC4626_init(IERC20Upgradeable asset_) internal onlyInitializing {
        __ERC4626_init_unchained(asset_);
    }

    function __ERC4626_init_unchained(IERC20Upgradeable asset_) internal onlyInitializing {
        (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_);
        _underlyingDecimals = success ? assetDecimals : 18;
        _asset = asset_;
    }

    /**
     * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way.
     */
    function _tryGetAssetDecimals(IERC20Upgradeable asset_) private view returns (bool, uint8) {
        (bool success, bytes memory encodedDecimals) = address(asset_).staticcall(
            abi.encodeWithSelector(IERC20MetadataUpgradeable.decimals.selector)
        );
        if (success && encodedDecimals.length >= 32) {
            uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256));
            if (returnedDecimals <= type(uint8).max) {
                return (true, uint8(returnedDecimals));
            }
        }
        return (false, 0);
    }

    /**
     * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This
     * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the
     * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals.
     *
     * See {IERC20Metadata-decimals}.
     */
    function decimals() public view virtual override(IERC20MetadataUpgradeable, ERC20Upgradeable) returns (uint8) {
        return _underlyingDecimals + _decimalsOffset();
    }

    /** @dev See {IERC4626-asset}. */
    function asset() public view virtual override returns (address) {
        return address(_asset);
    }

    /** @dev See {IERC4626-totalAssets}. */
    function totalAssets() public view virtual override returns (uint256) {
        return _asset.balanceOf(address(this));
    }

    /** @dev See {IERC4626-convertToShares}. */
    function convertToShares(uint256 assets) public view virtual override returns (uint256) {
        return _convertToShares(assets, MathUpgradeable.Rounding.Down);
    }

    /** @dev See {IERC4626-convertToAssets}. */
    function convertToAssets(uint256 shares) public view virtual override returns (uint256) {
        return _convertToAssets(shares, MathUpgradeable.Rounding.Down);
    }

    /** @dev See {IERC4626-maxDeposit}. */
    function maxDeposit(address) public view virtual override returns (uint256) {
        return type(uint256).max;
    }

    /** @dev See {IERC4626-maxMint}. */
    function maxMint(address) public view virtual override returns (uint256) {
        return type(uint256).max;
    }

    /** @dev See {IERC4626-maxWithdraw}. */
    function maxWithdraw(address owner) public view virtual override returns (uint256) {
        return _convertToAssets(balanceOf(owner), MathUpgradeable.Rounding.Down);
    }

    /** @dev See {IERC4626-maxRedeem}. */
    function maxRedeem(address owner) public view virtual override returns (uint256) {
        return balanceOf(owner);
    }

    /** @dev See {IERC4626-previewDeposit}. */
    function previewDeposit(uint256 assets) public view virtual override returns (uint256) {
        return _convertToShares(assets, MathUpgradeable.Rounding.Down);
    }

    /** @dev See {IERC4626-previewMint}. */
    function previewMint(uint256 shares) public view virtual override returns (uint256) {
        return _convertToAssets(shares, MathUpgradeable.Rounding.Up);
    }

    /** @dev See {IERC4626-previewWithdraw}. */
    function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {
        return _convertToShares(assets, MathUpgradeable.Rounding.Up);
    }

    /** @dev See {IERC4626-previewRedeem}. */
    function previewRedeem(uint256 shares) public view virtual override returns (uint256) {
        return _convertToAssets(shares, MathUpgradeable.Rounding.Down);
    }

    /** @dev See {IERC4626-deposit}. */
    function deposit(uint256 assets, address receiver) public virtual override returns (uint256) {
        require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max");

        uint256 shares = previewDeposit(assets);
        _deposit(_msgSender(), receiver, assets, shares);

        return shares;
    }

    /** @dev See {IERC4626-mint}.
     *
     * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero.
     * In this case, the shares will be minted without requiring any assets to be deposited.
     */
    function mint(uint256 shares, address receiver) public virtual override returns (uint256) {
        require(shares <= maxMint(receiver), "ERC4626: mint more than max");

        uint256 assets = previewMint(shares);
        _deposit(_msgSender(), receiver, assets, shares);

        return assets;
    }

    /** @dev See {IERC4626-withdraw}. */
    function withdraw(uint256 assets, address receiver, address owner) public virtual override returns (uint256) {
        require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max");

        uint256 shares = previewWithdraw(assets);
        _withdraw(_msgSender(), receiver, owner, assets, shares);

        return shares;
    }

    /** @dev See {IERC4626-redeem}. */
    function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256) {
        require(shares <= maxRedeem(owner), "ERC4626: redeem more than max");

        uint256 assets = previewRedeem(shares);
        _withdraw(_msgSender(), receiver, owner, assets, shares);

        return assets;
    }

    /**
     * @dev Internal conversion function (from assets to shares) with support for rounding direction.
     */
    function _convertToShares(uint256 assets, MathUpgradeable.Rounding rounding) internal view virtual returns (uint256) {
        return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding);
    }

    /**
     * @dev Internal conversion function (from shares to assets) with support for rounding direction.
     */
    function _convertToAssets(uint256 shares, MathUpgradeable.Rounding rounding) internal view virtual returns (uint256) {
        return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding);
    }

    /**
     * @dev Deposit/mint common workflow.
     */
    function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual {
        // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the
        // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer,
        // calls the vault, which is assumed not malicious.
        //
        // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
        // assets are transferred and before the shares are minted, which is a valid state.
        // slither-disable-next-line reentrancy-no-eth
        SafeERC20Upgradeable.safeTransferFrom(_asset, caller, address(this), assets);
        _mint(receiver, shares);

        emit Deposit(caller, receiver, assets, shares);
    }

    /**
     * @dev Withdraw/redeem common workflow.
     */
    function _withdraw(
        address caller,
        address receiver,
        address owner,
        uint256 assets,
        uint256 shares
    ) internal virtual {
        if (caller != owner) {
            _spendAllowance(owner, caller, shares);
        }

        // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the
        // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer,
        // calls the vault, which is assumed not malicious.
        //
        // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the
        // shares are burned and after the assets are transferred, which is a valid state.
        _burn(owner, shares);
        SafeERC20Upgradeable.safeTransfer(_asset, receiver, assets);

        emit Withdraw(caller, receiver, owner, assets, shares);
    }

    function _decimalsOffset() internal view virtual returns (uint8) {
        return 0;
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}

File 54 of 54 : IERC4626Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol)

pragma solidity ^0.8.0;

import "../token/ERC20/IERC20Upgradeable.sol";
import "../token/ERC20/extensions/IERC20MetadataUpgradeable.sol";

/**
 * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
 * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
 *
 * _Available since v4.7._
 */
interface IERC4626Upgradeable is IERC20Upgradeable, IERC20MetadataUpgradeable {
    event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);

    event Withdraw(
        address indexed sender,
        address indexed receiver,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );

    /**
     * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
     *
     * - MUST be an ERC-20 token contract.
     * - MUST NOT revert.
     */
    function asset() external view returns (address assetTokenAddress);

    /**
     * @dev Returns the total amount of the underlying asset that is “managed” by Vault.
     *
     * - SHOULD include any compounding that occurs from yield.
     * - MUST be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT revert.
     */
    function totalAssets() external view returns (uint256 totalManagedAssets);

    /**
     * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToShares(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToAssets(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
     * through a deposit call.
     *
     * - MUST return a limited value if receiver is subject to some deposit limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
     * - MUST NOT revert.
     */
    function maxDeposit(address receiver) external view returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
     *   call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
     *   in the same transaction.
     * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
     *   deposit would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewDeposit(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   deposit execution, and are accounted for during deposit.
     * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    /**
     * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
     * - MUST return a limited value if receiver is subject to some mint limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
     * - MUST NOT revert.
     */
    function maxMint(address receiver) external view returns (uint256 maxShares);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
     *   in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
     *   same transaction.
     * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
     *   would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by minting.
     */
    function previewMint(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
     *   execution, and are accounted for during mint.
     * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
     * Vault, through a withdraw call.
     *
     * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
     * - MUST NOT revert.
     */
    function maxWithdraw(address owner) external view returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
     * given current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
     *   call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
     *   called
     *   in the same transaction.
     * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
     *   the withdrawal would be accepted, regardless if the user has enough shares, etc.
     * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewWithdraw(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
     *
     * - MUST emit the Withdraw event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   withdraw execution, and are accounted for during withdraw.
     * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
     *   not having enough shares, etc).
     *
     * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
     * Those methods should be performed separately.
     */
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);

    /**
     * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
     * through a redeem call.
     *
     * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
     * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
     * - MUST NOT revert.
     */
    function maxRedeem(address owner) external view returns (uint256 maxShares);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
     * given current on-chain conditions.
     *
     * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
     *   in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
     *   same transaction.
     * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
     *   redemption would be accepted, regardless if the user has enough shares, etc.
     * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by redeeming.
     */
    function previewRedeem(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
     *
     * - MUST emit the Withdraw event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   redeem execution, and are accounted for during redeem.
     * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
     *   not having enough shares, etc).
     *
     * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
     * Those methods should be performed separately.
     */
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}

Settings
{
  "remappings": [
    "@aave/=node_modules/@aave/",
    "@account-abstraction/=node_modules/@account-abstraction/",
    "@chainlink/=node_modules/@chainlink/",
    "@eth-optimism/=node_modules/@chainlink/contracts/node_modules/@eth-optimism/",
    "@openzeppelin/=node_modules/@openzeppelin/",
    "@uniswap/=node_modules/@uniswap/",
    "base64-sol/=node_modules/base64-sol/",
    "ds-test/=lib/ds-test/",
    "eth-gas-reporter/=node_modules/eth-gas-reporter/",
    "forge-std/=lib/forge-std/src/",
    "hardhat/=node_modules/hardhat/",
    "solidity-bytes-utils/=node_modules/solidity-bytes-utils/",
    "erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
    "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
    "openzeppelin/=lib/openzeppelin-contracts-upgradeable/contracts/",
    "solmate/=lib/solmate/src/",
    "abdk-libraries-solidity/=node_modules/abdk-libraries-solidity/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "shanghai",
  "viaIR": true,
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"BaseSupplyZero","type":"error"},{"inputs":[],"name":"BurnAmountExceedsBalance","type":"error"},{"inputs":[],"name":"CannotRecoverToken","type":"error"},{"inputs":[],"name":"CoolingOffPeriodActive","type":"error"},{"inputs":[],"name":"CoolingOffPeriodTooLarge","type":"error"},{"inputs":[],"name":"ErrorMinimumBurningAmount","type":"error"},{"inputs":[],"name":"ErrorMinimumMintingAmount","type":"error"},{"inputs":[],"name":"ExceedsMaxSupply","type":"error"},{"inputs":[],"name":"InvalidBaseTokenPrice","type":"error"},{"inputs":[],"name":"InvalidDecimals","type":"error"},{"inputs":[],"name":"InvalidFeeModel","type":"error"},{"inputs":[],"name":"InvalidFixedYieldAmount","type":"error"},{"inputs":[],"name":"InvalidFundingFeeRate","type":"error"},{"inputs":[],"name":"InvalidManagementFee","type":"error"},{"inputs":[],"name":"NotPermitted","type":"error"},{"inputs":[],"name":"ParameterUnchanged","type":"error"},{"inputs":[],"name":"TransferAmountExceedsBalance","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Burned","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"CoolingOffPeriodTriggered","type":"event"},{"anonymous":false,"inputs":[],"name":"EIP712DomainChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeesCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Minted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"oldValue","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"UpdateCoolingOffPeriod","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"oldRate","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"newRate","type":"uint256"}],"name":"UpdateFundingFeeRate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"groupId","type":"bytes32"}],"name":"UpdateGroup","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"oldRate","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"newRate","type":"uint256"}],"name":"UpdateManagementFeeRate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"oldMaxSupply","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"newMaxSupply","type":"uint256"}],"name":"UpdateMaxSupply","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldTreasury","type":"address"},{"indexed":true,"internalType":"address","name":"newTreasury","type":"address"}],"name":"UpdateTreasuryAddress","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"oldRate","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"newRate","type":"uint256"}],"name":"UpdateVariableFundingFeeRate","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_SUPPLY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TREASURY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"aToken","outputs":[{"internalType":"contract IAToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"annualFundingFeeRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"associatedGroupId","outputs":[{"internalType":"GroupId","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"beta","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collectFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"coolingOffPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decayFactor","outputs":[{"internalType":"bytes16","name":"","type":"bytes16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"dxTokenAmount","type":"uint256"}],"name":"dxTokenToBaseToken","outputs":[{"internalType":"uint256","name":"baseTokenAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeModel","outputs":[{"internalType":"enum FeeModel","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fixedYieldAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFeeModel","outputs":[{"internalType":"enum FeeModel","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"address","name":"aTokenAddress","type":"address"},{"internalType":"address","name":"treasuryAddress","type":"address"},{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"uint256","name":"maxSupply","type":"uint256"},{"internalType":"uint256","name":"initialCoolingOffPeriod","type":"uint256"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"internalType":"struct DDXToken.TokenParams","name":"tokenParams","type":"tuple"},{"components":[{"internalType":"enum FeeModel","name":"feeModel","type":"uint8"},{"internalType":"uint256","name":"managementFeeRate","type":"uint256"},{"internalType":"uint256","name":"fixedYieldAmount","type":"uint256"},{"internalType":"uint256","name":"annualFundingFeeRate","type":"uint256"}],"internalType":"struct DDXToken.FeeParams","name":"feeParams","type":"tuple"},{"internalType":"address","name":"_admin","type":"address"},{"internalType":"bool","name":"_beta","type":"bool"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lastDecayTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"managementFeeRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"mintAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nav","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"tokenAmount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"groupId","type":"bytes32"}],"name":"setGroup","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"treasury","outputs":[{"internalType":"contract ITreasury","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newCoolingOffPeriod","type":"uint256"}],"name":"updateCoolingOffPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newRate","type":"uint256"}],"name":"updateFundingFeeRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newRate","type":"uint256"}],"name":"updateManagementFeeRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newSupply","type":"uint256"}],"name":"updateMaxSupply","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newTreasury","type":"address"}],"name":"updateTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newAmount","type":"uint256"}],"name":"updateVariableFundingFeeRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

60808060405234610117575f5460ff8160081c16159182809361010a575b80156100f3575b1561009a575060ff1981166001175f5581610088575b5061004f575b604051615b25908161011c8239f35b61ff00195f54165f557f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498602060405160018152a1610040565b61ffff1916610101175f90815561003a565b62461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608490fd5b50303b1580156100245750600160ff831614610024565b50600160ff83161061001d565b5f80fdfe6080806040526004361015610034575b503615610025576339218f3b60e01b5f5260045ffd5b6339218f3b60e01b5f5260045ffd5b5f3560e01c90816301ffc9a7146126e55750806306fdde0314612653578063095ea7b31461262d578063124ca5291461255e57806318160ddd146125445780631cb257df146124bf57806320fb30161461249457806323b872dd146122d1578063248a9ca3146122a65780632f2ff15d146122005780632febaa74146121e2578063313ce567146121c157806332cb6b0c146121a35780633644e5151461218957806336568abe1461212a57806339509351146120dc5780633f4ba83a1461204757806340c10f1914611f255780635c975abb14611f0257806361d027b314611ed957806370a0823114611e8157806372a4c42514611e3457806372b8e66214611e16578063767e29f414611df85780637ecebe0014611dc05780637f51bb1f14611c305780638456cb5914611ada57806384b0196e146119ce5780638980f11f1461184f5780638c2a84dd14610dc457806391a543b614610da657806391d1485414610d5d57806395d89b4114610c9257806397ee114414610a9657806397f2d09914610c745780639dc29fac14610b125780639faa3c9114610aec578063a0c1f15e14610ac3578063a111723114610a96578063a217fddf14610a7c578063a457c2d7146109ce578063a9059cbb1461086f578063b5a12d8e146107db578063bcd897d6146107a2578063bd80ee4314610784578063c1590cd714610762578063c879657214610732578063d11a57ec1461070b578063d505accf1461056c578063d547741f146104b2578063dd62ed3e14610462578063e63ab1e91461043b578063f103b433146103da578063f6dabb561461036e5763fa5d61611461029d575f61000f565b3461036a57602036600319011261036a576004356102b961350c565b60ff61019a5460a01c166006811015610356576002036103475780158015610335575b6103265761019d548082146103175781907f6d1dd8ca82ec8d9b85ef901ed35091fc5da0e020e66be1f7cd65da7b4c0404d35f80a361019d55005b630807e00f60e01b5f5260045ffd5b635bec58ad60e11b5f5260045ffd5b50683635c9adc5dea0000081116102dc565b6308e7052d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b3461036a57602036600319011261036a5760043561038a61350c565b6201518081116103cb57610196548082146103175781907fa35891b1e3dd3cde5b3c2daa4942c8fda86ea73e919846d38216cb36b0748d965f80a361019655005b6353205c9560e01b5f5260045ffd5b3461036a57602036600319011261036a576004356103f661350c565b6101955480821461031757610409612988565b82106100255781907f6a84334bf6663b783f2bbfcaf459b2cbc73570cf346a46d9e6a0f290fcf3ebfc5f80a361019555005b3461036a575f36600319011261036a5760206040515f516020615a505f395f51905f528152f35b3461036a57604036600319011261036a5761047b612798565b6104836127ae565b6001600160a01b039182165f908152603460209081526040808320949093168252928352819020549051908152f35b3461036a57604036600319011261036a576004356104ce6127ae565b906104ed6104e8825f5260fe602052600160405f20015490565b6135aa565b805f5260fe60205260405f2060018060a01b0383165f5260205260ff60405f20541661051557005b5f81815260fe602090815260408083206001600160a01b0395909516808452949091528120805460ff19169055339291907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9080a4005b3461036a5760e036600319011261036a57610585612798565b61058d6127ae565b6044359060643560843560ff8116810361036a578142116106c6576106606106689160018060a01b03871693845f52609960205260405f20908154916001830190556040519060208201927f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9845287604084015260018060a01b038916606084015289608084015260a083015260c082015260c0815261062e60e08261280f565b519020610639613b70565b906040519161190160f01b83526002830152602282015260c43591604260a4359220613f65565b919091613fe0565b6001600160a01b0316036106815761067f92612d90565b005b60405162461bcd60e51b815260206004820152601e60248201527f45524332305065726d69743a20696e76616c6964207369676e617475726500006044820152606490fd5b60405162461bcd60e51b815260206004820152601d60248201527f45524332305065726d69743a206578706972656420646561646c696e650000006044820152606490fd5b3461036a575f36600319011261036a5760206040515f516020615ab05f395f51905f528152f35b3461036a575f36600319011261036a5761074a61366a565b61075261355e565b61075a612cc3565b600161016255005b3461036a575f36600319011261036a57602061077c612a81565b604051908152f35b3461036a575f36600319011261036a57602061019b54604051908152f35b3461036a57602036600319011261036a576001600160a01b036107c3612798565b165f526101a2602052602060405f2054604051908152f35b3461036a57602036600319011261036a576004356107f761350c565b60ff61019a5460a01c166006811015610356576003036103475780158015610864575b6108555761019e548082146103175781907fcf54f00eeca1672a389ca93ff0502efe437c7462c04d9b4414c267b04a522e785f80a361019e55005b63dad4fd6160e01b5f5260045ffd5b50610bb8811161081a565b3461036a57604036600319011261036a57610888612798565b6024356108936136c2565b331580156109bd575b6109ae576108a8613707565b6108d56108d06108c36108be6101945485612957565b613050565b6101a05460801b9061372c565b6134a2565b91335f526101a26020526108ed60405f205442612a5c565b610196541161097957335f526101a160205260405f205483811061096a578361091591612a5c565b335f526101a160205260405f205560018060a01b031691825f526101a160205261094460405f209182546129a8565b90556040519081525f516020615a905f395f51905f5260203392a3602060405160018152f35b635dd58b8b60e01b5f5260045ffd5b42337f79ac2681a86c290166a70a3ceb9184346b4dc726de698af4f964a0fe1751f6fb5f80a363ad88028160e01b5f5260045ffd5b63d92e233d60e01b5f5260045ffd5b506001600160a01b0382161561089c565b3461036a57604036600319011261036a576109e7612798565b60243590335f52603460205260405f2060018060a01b0382165f5260205260405f205491808310610a2957610a1e92039033612d90565b602060405160018152f35b60405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608490fd5b3461036a575f36600319011261036a5760206040515f8152f35b3461036a575f36600319011261036a57610abf60ff61019a5460a01c16604051918291826127c4565b0390f35b3461036a575f36600319011261036a57610199546040516001600160a01b039091168152602090f35b3461036a575f36600319011261036a57602060ff6101a05460801c166040519015158152f35b3461036a57604036600319011261036a57610b2b612798565b60243590610b3761366a565b610b3f6136c2565b610b4761355e565b620f42408210610c65576001600160a01b031680156109ae57805f526101a2602052610b7760405f205442612a5c565b6101965411610c3057610b88613707565b610b9e6108d06108c36108be6101945486612957565b815f526101a160205260405f205490808210610c2157610bc181610bd893612a5c565b835f526101a160205260405f205561019f54612a5c565b61019f555f815f516020615a905f395f51905f526020604051868152a37f696de425f79f4a40bc6d2122ca50507f0efbeabbff86a84871b7196ab8ea8df75f80a3600161016255005b630bba337f60e11b5f5260045ffd5b42907f79ac2681a86c290166a70a3ceb9184346b4dc726de698af4f964a0fe1751f6fb5f80a363ad88028160e01b5f5260045ffd5b633f42625760e01b5f5260045ffd5b3461036a575f36600319011261036a57602061019854604051908152f35b3461036a575f36600319011261036a576040515f603754610cb2816127d7565b8084529060018116908115610d395750600114610cee575b610abf83610cda8185038261280f565b604051918291602083526020830190612773565b60375f9081525f516020615ad05f395f51905f52939250905b808210610d1f57509091508101602001610cda610cca565b919260018160209254838588010152019101909291610d07565b60ff191660208086019190915291151560051b84019091019150610cda9050610cca565b3461036a57604036600319011261036a57610d766127ae565b6004355f5260fe60205260405f209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b3461036a575f36600319011261036a57602061019c54604051908152f35b3461036a5760e036600319011261036a5760043567ffffffffffffffff811161036a578060040190610100600319823603011261036a57608036602319011261036a5760a4356001600160a01b038116929083900361036a5760c4359081151580920361036a575f549260ff8460081c161593848095611842575b801561182b575b156117cf5760ff1981166001175f55846117be575b50604481016001600160a01b03610e71826129b5565b16156109ae5760648201916001600160a01b03610e8d846129b5565b16156109ae5786156109ae5760e4810160ff610ea8826129c9565b161580156117a9575b61179a5760c4820135946201518086116103cb57610ef7610ed282806129d7565b610eef610ee560248895949501866129d7565b9490923691612a26565b923691612a26565b90610f1160ff5f5460081c16610f0c8161399c565b61399c565b80519067ffffffffffffffff82116114c0578190610f306036546127d7565b601f8111611728575b50602090601f83116001146116b7575f926116ac575b50508160011b915f199060031b1c1916176036555b80519067ffffffffffffffff82116114c057610f816037546127d7565b601f8111611644575b50602090601f83116001146115cd579180610fcd9492610fc6945f926115c2575b50508160011b915f199060031b1c1916176037555b806129d7565b3691612a26565b9760ff5f5460081c16610fdf8161399c565b6040996110058b5192610ff28d8561280f565b60018452603160f81b602085015261399c565b80519067ffffffffffffffff82116114c05781906110246067546127d7565b601f8111611550575b50602090601f83116001146114df575f926114d4575b50508160011b915f199060031b1c1916176067555b80519067ffffffffffffffff82116114c0576110756068546127d7565b601f8111611458575b50602090601f83116001146113de57928261115c93608497969360ff965f926113d3575b50508160011b915f199060031b1c1916176068555b5f6065555f6066556110f8845f5460081c166110d28161399c565b6110db8161399c565b6110e48161399c565b8519610130541661013055610f0c8161399c565b6001610162555f805260fe6020528b5f20815f52602052838c5f2054161561139c575b61112c611127896129b5565b6135e9565b5f516020615a505f395f51905f525f5260fe6020528b5f20815f52602052838c5f2054161561134a575b506129c9565b1660ff196101975416176101975560a48101356101955501356101985561118960018060a01b03916129b5565b166bffffffffffffffffffffffff60a01b610199541617610199556111b460018060a01b03916129b5565b169061019a54906101965560243591600683101561036a576001600160a81b03199091161760a091821b60ff60a01b161761019a819055901c60ff16600681101561035657600181036112d25750604435801580156112c7575b6112b85761019c555b60ff6101975416604d81116112a45761123790600a0a8061019455613050565b60801c6101a054914261019b5560ff60801b9060801b169170ffffffffffffffffffffffffffffffffff191617176101a05561126f57005b60207f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989161ff00195f54165f555160018152a1005b634e487b7160e01b5f52601160045260245ffd5b638e9b51ff60e01b5f5260045ffd5b50610bb8811161120e565b600281036113075750606435801580156112f5575b6103265761019d555b611217565b50683635c9adc5dea0000081116112e7565b60038103611334575060843580158015611329575b6108555761019e55611217565b50610bb8811161131c565b155f036112f0576308e7052d60e21b5f5260045ffd5b5f516020615a505f395f51905f525f5260fe6020528b5f20815f526020528b5f206001851982541617905533905f516020615a505f395f51905f525f5160206159d05f395f51905f525f80a48b611156565b5f805260fe6020528b5f20815f526020528b5f206001851982541617905533815f5f5160206159d05f395f51905f528180a461111b565b015190508e806110a2565b90601f1983169160685f525f516020615a705f395f51905f52925f5b818110611440575093608497969360ff96936001938361115c9810611428575b505050811b016068556110b7565b01515f1960f88460031b161c191690558e808061141a565b929360206001819287860151815501950193016113fa565b60685f52601f830160051c5f516020615a705f395f51905f520190602084106114ab575b601f0160051c5f516020615a705f395f51905f5201905b8181106114a0575061107e565b5f8155600101611493565b5f516020615a705f395f51905f52915061147c565b634e487b7160e01b5f52604160045260245ffd5b015190508c80611043565b60675f9081525f516020615a305f395f51905f529350601f198516905b8181106115385750908460019594939210611520575b505050811b01606755611058565b01515f1960f88460031b161c191690558c8080611512565b929360206001819287860151815501950193016114fc565b90915060675f52601f830160051c5f516020615a305f395f51905f520190602084106115ad575b90601f8493920160051c5f516020615a305f395f51905f5201905b81811061159f575061102d565b5f8155849350600101611592565b5f516020615a305f395f51905f529150611577565b015190508d80610fab565b90601f1983169160375f525f516020615ad05f395f51905f52925f5b81811061162c5750926001928592610fc696610fcd989610611614575b505050811b01603755610fc0565b01515f1960f88460031b161c191690558d8080611606565b929360206001819287860151815501950193016115e9565b60375f52601f830160051c5f516020615ad05f395f51905f52019060208410611697575b601f0160051c5f516020615ad05f395f51905f5201905b81811061168c5750610f8a565b5f815560010161167f565b5f516020615ad05f395f51905f529150611668565b015190508c80610f4f565b60365f9081525f516020615a105f395f51905f529350601f198516905b81811061171057509084600195949392106116f8575b505050811b01603655610f64565b01515f1960f88460031b161c191690558c80806116ea565b929360206001819287860151815501950193016116d4565b90915060365f52601f830160051c5f516020615a105f395f51905f52019060208410611785575b90601f8493920160051c5f516020615a105f395f51905f5201905b8181106117775750610f39565b5f815584935060010161176a565b5f516020615a105f395f51905f52915061174f565b630692acc560e51b5f5260045ffd5b50601260ff6117b7836129c9565b1611610eb1565b61ffff1916610101175f5585610e5b565b60405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608490fd5b50303b158015610e465750600160ff821614610e46565b50600160ff821610610e3f565b3461036a57604036600319011261036a57611868612798565b61187061366a565b61187861350c565b6001600160a01b03163081146119bf5761192990604051905f806020840163a9059cbb60e01b81523360248601526024356044860152604485526118bd60648661280f565b604051946118cc60408761280f565b602086527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65646020870152519082855af13d156119b7573d9161190d83612a0a565b9261191b604051948561280f565b83523d5f602085013e615936565b805190811591821561199d575b50501561194557600161016255005b60405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608490fd5b6119b09250602080918301019101612a69565b8180611936565b606091615936565b6380eb2a0160e01b5f5260045ffd5b3461036a575f36600319011261036a576065541580611ad0575b15611a9357611a376119f8612831565b611a006128d5565b6020611a4560405192611a13838561280f565b5f84525f368137604051958695600f60f81b875260e08588015260e0870190612773565b908582036040870152612773565b4660608501523060808501525f60a085015283810360c08501528180845192838152019301915f5b828110611a7c57505050500390f35b835185528695509381019392810192600101611a6d565b60405162461bcd60e51b81526020600482015260156024820152741152540dcc4c8e88155b9a5b9a5d1a585b1a5e9959605a1b6044820152606490fd5b50606654156119e8565b3461036a575f36600319011261036a57335f9081527f91a139d95687315930077a8e61e11d3a6ef29f94bb4d435fbbb8ba7608364937602052604090205460ff1615611b6357611b286136c2565b600160ff19610130541617610130557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a1005b611c2c611b6f3361573d565b611c0c6011611b8a5f516020615a505f395f51905f52615823565b9260376040519485927f416363657373436f6e74726f6c3a206163636f756e74200000000000000000006020850152611bcc8151809260208688019101612752565b83017001034b99036b4b9b9b4b733903937b6329607d1b83820152611bfb825180936020604885019101612752565b01010301601f19810183528261280f565b60405162461bcd60e51b8152602060048201529182916024830190612773565b0390fd5b3461036a57602036600319011261036a57611c49612798565b611c5161350c565b6001600160a01b0381169081156109ae5761019a80546001600160a01b0319811684179091555f516020615ab05f395f51905f525f5260fe6020527fda24b4380d7c1044352b34a0472755b2b85c5ec87ad6f44aa3cd7277d1fab7f7546001600160a01b0390911691611cc791611127906135aa565b5f516020615ab05f395f51905f525f5260fe6020527fda24b4380d7c1044352b34a0472755b2b85c5ec87ad6f44aa3cd7277d1fab7f754611d07906135aa565b6001600160a01b0381165f9081525f5160206159f05f395f51905f52602052604090205460ff16611d59575b7f5634a90413b79beba6c5f37aa8f19d1aee84a5320ff20ac7bd1ac63280867d5c5f80a3005b6001600160a01b0381165f9081525f5160206159f05f395f51905f5260205260408120805460ff19169055339082905f516020615ab05f395f51905f52907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9080a4611d33565b3461036a57602036600319011261036a576001600160a01b03611de1612798565b165f526099602052602060405f2054604051908152f35b3461036a575f36600319011261036a57602061019e54604051908152f35b3461036a575f36600319011261036a57602061019d54604051908152f35b3461036a57602036600319011261036a57600435611e5061350c565b80156109ae5780610198557f8fc06d8c0744b7cc9ac27129e8de216bee71eeb601b4bc040ec32669b60bffc85f80a2005b3461036a57602036600319011261036a57602061077c611ecf6108d0611ea5612798565b611ead612e94565b9060018060a01b03165f526101a18552611eca60405f2054613050565b613265565b610194549061296a565b3461036a575f36600319011261036a5761019a546040516001600160a01b039091168152602090f35b3461036a575f36600319011261036a57602060ff61013054166040519015158152f35b3461036a57604036600319011261036a57611f3e612798565b60243590611f4a61366a565b611f526136c2565b611f5a61355e565b620f42408210612038576001600160a01b031680156109ae57611f8482611f7f612988565b6129a8565b610195541061202957611f95613707565b611fd1611fae6108d06108c36108be6101945487612957565b825f526101a160205260405f20611fc68282546129a8565b905561019f546129a8565b61019f55805f526101a26020524260405f2055805f5f516020615a905f395f51905f526020604051868152a37f30385c845b448a36257a6a1716e6ad2e1bc2cbe333cde1e69fe849ad6511adfe5f80a3600161016255005b63c30436e960e01b5f5260045ffd5b63225a295160e21b5f5260045ffd5b3461036a575f36600319011261036a5761205f61350c565b6101305460ff8116156120a05760ff1916610130557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a1005b60405162461bcd60e51b815260206004820152601460248201527314185d5cd8589b194e881b9bdd081c185d5cd95960621b6044820152606490fd5b3461036a57604036600319011261036a57610a1e6120f8612798565b335f52603460205260405f2060018060a01b0382165f5260205261212360405f2060243590546129a8565b9033612d90565b3461036a57604036600319011261036a576121436127ae565b5060405162461bcd60e51b815260206004820152601860248201527f526f6c65732063616e27742062652072656e6f756e63656400000000000000006044820152606490fd5b3461036a575f36600319011261036a57602061077c613b70565b3461036a575f36600319011261036a57602061019554604051908152f35b3461036a575f36600319011261036a57602060ff6101975416604051908152f35b3461036a575f36600319011261036a57602061019654604051908152f35b3461036a57604036600319011261036a5760043561221c6127ae565b906122366104e8825f5260fe602052600160405f20015490565b805f5260fe60205260405f2060018060a01b0383165f5260205260ff60405f2054161561225f57005b5f81815260fe602090815260408083206001600160a01b0395909516808452949091528120805460ff19166001179055339291905f5160206159d05f395f51905f529080a4005b3461036a57602036600319011261036a57602061077c6004355f5260fe602052600160405f20015490565b3461036a57606036600319011261036a576122ea612798565b6122f26127ae565b6001600160a01b0382165f8181526034602090815260408083203384529091529020546044359391929160018201612433575b505061232f6136c2565b81158015612422575b6109ae57612344613707565b61235a6108d06108c36108be6101945487612957565b92825f526101a260205261237260405f205442612a5c565b61019654116123ed57825f526101a160205260405f20549084821061096a575f516020615a905f395f51905f52926123ac86602094612a5c565b855f526101a1845260405f205560018060a01b031694855f526101a183526123d960405f209182546129a8565b9055604051908152a3602060405160018152f35b42837f79ac2681a86c290166a70a3ceb9184346b4dc726de698af4f964a0fe1751f6fb5f80a363ad88028160e01b5f5260045ffd5b506001600160a01b03811615612338565b84821061244f57846124489203903390612d90565b8380612325565b60405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b3461036a575f36600319011261036a5760206101a05460801b604051906001600160801b0319168152f35b3461036a57602036600319011261036a576004356124db61350c565b60ff61019a5460a01c166006811015610356576001036103475780158015612539575b6112b85761019c548082146103175781907f563865324a2fcb87f9aa83da48c42017705ef1c6d1969604120a693538fa28b95f80a361019c55005b50610bb881116124fe565b3461036a575f36600319011261036a57602061077c612988565b3461036a57602036600319011261036a57612577612a81565b60018060a01b0361019a541690602061019854602460405180958193631cd80bed60e21b835260048301525afa918215612622575f926125ee575b50801580156125e6575b6125d7576020916125d261077c92600435612957565b61296a565b63375e76af60e21b5f5260045ffd5b5081156125bc565b9091506020813d60201161261a575b8161260a6020938361280f565b8101031261036a575190826125b2565b3d91506125fd565b6040513d5f823e3d90fd5b3461036a57604036600319011261036a57610a1e612649612798565b6024359033612d90565b3461036a575f36600319011261036a576040515f603654612673816127d7565b8084529060018116908115610d39575060011461269a57610abf83610cda8185038261280f565b60365f9081525f516020615a105f395f51905f52939250905b8082106126cb57509091508101602001610cda610cca565b9192600181602092548385880101520191019092916126b3565b3461036a57602036600319011261036a576004359063ffffffff60e01b821680920361036a57602091630cefc5c160e31b8114908115612727575b5015158152f35b637965db0b60e01b811491508115612741575b5083612720565b6301ffc9a760e01b1490508361273a565b5f5b8381106127635750505f910152565b8181015183820152602001612754565b9060209161278c81518092818552858086019101612752565b601f01601f1916010190565b600435906001600160a01b038216820361036a57565b602435906001600160a01b038216820361036a57565b9190602083019260068210156103565752565b90600182811c92168015612805575b60208310146127f157565b634e487b7160e01b5f52602260045260245ffd5b91607f16916127e6565b90601f8019910116810190811067ffffffffffffffff8211176114c057604052565b604051905f8260675491612844836127d7565b80835292600181169081156128b6575060011461286a575b6128689250038361280f565b565b5060675f90815290915f516020615a305f395f51905f525b81831061289a5750509060206128689282010161285c565b6020919350806001915483858901015201910190918492612882565b6020925061286894915060ff191682840152151560051b82010161285c565b604051905f82606854916128e8836127d7565b80835292600181169081156128b6575060011461290b576128689250038361280f565b5060685f90815290915f516020615a705f395f51905f525b81831061293b5750509060206128689282010161285c565b6020919350806001915483858901015201910190918492612923565b818102929181159184041417156112a457565b8115612974570490565b634e487b7160e01b5f52601260045260245ffd5b6129a5611ecf6108d0612999612e94565b611eca61019f54613050565b90565b919082018092116112a457565b356001600160a01b038116810361036a5790565b3560ff8116810361036a5790565b903590601e198136030182121561036a570180359067ffffffffffffffff821161036a5760200191813603831361036a57565b67ffffffffffffffff81116114c057601f01601f191660200190565b929192612a3282612a0a565b91612a40604051938461280f565b82948184528183011161036a578281602093845f960137010152565b919082039182116112a457565b9081602091031261036a5751801515810361036a5790565b61019a5461019854604051631cd80bed60e21b8152600481018290526001600160a01b0390921691602081602481865afa908115612622575f91612c91575b5060405163430cc02960e11b81526004810183905292602084602481845afa938415612622575f94612c5c575b50600490612af9612988565b610199546040516318160ddd60e01b81529195919360209185919082906001600160a01b03165afa928315612622575f93612c28575b5060ff6101a05460801c16612bf657602090602460405180948193631367580960e21b835260048301525afa908115612622575f91612bc7575b50612bbf57606483108015612bb5575b612baa57612ba4612b906129a5956125d294612957565b612b9e61019454809261296a565b92612957565b90612a5c565b505050506101945490565b5060648410612b79565b505050505f90565b612be9915060203d602011612bef575b612be1818361280f565b810190612a69565b5f612b69565b503d612bd7565b50506064612c0784839694966129a8565b10612baa57612c1c612c22926129a595612957565b926129a8565b9061296a565b9092506020813d602011612c54575b81612c446020938361280f565b8101031261036a5751915f612b2f565b3d9150612c37565b9093506020813d602011612c89575b81612c786020938361280f565b8101031261036a5751926004612aed565b3d9150612c6b565b90506020813d602011612cbb575b81612cac6020938361280f565b8101031261036a57515f612ac0565b3d9150612c9f565b612cd96101a05460801b611eca61019f54613050565b612ce1613707565b612ced61019f54613050565b612cfe6101a05460801b8092613265565b905f612d0a84846139fc565b810b1215612d8b57612d5c612d396108d0612d32612d6596611ecf96600160ff1b1890613bd7565b938461372c565b60018060a01b0361019a54165f526101a160205260405f20611fc68282546129a8565b61019f556134a2565b7f860c0aa5520013080c2f65981705fcdea474d9f7c3daf954656ed5e65d692d1f5f80a2565b505050565b6001600160a01b0316908115612e43576001600160a01b0316918215612df35760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591835f526034825260405f20855f5282528060405f2055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b612ea161019b5442612a5c565b60648110612fdb576101a05460801b8060ff61019a5460a01c1660068110156103565760018103612f24575050612f14612f1f612f1a6129a594612f14612efb612eed61019c54613050565b612ef5612fe6565b9061372c565b600160ff1b90612f0d90612ef5613042565b1891613050565b90613265565b6130bd565b6140e4565b60038103612f4d575050612f14612f1f612f1a6129a594612f14612efb612eed61019e54613050565b90915060028103612fd1575061019f549160648310612fcb57612f76612f809161019d54612957565b6101945490612957565b620151808302928084046201518014901517156112a457612fac6108be612fb394600160ff1b9361296a565b1890613bd7565b5f612fbe81836139fc565b810b12156129a557505f90565b50905090565b9091506103475790565b506101a05460801b90565b612710612ff281613ab1565b90607082101561302e57816070031b5b6001600160701b0316613fff90910160701b6001600160801b03161760801b6001600160801b03191690565b607082111561300257606f1982011c613002565b6301e13380612ff281613ab1565b8061305a57505f90565b8061306482613ab1565b9160708310156130a05750816070031b6001600160701b0316613fff90910160701b6001600160801b03161760801b6001600160801b03191690565b607083116130af575b50613002565b606f1983011c90505f6130a9565b5f617fff8260f01c16617fff81145f146130e15750156129a5575061ffff60ef1b90565b608083901c6001600160701b031691508061325957506001905b6e0171547652b82fe1777d0ffda0d23a0290811561322b57613fff01600160e11b821061320b5760e15b8181019061407082101561317257505050505f905f905b6001600160801b03199260701b906001607f1b906f1fffb8aa3b295c17f0bbbe87fed0691d60811b1860801c16171760801b1690565b6140e08210156131b157505061407081101561319557614070031c5b5f9161313c565b61407081116131a5575b5061318e565b61406f19011b5f61319f565b91509161c0dd83115f146131cd57505050617fff905f9061313c565b6001600160701b039160708111156131f257606f19011c5b16906140de19019161313c565b60708110613201575b506131e5565b6070031b5f6131fb565b600160e01b821061321d5760e0613125565b61322682613ab1565b613125565b50506f1fffb8aa3b295c17f0bbbe87fed0691d60811b18600160ff1b161561325557600160ff1b90565b5f90565b90600160701b176130fb565b90617fff8260f01c16617fff8260f01c1690617fff81145f146132f85750617fff036132d1576001600160801b0319828116908216036132a957600160ff1b161890565b908082186001600160801b031916600160ff1b036132c5571790565b5061ffff60ef1b919050565b600160801b600160ff1b0381166132ee575061ffff60ef1b919050565b600160ff1b161890565b617fff8203613323575090919050600160801b600160ff1b0381166132ee575061ffff60ef1b919050565b6001600160701b038460801c169080155f1461349357506001915b6001600160701b038460801c169080155f1461348457506001915b0291821561346d5701600160e11b821061344d5760e15b818101906140708210156133aa5750505050905f915f915b6001600160801b03199360701b916001607f1b911860801c16171760801b1690565b6140e08210156133eb5750506140708110156133ce57614070031c905b5f92613388565b61407081116133df575b50906133c7565b61406f19011b5f6133d8565b9394939092915061c0dd83111561340a57505050617fff915f91613388565b929391926001600160701b039190607081111561343457606f19011c5b16916140de190192613388565b60708110613443575b50613427565b6070031b5f61343d565b600160e01b821061345f5760e0613370565b61346882613ab1565b613370565b50505018600160ff1b161561325557600160ff1b90565b91600160701b90911790613359565b91600160701b9091179061333e565b617fff8160801c9160f01c1690613fff8210613506576001607f1b81101561036a576140fe821161036a576001600160701b0316600160701b179061406f8110156134ef5761406f031c90565b61406f81116134fd575b5090565b61406e19011b90565b50505f90565b335f9081527f32796e36004994222362c2f9423d5e208bb848170964890784a8d59ed40f50af602052604090205460ff161561354457565b611c2c6135503361573d565b611c0c6011611b8a5f615823565b335f9081525f5160206159f05f395f51905f52602052604090205460ff161561358357565b611c2c61358f3361573d565b611c0c6011611b8a5f516020615ab05f395f51905f52615823565b5f81815260fe6020908152604080832033845290915290205460ff16156135ce5750565b611c2c90611c0c6011611b8a6135e33361573d565b93615823565b6001600160a01b0381165f9081525f5160206159f05f395f51905f52602052604090205460ff16156136185750565b6001600160a01b03165f8181525f5160206159f05f395f51905f5260205260408120805460ff191660011790553391905f516020615ab05f395f51905f52905f5160206159d05f395f51905f529080a4565b6002610162541461367d57600261016255565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b60ff61013054166136cf57565b60405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606490fd5b61370f612e94565b60801c6001600160801b03196101a05416176101a0554261019b55565b90617fff8260f01c16617fff8260f01c16617fff82145f1461375f57617fff141590506132ee575061ffff60ef1b919050565b617fff81036137995750506dffffffffffffffffffffffffffff60801b81161561378f575061ffff60ef1b919050565b18600160ff1b1690565b600160801b600160ff1b0383166137db575050600160801b600160ff1b0382166137c9575061ffff60ef1b919050565b617fff60f01b9118600160ff1b161790565b6001600160701b038360801c169080155f1461399057506001905b6001600160701b038560801c1683155f1461397d5780613955575b9061381b9161296a565b91821561346d576001606c1b831061394157600160731b831061390e5761384183613ab1565b81810190614071840182111561387857505050505018608090811c6001607f1b16617fff60701b17901b6001600160801b03191690565b83613ffc8301105f14613894575050505050905f915f91613388565b83613f8c8301105f146138dd575050613ffc8101828111156138bf575003613ffc011b905f92613388565b82116138ce575b5050906133c7565b9003613ffb19011c5f806138c6565b613f8d939250938460706001600160701b0393979611613903575b501693030192613388565b606f19011c5f6138f8565b600160721b83106139245760ff60725b16613841565b600160711b83106139385760ff607161391e565b60ff607061391e565b634e487b7160e01b5f52600160045260245ffd5b925061381b9061396484613ab1565b60e20393841b9260019460711991010192909150613811565b61381b9190600160701b1760721b61296a565b90600160701b176137f6565b156139a357565b60405162461bcd60e51b815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201526a6e697469616c697a696e6760a81b6064820152608490fd5b8060801c60016001607f1b03811691617fff60701b831161036a578360801c9060016001607f1b03821694617fff60701b861161036a576001600160801b0319918216911614801581613aa3575b1561036a5715613a5c57505050505f90565b6001607f1b908111159110613a875715613a80571115613a7b575f1990565b600190565b50505f1990565b15613a93575050600190565b1115613a9e57600190565b5f1990565b50617fff60701b8410613a4a565b801561036a575f90600160801b811015613b65575b80600160401b6002921015613b59575b640100000000811015613b4d575b62010000811015613b41575b610100811015613b35575b6010811015613b29575b6004811015613b1e575b1015613b185790565b60010190565b91810191811c613b0f565b6004928301921c613b05565b6008928301921c613afb565b6010928301921c613af0565b6020928301921c613ae4565b6040928301921c613ad6565b60809150811c613ac6565b613b786158c5565b613b80615910565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a08152613bd160c08261280f565b51902090565b908160801c617fff8360f01c16908260801c617fff8460f01c1692617fff81145f14613c2857505050617fff036134f9576001600160801b0319828116911603613c1e5790565b5061ffff60ef1b90565b909192617fff81969596145f14613c4157505050505090565b6001607f1b841015936001600160701b03169180613f5657506001925b6001600160701b038116916001607f1b909110159080613f48575060015b83613ca55750505050506001600160801b03198316600160ff1b141591506129a5905057505f90565b8293949596979192155f14613cd85750505050506001600160801b03198316600160ff1b141591506129a5905057505f90565b80860392878103613dd357506070831315613cf7575050505050505090565b90919293949596505f83135f14613d985750501c905b0190600160711b821015613d8c575b617fff8103613d4157505015613d38576001600160f01b031990565b617fff60f01b90565b600160701b821015613d7b57505f915b15613d75576001607f1b5b6001600160801b0380199360701b9116171760801b1690565b5f613d5c565b91906001600160701b031690613d51565b600191821c9101613d1c565b9095949391606f19811215613db05750505050505090565b909192939495505f8112613dc6575b5050613d0d565b9093505f031c5f80613dbf565b95949280949798505f919250135f14613f28575060011b915f1901935b6070821315613ed25750506001915b828210613ec95750035b8015613ec257613e1881613ab1565b60718103613e7257506001918201911c6001600160701b03165b617fff8203613e4e57505015613d38576001600160f01b031990565b909115613d755760709190911b176001607f1b1760801b6001600160801b03191690565b91906070831015613eb157826070038083115f14613ea35791909201606f190191901b6001600160701b0316613e32565b5091505f19011b5f90613e32565b6001600160701b0391925016613e32565b5050505f90565b93509003613e09565b6001821315613eec575f1990810191011c60010191613dff565b9290606f19811215613f02575060019150613dff565b5f198112613f11575b50613dff565b600191925f1901905f19905f03011c01905f613f0b565b5f839692949612613f3a575b50613df0565b5f1901945060011b5f613f34565b600160701b90921791613c7c565b92600160701b90921791613c5e565b7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411613fd5576020935f9360ff60809460405194855216868401526040830152606082015282805260015afa15612622575f516001600160a01b03811615613fcd57905f90565b505f90600190565b505050505f90600390565b60058110156103565780613ff15750565b6001810361403e5760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e617475726500000000000000006044820152606490fd5b6002810361408b5760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606490fd5b60031461409457565b60405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608490fd5b8060801c6001600160701b03617fff6001607f1b83119360f01c16911690617fff81148061570f575b1561411f575061ffff60ef1b92915050565b61400d81111561413457505015613d38575f90565b613f7f81101561414b5750613fff60f01b92915050565b80615701575060015b613fef8111156156e557613fee19011b905b80806156d7575b61350657801580806156bc575b6156af576001600160801b038360801c931691806156a6575b615698575b6001607f1b91808316615680575b6001607e1b8116615663575b6001607d1b8116615646575b6001607c1b8116615629575b6001607b1b811661560c575b6001607a1b81166155ef575b600160791b81166155d2575b600160781b81166155b5575b600160771b8116615598575b600160761b811661557b575b600160751b811661555e575b600160741b8116615541575b600160731b8116615524575b600160721b8116615507575b600160711b81166154ea575b600160701b81166154cd575b6001606f1b81166154b0575b6001606e1b8116615493575b6001606d1b8116615476575b6001606c1b8116615459575b6001606b1b811661543c575b6001606a1b811661541f575b600160691b8116615402575b600160681b81166153e5575b600160671b81166153c8575b600160661b81166153ab575b600160651b811661538e575b600160641b8116615371575b600160631b8116615354575b600160621b8116615337575b600160611b811661531a575b600160601b81166152fd575b6001605f1b81166152e0575b6001605e1b81166152c3575b6001605d1b81166152a6575b6001605c1b8116615289575b6001605b1b811661526c575b6001605a1b811661524f575b600160591b8116615232575b600160581b8116615215575b600160571b81166151f8575b600160561b81166151db575b600160551b81166151be575b600160541b81166151a1575b600160531b8116615184575b600160521b8116615167575b600160511b811661514a575b600160501b811661512d575b69800000000000000000008116615110575b694000000000000000000081166150f3575b692000000000000000000081166150d6575b691000000000000000000081166150b9575b6908000000000000000000811661509c575b6904000000000000000000811661507f575b69020000000000000000008116615062575b69010000000000000000008116615045575b688000000000000000008116615028575b68400000000000000000811661500b575b682000000000000000008116614fee575b681000000000000000008116614fd1575b680800000000000000008116614fb4575b680400000000000000008116614f97575b680200000000000000008116614f7a575b600160401b8116614f5d575b6780000000000000008116614f40575b6740000000000000008116614f23575b6720000000000000008116614f06575b6710000000000000008116614ee9575b6708000000000000008116614ecc575b6704000000000000008116614eaf575b6702000000000000008116614e92575b6701000000000000008116614e75575b66800000000000008116614e58575b66400000000000008116614e3b575b66200000000000008116614e1e575b66100000000000008116614e01575b66080000000000008116614de4575b66040000000000008116614dc7575b66020000000000008116614daa575b66010000000000008116614d8d575b658000000000008116614d70575b654000000000008116614d53575b652000000000008116614d36575b651000000000008116614d19575b650800000000008116614cfc575b650400000000008116614cdf575b650200000000008116614cc2575b650100000000008116614ca5575b6480000000008116614c88575b6440000000008116614c6b575b6420000000008116614c4e575b6410000000008116614c31575b6408000000008116614c14575b6404000000008116614bf7575b6402000000008116614bda575b6401000000008116614bbd575b63800000008116614ba0575b63400000008116614b83575b63200000008116614b66575b63100000008116614b49575b63080000008116614b2c575b63040000008116614b0f575b63020000008116614af2575b63010000008116614ad5575b628000008116614ab8575b624000008116614a9b575b622000008116614a7e575b621000008116614a61575b620800008116614a44575b620400008116614a27575b620200008116614a0a575b6201000081166149ed575b61800081166149d0575b61400081166149b3575b6120008116614996575b6110008116614979575b610800811661495c575b610400811661493f575b6102008116614922575b6101008116614905575b608081166148e8575b604081166148cb575b602081166148ae575b60108116614891575b60088116614873575b600416614856575b15614828576001600160701b03613fff91600f1c169101905b6001600160801b03199160701b1760801b1690565b613ffe8211614849576001600160701b0390600f1c1690613fff0390614813565b5f91613fee19011c614813565b907001000000000000000000000000000000010260801c906147fa565b70010000000000000000000000000000000490920260801c916147f2565b9170010000000000000000000000000000000a0260801c916147e9565b917001000000000000000000000000000000150260801c916147e0565b9170010000000000000000000000000000002b0260801c916147d7565b917001000000000000000000000000000000570260801c916147ce565b917001000000000000000000000000000000b00260801c916147c5565b917001000000000000000000000000000001610260801c916147bb565b917001000000000000000000000000000002c40260801c916147b1565b9170010000000000000000000000000000058a0260801c916147a7565b91700100000000000000000000000000000b160260801c9161479d565b9170010000000000000000000000000000162d0260801c91614793565b91700100000000000000000000000000002c5b0260801c91614789565b917001000000000000000000000000000058b80260801c9161477f565b9170010000000000000000000000000000b1710260801c91614775565b917001000000000000000000000000000162e30260801c9161476a565b9170010000000000000000000000000002c5c70260801c9161475f565b91700100000000000000000000000000058b8f0260801c91614754565b917001000000000000000000000000000b17200260801c91614749565b91700100000000000000000000000000162e410260801c9161473e565b917001000000000000000000000000002c5c840260801c91614733565b9170010000000000000000000000000058b90a0260801c91614728565b91700100000000000000000000000000b172160260801c9161471d565b9170010000000000000000000000000162e42e0260801c91614711565b91700100000000000000000000000002c5c85e0260801c91614705565b917001000000000000000000000000058b90be0260801c916146f9565b9170010000000000000000000000000b17217e0260801c916146ed565b917001000000000000000000000000162e42fd0260801c916146e1565b9170010000000000000000000000002c5c85fc0260801c916146d5565b91700100000000000000000000000058b90bfa0260801c916146c9565b917001000000000000000000000000b17217f60260801c916146bd565b91700100000000000000000000000162e42fee0260801c916146b0565b917001000000000000000000000002c5c85fde0260801c916146a3565b9170010000000000000000000000058b90bfbd0260801c91614696565b91700100000000000000000000000b17217f7c0260801c91614689565b9170010000000000000000000000162e42fef90260801c9161467c565b91700100000000000000000000002c5c85fdf30260801c9161466f565b917001000000000000000000000058b90bfbe70260801c91614662565b9170010000000000000000000000b17217f7d00260801c91614655565b917001000000000000000000000162e42fefa20260801c91614647565b9170010000000000000000000002c5c85fdf460260801c91614639565b91700100000000000000000000058b90bfbe8d0260801c9161462b565b917001000000000000000000000b17217f7d1b0260801c9161461d565b91700100000000000000000000162e42fefa380260801c9161460f565b917001000000000000000000002c5c85fdf4720260801c91614601565b9170010000000000000000000058b90bfbe8e60260801c916145f3565b91700100000000000000000000b17217f7d1ce0260801c916145e5565b9170010000000000000000000162e42fefa39d0260801c916145d6565b91700100000000000000000002c5c85fdf473c0260801c916145c7565b917001000000000000000000058b90bfbe8e7a0260801c916145b8565b9170010000000000000000000b17217f7d1cf60260801c916145a9565b917001000000000000000000162e42fefa39ee0260801c9161459a565b9170010000000000000000002c5c85fdf473dd0260801c9161458b565b91700100000000000000000058b90bfbe8e7bb0260801c9161457c565b917001000000000000000000b17217f7d1cf780260801c9161456d565b91700100000000000000000162e42fefa39ef20260801c9161455d565b917001000000000000000002c5c85fdf473de50260801c9161454d565b9170010000000000000000058b90bfbe8e7bcc0260801c9161453d565b91700100000000000000000b17217f7d1cf7990260801c9161452d565b9170010000000000000000162e42fefa39ef340260801c9161451d565b91700100000000000000002c5c85fdf473de6a0260801c9161450d565b917001000000000000000058b90bfbe8e7bcd50260801c916144fd565b9170010000000000000000b17217f7d1cf79ab0260801c916144ed565b917001000000000000000162e42fefa39ef3580260801c916144e1565b9170010000000000000002c5c85fdf473de6b20260801c916144d0565b91700100000000000000058b90bfbe8e7bcd6d0260801c916144bf565b917001000000000000000b17217f7d1cf79afa0260801c916144ae565b91700100000000000000162e42fefa39ef366f0260801c9161449d565b917001000000000000002c5c85fdf473de6eca0260801c9161448c565b9170010000000000000058b90bfbe8e7bce5440260801c9161447b565b91700100000000000000b17217f7d1cf79e9490260801c9161446a565b9170010000000000000162e42fefa39ef44d910260801c91614458565b91700100000000000002c5c85fdf473dea871f0260801c91614446565b917001000000000000058b90bfbe8e7bdcbe2e0260801c91614434565b9170010000000000000b17217f7d1cf7d83c1a0260801c91614422565b917001000000000000162e42fefa39f02b772c0260801c91614410565b9170010000000000002c5c85fdf473e242ea380260801c916143fe565b91700100000000000058b90bfbe8e7cc35c3f00260801c916143ec565b917001000000000000b17217f7d1cfb72b45e10260801c916143da565b91700100000000000162e42fefa39fe95583c20260801c916143ce565b917001000000000002c5c85fdf4741bea6e77e0260801c916143c2565b9170010000000000058b90bfbe8e8b2d3d4ede0260801c916143b6565b91700100000000000b17217f7d1d351a389d400260801c916143aa565b9170010000000000162e42fefa3ae53369388c0260801c9161439e565b91700100000000002c5c85fdf477b662b269450260801c91614392565b917001000000000058b90bfbe8f71cb4e4b33d0260801c91614386565b9170010000000000b17217f7d20cf927c8e94c0260801c9161437a565b917001000000000162e42fefa494f1478fde050260801c9161436e565b9170010000000002c5c85fdf4b15de6f17eb0d0260801c91614362565b91700100000000058b90bfbe9ddbac5e109cce0260801c91614356565b917001000000000b17217f7d5a7716bba4a9ae0260801c9161434a565b91700100000000162e42fefb2fed257559bdaa0260801c9161433e565b917001000000002c5c85fdf84bd62ae30a74cc0260801c91614332565b9170010000000058b90bfbf8479bd5a81b51ad0260801c91614326565b91700100000000b17217f80f4ef5aadda455540260801c9161431a565b9170010000000162e42ff0999ce3541b9fffcf0260801c9161430e565b91700100000002c5c85fe31f35a6a30da1be500260801c91614302565b917001000000058b90bfcdee5acd3c1cedc8230260801c916142f6565b9170010000000b17217fba9c739aa5819f44f90260801c916142ea565b917001000000162e42fff037df38aa2b219f060260801c916142de565b9170010000002c5c8601cc6b9e94213c72737a0260801c916142d2565b91700100000058b90c0b48c6be5df846c5b2ef0260801c916142c6565b917001000000b1721835514b86e6d96efd1bfe0260801c916142ba565b91700100000162e430e5a18f6119e3c02282a50260801c916142ae565b917001000002c5c863b73f016468f6bac5ca2b0260801c916142a2565b9170010000058b90cf1e6d97f9ca14dbcc16280260801c91614296565b91700100000b1721bcfc99d9f890ea069117630260801c9161428a565b9170010000162e43f4f831060e02d839a9d16d0260801c9161427e565b91700100002c5c89d5ec6ca4d7c8acc017b7c90260801c91614272565b917001000058b91b5bc9ae2eed81e9b7d4cfab0260801c91614266565b9170010000b17255775c040618bf4a4ade83fc0260801c9161425a565b917001000162e525ee054754457d59952920260260801c9161424e565b9170010002c5cc37da9491d0985c348c68e7b30260801c91614242565b91700100058ba01fb9f96d6cacd4b180917c3d0260801c91614236565b917001000b175effdc76ba38e31671ca9397250260801c9161422a565b91700100162f3904051fa128bca9c55c31e5df0260801c9161421e565b917001002c605e2e8cec506d21bfc89a23a00f0260801c91614212565b9170010058c86da1c09ea1ff19d294cf2f679b0260801c91614206565b91700100b1afa5abcbed6129ab13ec11dc95430260801c916141fa565b9170010163da9fb33356d84a66ae336dcdfa3f0260801c916141ee565b91700102c9a3e778060ee6f7caca4f7a29bde80260801c916141e2565b917001059b0d31585743ae7c548eb68ca417fd0260801c916141d6565b9170010b5586cf9890f6298b92b71842a983630260801c916141ca565b917001172b83c7d517adcdf7c8c50eb14a791f0260801c916141be565b917001306fe0a31b7152de8d5a46305c85edec0260801c916141b2565b6fb504f333f9de6484597d89b3754abe9f92506141a6565b919060019019910191614198565b50811515614193565b50617fff60f01b92915050565b50713fffffffffffffffffffffffffffffffffff831161417a565b5061203760811b821161416d565b613fef81106156f6575b5090614166565b613fef031c5f6156ef565b600160701b90911790614154565b5081151561410d565b908151811015615729570160200190565b634e487b7160e01b5f52603260045260245ffd5b615747602a612a0a565b90615755604051928361280f565b602a8252615763602a612a0a565b6020830190601f19013682378251156157295760309053815160011015615729576078602183015360295b600181116157e2575061579e5790565b606460405162461bcd60e51b815260206004820152602060248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152fd5b90600f81166010811015615729576f181899199a1a9b1b9c1cb0b131b232b360811b901a6158108385615718565b5360041c9080156112a4575f190161578e565b61582d6042612a0a565b9061583b604051928361280f565b604282526158496042612a0a565b6020830190601f19013682378251156157295760309053815160011015615729576078602183015360415b60018111615884575061579e5790565b90600f81166010811015615729576f181899199a1a9b1b9c1cb0b131b232b360811b901a6158b28385615718565b5360041c9080156112a4575f1901615874565b6158cd612831565b80519081156158dd576020012090565b505060655480156158eb5790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b6159186128d5565b8051908115615928576020012090565b505060665480156158eb5790565b91929015615998575081511561594a575090565b3b156159535790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b8251909150156159ab5750805190602001fd5b60405162461bcd60e51b815260206004820152908190611c2c90602483019061277356fe2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0dda24b4380d7c1044352b34a0472755b2b85c5ec87ad6f44aa3cd7277d1fab7f64a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b89787eeb91fe3101235e4a76063c7023ecb40f923f97916639c598592fa30d6ae65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862aa2153420d844928b4421650203c77babc8b33d7f2e7b450e2966db0c22097753ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efe1dcbdb91df27212a29bc27177c840cf2f819ecf2187432e1fac86c2dd5dfca942a7b7dd785cd69714a189dffb3fd7d7174edc9ece837694ce50f7078f7c31aea2646970667358221220b89ca9042a1211c99dfe2477a82a102311dfc75fc5032a9a5baf639f99750a8664736f6c634300081c0033

Deployed Bytecode

0x6080806040526004361015610034575b503615610025576339218f3b60e01b5f5260045ffd5b6339218f3b60e01b5f5260045ffd5b5f3560e01c90816301ffc9a7146126e55750806306fdde0314612653578063095ea7b31461262d578063124ca5291461255e57806318160ddd146125445780631cb257df146124bf57806320fb30161461249457806323b872dd146122d1578063248a9ca3146122a65780632f2ff15d146122005780632febaa74146121e2578063313ce567146121c157806332cb6b0c146121a35780633644e5151461218957806336568abe1461212a57806339509351146120dc5780633f4ba83a1461204757806340c10f1914611f255780635c975abb14611f0257806361d027b314611ed957806370a0823114611e8157806372a4c42514611e3457806372b8e66214611e16578063767e29f414611df85780637ecebe0014611dc05780637f51bb1f14611c305780638456cb5914611ada57806384b0196e146119ce5780638980f11f1461184f5780638c2a84dd14610dc457806391a543b614610da657806391d1485414610d5d57806395d89b4114610c9257806397ee114414610a9657806397f2d09914610c745780639dc29fac14610b125780639faa3c9114610aec578063a0c1f15e14610ac3578063a111723114610a96578063a217fddf14610a7c578063a457c2d7146109ce578063a9059cbb1461086f578063b5a12d8e146107db578063bcd897d6146107a2578063bd80ee4314610784578063c1590cd714610762578063c879657214610732578063d11a57ec1461070b578063d505accf1461056c578063d547741f146104b2578063dd62ed3e14610462578063e63ab1e91461043b578063f103b433146103da578063f6dabb561461036e5763fa5d61611461029d575f61000f565b3461036a57602036600319011261036a576004356102b961350c565b60ff61019a5460a01c166006811015610356576002036103475780158015610335575b6103265761019d548082146103175781907f6d1dd8ca82ec8d9b85ef901ed35091fc5da0e020e66be1f7cd65da7b4c0404d35f80a361019d55005b630807e00f60e01b5f5260045ffd5b635bec58ad60e11b5f5260045ffd5b50683635c9adc5dea0000081116102dc565b6308e7052d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b3461036a57602036600319011261036a5760043561038a61350c565b6201518081116103cb57610196548082146103175781907fa35891b1e3dd3cde5b3c2daa4942c8fda86ea73e919846d38216cb36b0748d965f80a361019655005b6353205c9560e01b5f5260045ffd5b3461036a57602036600319011261036a576004356103f661350c565b6101955480821461031757610409612988565b82106100255781907f6a84334bf6663b783f2bbfcaf459b2cbc73570cf346a46d9e6a0f290fcf3ebfc5f80a361019555005b3461036a575f36600319011261036a5760206040515f516020615a505f395f51905f528152f35b3461036a57604036600319011261036a5761047b612798565b6104836127ae565b6001600160a01b039182165f908152603460209081526040808320949093168252928352819020549051908152f35b3461036a57604036600319011261036a576004356104ce6127ae565b906104ed6104e8825f5260fe602052600160405f20015490565b6135aa565b805f5260fe60205260405f2060018060a01b0383165f5260205260ff60405f20541661051557005b5f81815260fe602090815260408083206001600160a01b0395909516808452949091528120805460ff19169055339291907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9080a4005b3461036a5760e036600319011261036a57610585612798565b61058d6127ae565b6044359060643560843560ff8116810361036a578142116106c6576106606106689160018060a01b03871693845f52609960205260405f20908154916001830190556040519060208201927f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9845287604084015260018060a01b038916606084015289608084015260a083015260c082015260c0815261062e60e08261280f565b519020610639613b70565b906040519161190160f01b83526002830152602282015260c43591604260a4359220613f65565b919091613fe0565b6001600160a01b0316036106815761067f92612d90565b005b60405162461bcd60e51b815260206004820152601e60248201527f45524332305065726d69743a20696e76616c6964207369676e617475726500006044820152606490fd5b60405162461bcd60e51b815260206004820152601d60248201527f45524332305065726d69743a206578706972656420646561646c696e650000006044820152606490fd5b3461036a575f36600319011261036a5760206040515f516020615ab05f395f51905f528152f35b3461036a575f36600319011261036a5761074a61366a565b61075261355e565b61075a612cc3565b600161016255005b3461036a575f36600319011261036a57602061077c612a81565b604051908152f35b3461036a575f36600319011261036a57602061019b54604051908152f35b3461036a57602036600319011261036a576001600160a01b036107c3612798565b165f526101a2602052602060405f2054604051908152f35b3461036a57602036600319011261036a576004356107f761350c565b60ff61019a5460a01c166006811015610356576003036103475780158015610864575b6108555761019e548082146103175781907fcf54f00eeca1672a389ca93ff0502efe437c7462c04d9b4414c267b04a522e785f80a361019e55005b63dad4fd6160e01b5f5260045ffd5b50610bb8811161081a565b3461036a57604036600319011261036a57610888612798565b6024356108936136c2565b331580156109bd575b6109ae576108a8613707565b6108d56108d06108c36108be6101945485612957565b613050565b6101a05460801b9061372c565b6134a2565b91335f526101a26020526108ed60405f205442612a5c565b610196541161097957335f526101a160205260405f205483811061096a578361091591612a5c565b335f526101a160205260405f205560018060a01b031691825f526101a160205261094460405f209182546129a8565b90556040519081525f516020615a905f395f51905f5260203392a3602060405160018152f35b635dd58b8b60e01b5f5260045ffd5b42337f79ac2681a86c290166a70a3ceb9184346b4dc726de698af4f964a0fe1751f6fb5f80a363ad88028160e01b5f5260045ffd5b63d92e233d60e01b5f5260045ffd5b506001600160a01b0382161561089c565b3461036a57604036600319011261036a576109e7612798565b60243590335f52603460205260405f2060018060a01b0382165f5260205260405f205491808310610a2957610a1e92039033612d90565b602060405160018152f35b60405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608490fd5b3461036a575f36600319011261036a5760206040515f8152f35b3461036a575f36600319011261036a57610abf60ff61019a5460a01c16604051918291826127c4565b0390f35b3461036a575f36600319011261036a57610199546040516001600160a01b039091168152602090f35b3461036a575f36600319011261036a57602060ff6101a05460801c166040519015158152f35b3461036a57604036600319011261036a57610b2b612798565b60243590610b3761366a565b610b3f6136c2565b610b4761355e565b620f42408210610c65576001600160a01b031680156109ae57805f526101a2602052610b7760405f205442612a5c565b6101965411610c3057610b88613707565b610b9e6108d06108c36108be6101945486612957565b815f526101a160205260405f205490808210610c2157610bc181610bd893612a5c565b835f526101a160205260405f205561019f54612a5c565b61019f555f815f516020615a905f395f51905f526020604051868152a37f696de425f79f4a40bc6d2122ca50507f0efbeabbff86a84871b7196ab8ea8df75f80a3600161016255005b630bba337f60e11b5f5260045ffd5b42907f79ac2681a86c290166a70a3ceb9184346b4dc726de698af4f964a0fe1751f6fb5f80a363ad88028160e01b5f5260045ffd5b633f42625760e01b5f5260045ffd5b3461036a575f36600319011261036a57602061019854604051908152f35b3461036a575f36600319011261036a576040515f603754610cb2816127d7565b8084529060018116908115610d395750600114610cee575b610abf83610cda8185038261280f565b604051918291602083526020830190612773565b60375f9081525f516020615ad05f395f51905f52939250905b808210610d1f57509091508101602001610cda610cca565b919260018160209254838588010152019101909291610d07565b60ff191660208086019190915291151560051b84019091019150610cda9050610cca565b3461036a57604036600319011261036a57610d766127ae565b6004355f5260fe60205260405f209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b3461036a575f36600319011261036a57602061019c54604051908152f35b3461036a5760e036600319011261036a5760043567ffffffffffffffff811161036a578060040190610100600319823603011261036a57608036602319011261036a5760a4356001600160a01b038116929083900361036a5760c4359081151580920361036a575f549260ff8460081c161593848095611842575b801561182b575b156117cf5760ff1981166001175f55846117be575b50604481016001600160a01b03610e71826129b5565b16156109ae5760648201916001600160a01b03610e8d846129b5565b16156109ae5786156109ae5760e4810160ff610ea8826129c9565b161580156117a9575b61179a5760c4820135946201518086116103cb57610ef7610ed282806129d7565b610eef610ee560248895949501866129d7565b9490923691612a26565b923691612a26565b90610f1160ff5f5460081c16610f0c8161399c565b61399c565b80519067ffffffffffffffff82116114c0578190610f306036546127d7565b601f8111611728575b50602090601f83116001146116b7575f926116ac575b50508160011b915f199060031b1c1916176036555b80519067ffffffffffffffff82116114c057610f816037546127d7565b601f8111611644575b50602090601f83116001146115cd579180610fcd9492610fc6945f926115c2575b50508160011b915f199060031b1c1916176037555b806129d7565b3691612a26565b9760ff5f5460081c16610fdf8161399c565b6040996110058b5192610ff28d8561280f565b60018452603160f81b602085015261399c565b80519067ffffffffffffffff82116114c05781906110246067546127d7565b601f8111611550575b50602090601f83116001146114df575f926114d4575b50508160011b915f199060031b1c1916176067555b80519067ffffffffffffffff82116114c0576110756068546127d7565b601f8111611458575b50602090601f83116001146113de57928261115c93608497969360ff965f926113d3575b50508160011b915f199060031b1c1916176068555b5f6065555f6066556110f8845f5460081c166110d28161399c565b6110db8161399c565b6110e48161399c565b8519610130541661013055610f0c8161399c565b6001610162555f805260fe6020528b5f20815f52602052838c5f2054161561139c575b61112c611127896129b5565b6135e9565b5f516020615a505f395f51905f525f5260fe6020528b5f20815f52602052838c5f2054161561134a575b506129c9565b1660ff196101975416176101975560a48101356101955501356101985561118960018060a01b03916129b5565b166bffffffffffffffffffffffff60a01b610199541617610199556111b460018060a01b03916129b5565b169061019a54906101965560243591600683101561036a576001600160a81b03199091161760a091821b60ff60a01b161761019a819055901c60ff16600681101561035657600181036112d25750604435801580156112c7575b6112b85761019c555b60ff6101975416604d81116112a45761123790600a0a8061019455613050565b60801c6101a054914261019b5560ff60801b9060801b169170ffffffffffffffffffffffffffffffffff191617176101a05561126f57005b60207f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989161ff00195f54165f555160018152a1005b634e487b7160e01b5f52601160045260245ffd5b638e9b51ff60e01b5f5260045ffd5b50610bb8811161120e565b600281036113075750606435801580156112f5575b6103265761019d555b611217565b50683635c9adc5dea0000081116112e7565b60038103611334575060843580158015611329575b6108555761019e55611217565b50610bb8811161131c565b155f036112f0576308e7052d60e21b5f5260045ffd5b5f516020615a505f395f51905f525f5260fe6020528b5f20815f526020528b5f206001851982541617905533905f516020615a505f395f51905f525f5160206159d05f395f51905f525f80a48b611156565b5f805260fe6020528b5f20815f526020528b5f206001851982541617905533815f5f5160206159d05f395f51905f528180a461111b565b015190508e806110a2565b90601f1983169160685f525f516020615a705f395f51905f52925f5b818110611440575093608497969360ff96936001938361115c9810611428575b505050811b016068556110b7565b01515f1960f88460031b161c191690558e808061141a565b929360206001819287860151815501950193016113fa565b60685f52601f830160051c5f516020615a705f395f51905f520190602084106114ab575b601f0160051c5f516020615a705f395f51905f5201905b8181106114a0575061107e565b5f8155600101611493565b5f516020615a705f395f51905f52915061147c565b634e487b7160e01b5f52604160045260245ffd5b015190508c80611043565b60675f9081525f516020615a305f395f51905f529350601f198516905b8181106115385750908460019594939210611520575b505050811b01606755611058565b01515f1960f88460031b161c191690558c8080611512565b929360206001819287860151815501950193016114fc565b90915060675f52601f830160051c5f516020615a305f395f51905f520190602084106115ad575b90601f8493920160051c5f516020615a305f395f51905f5201905b81811061159f575061102d565b5f8155849350600101611592565b5f516020615a305f395f51905f529150611577565b015190508d80610fab565b90601f1983169160375f525f516020615ad05f395f51905f52925f5b81811061162c5750926001928592610fc696610fcd989610611614575b505050811b01603755610fc0565b01515f1960f88460031b161c191690558d8080611606565b929360206001819287860151815501950193016115e9565b60375f52601f830160051c5f516020615ad05f395f51905f52019060208410611697575b601f0160051c5f516020615ad05f395f51905f5201905b81811061168c5750610f8a565b5f815560010161167f565b5f516020615ad05f395f51905f529150611668565b015190508c80610f4f565b60365f9081525f516020615a105f395f51905f529350601f198516905b81811061171057509084600195949392106116f8575b505050811b01603655610f64565b01515f1960f88460031b161c191690558c80806116ea565b929360206001819287860151815501950193016116d4565b90915060365f52601f830160051c5f516020615a105f395f51905f52019060208410611785575b90601f8493920160051c5f516020615a105f395f51905f5201905b8181106117775750610f39565b5f815584935060010161176a565b5f516020615a105f395f51905f52915061174f565b630692acc560e51b5f5260045ffd5b50601260ff6117b7836129c9565b1611610eb1565b61ffff1916610101175f5585610e5b565b60405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608490fd5b50303b158015610e465750600160ff821614610e46565b50600160ff821610610e3f565b3461036a57604036600319011261036a57611868612798565b61187061366a565b61187861350c565b6001600160a01b03163081146119bf5761192990604051905f806020840163a9059cbb60e01b81523360248601526024356044860152604485526118bd60648661280f565b604051946118cc60408761280f565b602086527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65646020870152519082855af13d156119b7573d9161190d83612a0a565b9261191b604051948561280f565b83523d5f602085013e615936565b805190811591821561199d575b50501561194557600161016255005b60405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608490fd5b6119b09250602080918301019101612a69565b8180611936565b606091615936565b6380eb2a0160e01b5f5260045ffd5b3461036a575f36600319011261036a576065541580611ad0575b15611a9357611a376119f8612831565b611a006128d5565b6020611a4560405192611a13838561280f565b5f84525f368137604051958695600f60f81b875260e08588015260e0870190612773565b908582036040870152612773565b4660608501523060808501525f60a085015283810360c08501528180845192838152019301915f5b828110611a7c57505050500390f35b835185528695509381019392810192600101611a6d565b60405162461bcd60e51b81526020600482015260156024820152741152540dcc4c8e88155b9a5b9a5d1a585b1a5e9959605a1b6044820152606490fd5b50606654156119e8565b3461036a575f36600319011261036a57335f9081527f91a139d95687315930077a8e61e11d3a6ef29f94bb4d435fbbb8ba7608364937602052604090205460ff1615611b6357611b286136c2565b600160ff19610130541617610130557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a1005b611c2c611b6f3361573d565b611c0c6011611b8a5f516020615a505f395f51905f52615823565b9260376040519485927f416363657373436f6e74726f6c3a206163636f756e74200000000000000000006020850152611bcc8151809260208688019101612752565b83017001034b99036b4b9b9b4b733903937b6329607d1b83820152611bfb825180936020604885019101612752565b01010301601f19810183528261280f565b60405162461bcd60e51b8152602060048201529182916024830190612773565b0390fd5b3461036a57602036600319011261036a57611c49612798565b611c5161350c565b6001600160a01b0381169081156109ae5761019a80546001600160a01b0319811684179091555f516020615ab05f395f51905f525f5260fe6020527fda24b4380d7c1044352b34a0472755b2b85c5ec87ad6f44aa3cd7277d1fab7f7546001600160a01b0390911691611cc791611127906135aa565b5f516020615ab05f395f51905f525f5260fe6020527fda24b4380d7c1044352b34a0472755b2b85c5ec87ad6f44aa3cd7277d1fab7f754611d07906135aa565b6001600160a01b0381165f9081525f5160206159f05f395f51905f52602052604090205460ff16611d59575b7f5634a90413b79beba6c5f37aa8f19d1aee84a5320ff20ac7bd1ac63280867d5c5f80a3005b6001600160a01b0381165f9081525f5160206159f05f395f51905f5260205260408120805460ff19169055339082905f516020615ab05f395f51905f52907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9080a4611d33565b3461036a57602036600319011261036a576001600160a01b03611de1612798565b165f526099602052602060405f2054604051908152f35b3461036a575f36600319011261036a57602061019e54604051908152f35b3461036a575f36600319011261036a57602061019d54604051908152f35b3461036a57602036600319011261036a57600435611e5061350c565b80156109ae5780610198557f8fc06d8c0744b7cc9ac27129e8de216bee71eeb601b4bc040ec32669b60bffc85f80a2005b3461036a57602036600319011261036a57602061077c611ecf6108d0611ea5612798565b611ead612e94565b9060018060a01b03165f526101a18552611eca60405f2054613050565b613265565b610194549061296a565b3461036a575f36600319011261036a5761019a546040516001600160a01b039091168152602090f35b3461036a575f36600319011261036a57602060ff61013054166040519015158152f35b3461036a57604036600319011261036a57611f3e612798565b60243590611f4a61366a565b611f526136c2565b611f5a61355e565b620f42408210612038576001600160a01b031680156109ae57611f8482611f7f612988565b6129a8565b610195541061202957611f95613707565b611fd1611fae6108d06108c36108be6101945487612957565b825f526101a160205260405f20611fc68282546129a8565b905561019f546129a8565b61019f55805f526101a26020524260405f2055805f5f516020615a905f395f51905f526020604051868152a37f30385c845b448a36257a6a1716e6ad2e1bc2cbe333cde1e69fe849ad6511adfe5f80a3600161016255005b63c30436e960e01b5f5260045ffd5b63225a295160e21b5f5260045ffd5b3461036a575f36600319011261036a5761205f61350c565b6101305460ff8116156120a05760ff1916610130557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a1005b60405162461bcd60e51b815260206004820152601460248201527314185d5cd8589b194e881b9bdd081c185d5cd95960621b6044820152606490fd5b3461036a57604036600319011261036a57610a1e6120f8612798565b335f52603460205260405f2060018060a01b0382165f5260205261212360405f2060243590546129a8565b9033612d90565b3461036a57604036600319011261036a576121436127ae565b5060405162461bcd60e51b815260206004820152601860248201527f526f6c65732063616e27742062652072656e6f756e63656400000000000000006044820152606490fd5b3461036a575f36600319011261036a57602061077c613b70565b3461036a575f36600319011261036a57602061019554604051908152f35b3461036a575f36600319011261036a57602060ff6101975416604051908152f35b3461036a575f36600319011261036a57602061019654604051908152f35b3461036a57604036600319011261036a5760043561221c6127ae565b906122366104e8825f5260fe602052600160405f20015490565b805f5260fe60205260405f2060018060a01b0383165f5260205260ff60405f2054161561225f57005b5f81815260fe602090815260408083206001600160a01b0395909516808452949091528120805460ff19166001179055339291905f5160206159d05f395f51905f529080a4005b3461036a57602036600319011261036a57602061077c6004355f5260fe602052600160405f20015490565b3461036a57606036600319011261036a576122ea612798565b6122f26127ae565b6001600160a01b0382165f8181526034602090815260408083203384529091529020546044359391929160018201612433575b505061232f6136c2565b81158015612422575b6109ae57612344613707565b61235a6108d06108c36108be6101945487612957565b92825f526101a260205261237260405f205442612a5c565b61019654116123ed57825f526101a160205260405f20549084821061096a575f516020615a905f395f51905f52926123ac86602094612a5c565b855f526101a1845260405f205560018060a01b031694855f526101a183526123d960405f209182546129a8565b9055604051908152a3602060405160018152f35b42837f79ac2681a86c290166a70a3ceb9184346b4dc726de698af4f964a0fe1751f6fb5f80a363ad88028160e01b5f5260045ffd5b506001600160a01b03811615612338565b84821061244f57846124489203903390612d90565b8380612325565b60405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b3461036a575f36600319011261036a5760206101a05460801b604051906001600160801b0319168152f35b3461036a57602036600319011261036a576004356124db61350c565b60ff61019a5460a01c166006811015610356576001036103475780158015612539575b6112b85761019c548082146103175781907f563865324a2fcb87f9aa83da48c42017705ef1c6d1969604120a693538fa28b95f80a361019c55005b50610bb881116124fe565b3461036a575f36600319011261036a57602061077c612988565b3461036a57602036600319011261036a57612577612a81565b60018060a01b0361019a541690602061019854602460405180958193631cd80bed60e21b835260048301525afa918215612622575f926125ee575b50801580156125e6575b6125d7576020916125d261077c92600435612957565b61296a565b63375e76af60e21b5f5260045ffd5b5081156125bc565b9091506020813d60201161261a575b8161260a6020938361280f565b8101031261036a575190826125b2565b3d91506125fd565b6040513d5f823e3d90fd5b3461036a57604036600319011261036a57610a1e612649612798565b6024359033612d90565b3461036a575f36600319011261036a576040515f603654612673816127d7565b8084529060018116908115610d39575060011461269a57610abf83610cda8185038261280f565b60365f9081525f516020615a105f395f51905f52939250905b8082106126cb57509091508101602001610cda610cca565b9192600181602092548385880101520191019092916126b3565b3461036a57602036600319011261036a576004359063ffffffff60e01b821680920361036a57602091630cefc5c160e31b8114908115612727575b5015158152f35b637965db0b60e01b811491508115612741575b5083612720565b6301ffc9a760e01b1490508361273a565b5f5b8381106127635750505f910152565b8181015183820152602001612754565b9060209161278c81518092818552858086019101612752565b601f01601f1916010190565b600435906001600160a01b038216820361036a57565b602435906001600160a01b038216820361036a57565b9190602083019260068210156103565752565b90600182811c92168015612805575b60208310146127f157565b634e487b7160e01b5f52602260045260245ffd5b91607f16916127e6565b90601f8019910116810190811067ffffffffffffffff8211176114c057604052565b604051905f8260675491612844836127d7565b80835292600181169081156128b6575060011461286a575b6128689250038361280f565b565b5060675f90815290915f516020615a305f395f51905f525b81831061289a5750509060206128689282010161285c565b6020919350806001915483858901015201910190918492612882565b6020925061286894915060ff191682840152151560051b82010161285c565b604051905f82606854916128e8836127d7565b80835292600181169081156128b6575060011461290b576128689250038361280f565b5060685f90815290915f516020615a705f395f51905f525b81831061293b5750509060206128689282010161285c565b6020919350806001915483858901015201910190918492612923565b818102929181159184041417156112a457565b8115612974570490565b634e487b7160e01b5f52601260045260245ffd5b6129a5611ecf6108d0612999612e94565b611eca61019f54613050565b90565b919082018092116112a457565b356001600160a01b038116810361036a5790565b3560ff8116810361036a5790565b903590601e198136030182121561036a570180359067ffffffffffffffff821161036a5760200191813603831361036a57565b67ffffffffffffffff81116114c057601f01601f191660200190565b929192612a3282612a0a565b91612a40604051938461280f565b82948184528183011161036a578281602093845f960137010152565b919082039182116112a457565b9081602091031261036a5751801515810361036a5790565b61019a5461019854604051631cd80bed60e21b8152600481018290526001600160a01b0390921691602081602481865afa908115612622575f91612c91575b5060405163430cc02960e11b81526004810183905292602084602481845afa938415612622575f94612c5c575b50600490612af9612988565b610199546040516318160ddd60e01b81529195919360209185919082906001600160a01b03165afa928315612622575f93612c28575b5060ff6101a05460801c16612bf657602090602460405180948193631367580960e21b835260048301525afa908115612622575f91612bc7575b50612bbf57606483108015612bb5575b612baa57612ba4612b906129a5956125d294612957565b612b9e61019454809261296a565b92612957565b90612a5c565b505050506101945490565b5060648410612b79565b505050505f90565b612be9915060203d602011612bef575b612be1818361280f565b810190612a69565b5f612b69565b503d612bd7565b50506064612c0784839694966129a8565b10612baa57612c1c612c22926129a595612957565b926129a8565b9061296a565b9092506020813d602011612c54575b81612c446020938361280f565b8101031261036a5751915f612b2f565b3d9150612c37565b9093506020813d602011612c89575b81612c786020938361280f565b8101031261036a5751926004612aed565b3d9150612c6b565b90506020813d602011612cbb575b81612cac6020938361280f565b8101031261036a57515f612ac0565b3d9150612c9f565b612cd96101a05460801b611eca61019f54613050565b612ce1613707565b612ced61019f54613050565b612cfe6101a05460801b8092613265565b905f612d0a84846139fc565b810b1215612d8b57612d5c612d396108d0612d32612d6596611ecf96600160ff1b1890613bd7565b938461372c565b60018060a01b0361019a54165f526101a160205260405f20611fc68282546129a8565b61019f556134a2565b7f860c0aa5520013080c2f65981705fcdea474d9f7c3daf954656ed5e65d692d1f5f80a2565b505050565b6001600160a01b0316908115612e43576001600160a01b0316918215612df35760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591835f526034825260405f20855f5282528060405f2055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b612ea161019b5442612a5c565b60648110612fdb576101a05460801b8060ff61019a5460a01c1660068110156103565760018103612f24575050612f14612f1f612f1a6129a594612f14612efb612eed61019c54613050565b612ef5612fe6565b9061372c565b600160ff1b90612f0d90612ef5613042565b1891613050565b90613265565b6130bd565b6140e4565b60038103612f4d575050612f14612f1f612f1a6129a594612f14612efb612eed61019e54613050565b90915060028103612fd1575061019f549160648310612fcb57612f76612f809161019d54612957565b6101945490612957565b620151808302928084046201518014901517156112a457612fac6108be612fb394600160ff1b9361296a565b1890613bd7565b5f612fbe81836139fc565b810b12156129a557505f90565b50905090565b9091506103475790565b506101a05460801b90565b612710612ff281613ab1565b90607082101561302e57816070031b5b6001600160701b0316613fff90910160701b6001600160801b03161760801b6001600160801b03191690565b607082111561300257606f1982011c613002565b6301e13380612ff281613ab1565b8061305a57505f90565b8061306482613ab1565b9160708310156130a05750816070031b6001600160701b0316613fff90910160701b6001600160801b03161760801b6001600160801b03191690565b607083116130af575b50613002565b606f1983011c90505f6130a9565b5f617fff8260f01c16617fff81145f146130e15750156129a5575061ffff60ef1b90565b608083901c6001600160701b031691508061325957506001905b6e0171547652b82fe1777d0ffda0d23a0290811561322b57613fff01600160e11b821061320b5760e15b8181019061407082101561317257505050505f905f905b6001600160801b03199260701b906001607f1b906f1fffb8aa3b295c17f0bbbe87fed0691d60811b1860801c16171760801b1690565b6140e08210156131b157505061407081101561319557614070031c5b5f9161313c565b61407081116131a5575b5061318e565b61406f19011b5f61319f565b91509161c0dd83115f146131cd57505050617fff905f9061313c565b6001600160701b039160708111156131f257606f19011c5b16906140de19019161313c565b60708110613201575b506131e5565b6070031b5f6131fb565b600160e01b821061321d5760e0613125565b61322682613ab1565b613125565b50506f1fffb8aa3b295c17f0bbbe87fed0691d60811b18600160ff1b161561325557600160ff1b90565b5f90565b90600160701b176130fb565b90617fff8260f01c16617fff8260f01c1690617fff81145f146132f85750617fff036132d1576001600160801b0319828116908216036132a957600160ff1b161890565b908082186001600160801b031916600160ff1b036132c5571790565b5061ffff60ef1b919050565b600160801b600160ff1b0381166132ee575061ffff60ef1b919050565b600160ff1b161890565b617fff8203613323575090919050600160801b600160ff1b0381166132ee575061ffff60ef1b919050565b6001600160701b038460801c169080155f1461349357506001915b6001600160701b038460801c169080155f1461348457506001915b0291821561346d5701600160e11b821061344d5760e15b818101906140708210156133aa5750505050905f915f915b6001600160801b03199360701b916001607f1b911860801c16171760801b1690565b6140e08210156133eb5750506140708110156133ce57614070031c905b5f92613388565b61407081116133df575b50906133c7565b61406f19011b5f6133d8565b9394939092915061c0dd83111561340a57505050617fff915f91613388565b929391926001600160701b039190607081111561343457606f19011c5b16916140de190192613388565b60708110613443575b50613427565b6070031b5f61343d565b600160e01b821061345f5760e0613370565b61346882613ab1565b613370565b50505018600160ff1b161561325557600160ff1b90565b91600160701b90911790613359565b91600160701b9091179061333e565b617fff8160801c9160f01c1690613fff8210613506576001607f1b81101561036a576140fe821161036a576001600160701b0316600160701b179061406f8110156134ef5761406f031c90565b61406f81116134fd575b5090565b61406e19011b90565b50505f90565b335f9081527f32796e36004994222362c2f9423d5e208bb848170964890784a8d59ed40f50af602052604090205460ff161561354457565b611c2c6135503361573d565b611c0c6011611b8a5f615823565b335f9081525f5160206159f05f395f51905f52602052604090205460ff161561358357565b611c2c61358f3361573d565b611c0c6011611b8a5f516020615ab05f395f51905f52615823565b5f81815260fe6020908152604080832033845290915290205460ff16156135ce5750565b611c2c90611c0c6011611b8a6135e33361573d565b93615823565b6001600160a01b0381165f9081525f5160206159f05f395f51905f52602052604090205460ff16156136185750565b6001600160a01b03165f8181525f5160206159f05f395f51905f5260205260408120805460ff191660011790553391905f516020615ab05f395f51905f52905f5160206159d05f395f51905f529080a4565b6002610162541461367d57600261016255565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b60ff61013054166136cf57565b60405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606490fd5b61370f612e94565b60801c6001600160801b03196101a05416176101a0554261019b55565b90617fff8260f01c16617fff8260f01c16617fff82145f1461375f57617fff141590506132ee575061ffff60ef1b919050565b617fff81036137995750506dffffffffffffffffffffffffffff60801b81161561378f575061ffff60ef1b919050565b18600160ff1b1690565b600160801b600160ff1b0383166137db575050600160801b600160ff1b0382166137c9575061ffff60ef1b919050565b617fff60f01b9118600160ff1b161790565b6001600160701b038360801c169080155f1461399057506001905b6001600160701b038560801c1683155f1461397d5780613955575b9061381b9161296a565b91821561346d576001606c1b831061394157600160731b831061390e5761384183613ab1565b81810190614071840182111561387857505050505018608090811c6001607f1b16617fff60701b17901b6001600160801b03191690565b83613ffc8301105f14613894575050505050905f915f91613388565b83613f8c8301105f146138dd575050613ffc8101828111156138bf575003613ffc011b905f92613388565b82116138ce575b5050906133c7565b9003613ffb19011c5f806138c6565b613f8d939250938460706001600160701b0393979611613903575b501693030192613388565b606f19011c5f6138f8565b600160721b83106139245760ff60725b16613841565b600160711b83106139385760ff607161391e565b60ff607061391e565b634e487b7160e01b5f52600160045260245ffd5b925061381b9061396484613ab1565b60e20393841b9260019460711991010192909150613811565b61381b9190600160701b1760721b61296a565b90600160701b176137f6565b156139a357565b60405162461bcd60e51b815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201526a6e697469616c697a696e6760a81b6064820152608490fd5b8060801c60016001607f1b03811691617fff60701b831161036a578360801c9060016001607f1b03821694617fff60701b861161036a576001600160801b0319918216911614801581613aa3575b1561036a5715613a5c57505050505f90565b6001607f1b908111159110613a875715613a80571115613a7b575f1990565b600190565b50505f1990565b15613a93575050600190565b1115613a9e57600190565b5f1990565b50617fff60701b8410613a4a565b801561036a575f90600160801b811015613b65575b80600160401b6002921015613b59575b640100000000811015613b4d575b62010000811015613b41575b610100811015613b35575b6010811015613b29575b6004811015613b1e575b1015613b185790565b60010190565b91810191811c613b0f565b6004928301921c613b05565b6008928301921c613afb565b6010928301921c613af0565b6020928301921c613ae4565b6040928301921c613ad6565b60809150811c613ac6565b613b786158c5565b613b80615910565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a08152613bd160c08261280f565b51902090565b908160801c617fff8360f01c16908260801c617fff8460f01c1692617fff81145f14613c2857505050617fff036134f9576001600160801b0319828116911603613c1e5790565b5061ffff60ef1b90565b909192617fff81969596145f14613c4157505050505090565b6001607f1b841015936001600160701b03169180613f5657506001925b6001600160701b038116916001607f1b909110159080613f48575060015b83613ca55750505050506001600160801b03198316600160ff1b141591506129a5905057505f90565b8293949596979192155f14613cd85750505050506001600160801b03198316600160ff1b141591506129a5905057505f90565b80860392878103613dd357506070831315613cf7575050505050505090565b90919293949596505f83135f14613d985750501c905b0190600160711b821015613d8c575b617fff8103613d4157505015613d38576001600160f01b031990565b617fff60f01b90565b600160701b821015613d7b57505f915b15613d75576001607f1b5b6001600160801b0380199360701b9116171760801b1690565b5f613d5c565b91906001600160701b031690613d51565b600191821c9101613d1c565b9095949391606f19811215613db05750505050505090565b909192939495505f8112613dc6575b5050613d0d565b9093505f031c5f80613dbf565b95949280949798505f919250135f14613f28575060011b915f1901935b6070821315613ed25750506001915b828210613ec95750035b8015613ec257613e1881613ab1565b60718103613e7257506001918201911c6001600160701b03165b617fff8203613e4e57505015613d38576001600160f01b031990565b909115613d755760709190911b176001607f1b1760801b6001600160801b03191690565b91906070831015613eb157826070038083115f14613ea35791909201606f190191901b6001600160701b0316613e32565b5091505f19011b5f90613e32565b6001600160701b0391925016613e32565b5050505f90565b93509003613e09565b6001821315613eec575f1990810191011c60010191613dff565b9290606f19811215613f02575060019150613dff565b5f198112613f11575b50613dff565b600191925f1901905f19905f03011c01905f613f0b565b5f839692949612613f3a575b50613df0565b5f1901945060011b5f613f34565b600160701b90921791613c7c565b92600160701b90921791613c5e565b7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411613fd5576020935f9360ff60809460405194855216868401526040830152606082015282805260015afa15612622575f516001600160a01b03811615613fcd57905f90565b505f90600190565b505050505f90600390565b60058110156103565780613ff15750565b6001810361403e5760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e617475726500000000000000006044820152606490fd5b6002810361408b5760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606490fd5b60031461409457565b60405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608490fd5b8060801c6001600160701b03617fff6001607f1b83119360f01c16911690617fff81148061570f575b1561411f575061ffff60ef1b92915050565b61400d81111561413457505015613d38575f90565b613f7f81101561414b5750613fff60f01b92915050565b80615701575060015b613fef8111156156e557613fee19011b905b80806156d7575b61350657801580806156bc575b6156af576001600160801b038360801c931691806156a6575b615698575b6001607f1b91808316615680575b6001607e1b8116615663575b6001607d1b8116615646575b6001607c1b8116615629575b6001607b1b811661560c575b6001607a1b81166155ef575b600160791b81166155d2575b600160781b81166155b5575b600160771b8116615598575b600160761b811661557b575b600160751b811661555e575b600160741b8116615541575b600160731b8116615524575b600160721b8116615507575b600160711b81166154ea575b600160701b81166154cd575b6001606f1b81166154b0575b6001606e1b8116615493575b6001606d1b8116615476575b6001606c1b8116615459575b6001606b1b811661543c575b6001606a1b811661541f575b600160691b8116615402575b600160681b81166153e5575b600160671b81166153c8575b600160661b81166153ab575b600160651b811661538e575b600160641b8116615371575b600160631b8116615354575b600160621b8116615337575b600160611b811661531a575b600160601b81166152fd575b6001605f1b81166152e0575b6001605e1b81166152c3575b6001605d1b81166152a6575b6001605c1b8116615289575b6001605b1b811661526c575b6001605a1b811661524f575b600160591b8116615232575b600160581b8116615215575b600160571b81166151f8575b600160561b81166151db575b600160551b81166151be575b600160541b81166151a1575b600160531b8116615184575b600160521b8116615167575b600160511b811661514a575b600160501b811661512d575b69800000000000000000008116615110575b694000000000000000000081166150f3575b692000000000000000000081166150d6575b691000000000000000000081166150b9575b6908000000000000000000811661509c575b6904000000000000000000811661507f575b69020000000000000000008116615062575b69010000000000000000008116615045575b688000000000000000008116615028575b68400000000000000000811661500b575b682000000000000000008116614fee575b681000000000000000008116614fd1575b680800000000000000008116614fb4575b680400000000000000008116614f97575b680200000000000000008116614f7a575b600160401b8116614f5d575b6780000000000000008116614f40575b6740000000000000008116614f23575b6720000000000000008116614f06575b6710000000000000008116614ee9575b6708000000000000008116614ecc575b6704000000000000008116614eaf575b6702000000000000008116614e92575b6701000000000000008116614e75575b66800000000000008116614e58575b66400000000000008116614e3b575b66200000000000008116614e1e575b66100000000000008116614e01575b66080000000000008116614de4575b66040000000000008116614dc7575b66020000000000008116614daa575b66010000000000008116614d8d575b658000000000008116614d70575b654000000000008116614d53575b652000000000008116614d36575b651000000000008116614d19575b650800000000008116614cfc575b650400000000008116614cdf575b650200000000008116614cc2575b650100000000008116614ca5575b6480000000008116614c88575b6440000000008116614c6b575b6420000000008116614c4e575b6410000000008116614c31575b6408000000008116614c14575b6404000000008116614bf7575b6402000000008116614bda575b6401000000008116614bbd575b63800000008116614ba0575b63400000008116614b83575b63200000008116614b66575b63100000008116614b49575b63080000008116614b2c575b63040000008116614b0f575b63020000008116614af2575b63010000008116614ad5575b628000008116614ab8575b624000008116614a9b575b622000008116614a7e575b621000008116614a61575b620800008116614a44575b620400008116614a27575b620200008116614a0a575b6201000081166149ed575b61800081166149d0575b61400081166149b3575b6120008116614996575b6110008116614979575b610800811661495c575b610400811661493f575b6102008116614922575b6101008116614905575b608081166148e8575b604081166148cb575b602081166148ae575b60108116614891575b60088116614873575b600416614856575b15614828576001600160701b03613fff91600f1c169101905b6001600160801b03199160701b1760801b1690565b613ffe8211614849576001600160701b0390600f1c1690613fff0390614813565b5f91613fee19011c614813565b907001000000000000000000000000000000010260801c906147fa565b70010000000000000000000000000000000490920260801c916147f2565b9170010000000000000000000000000000000a0260801c916147e9565b917001000000000000000000000000000000150260801c916147e0565b9170010000000000000000000000000000002b0260801c916147d7565b917001000000000000000000000000000000570260801c916147ce565b917001000000000000000000000000000000b00260801c916147c5565b917001000000000000000000000000000001610260801c916147bb565b917001000000000000000000000000000002c40260801c916147b1565b9170010000000000000000000000000000058a0260801c916147a7565b91700100000000000000000000000000000b160260801c9161479d565b9170010000000000000000000000000000162d0260801c91614793565b91700100000000000000000000000000002c5b0260801c91614789565b917001000000000000000000000000000058b80260801c9161477f565b9170010000000000000000000000000000b1710260801c91614775565b917001000000000000000000000000000162e30260801c9161476a565b9170010000000000000000000000000002c5c70260801c9161475f565b91700100000000000000000000000000058b8f0260801c91614754565b917001000000000000000000000000000b17200260801c91614749565b91700100000000000000000000000000162e410260801c9161473e565b917001000000000000000000000000002c5c840260801c91614733565b9170010000000000000000000000000058b90a0260801c91614728565b91700100000000000000000000000000b172160260801c9161471d565b9170010000000000000000000000000162e42e0260801c91614711565b91700100000000000000000000000002c5c85e0260801c91614705565b917001000000000000000000000000058b90be0260801c916146f9565b9170010000000000000000000000000b17217e0260801c916146ed565b917001000000000000000000000000162e42fd0260801c916146e1565b9170010000000000000000000000002c5c85fc0260801c916146d5565b91700100000000000000000000000058b90bfa0260801c916146c9565b917001000000000000000000000000b17217f60260801c916146bd565b91700100000000000000000000000162e42fee0260801c916146b0565b917001000000000000000000000002c5c85fde0260801c916146a3565b9170010000000000000000000000058b90bfbd0260801c91614696565b91700100000000000000000000000b17217f7c0260801c91614689565b9170010000000000000000000000162e42fef90260801c9161467c565b91700100000000000000000000002c5c85fdf30260801c9161466f565b917001000000000000000000000058b90bfbe70260801c91614662565b9170010000000000000000000000b17217f7d00260801c91614655565b917001000000000000000000000162e42fefa20260801c91614647565b9170010000000000000000000002c5c85fdf460260801c91614639565b91700100000000000000000000058b90bfbe8d0260801c9161462b565b917001000000000000000000000b17217f7d1b0260801c9161461d565b91700100000000000000000000162e42fefa380260801c9161460f565b917001000000000000000000002c5c85fdf4720260801c91614601565b9170010000000000000000000058b90bfbe8e60260801c916145f3565b91700100000000000000000000b17217f7d1ce0260801c916145e5565b9170010000000000000000000162e42fefa39d0260801c916145d6565b91700100000000000000000002c5c85fdf473c0260801c916145c7565b917001000000000000000000058b90bfbe8e7a0260801c916145b8565b9170010000000000000000000b17217f7d1cf60260801c916145a9565b917001000000000000000000162e42fefa39ee0260801c9161459a565b9170010000000000000000002c5c85fdf473dd0260801c9161458b565b91700100000000000000000058b90bfbe8e7bb0260801c9161457c565b917001000000000000000000b17217f7d1cf780260801c9161456d565b91700100000000000000000162e42fefa39ef20260801c9161455d565b917001000000000000000002c5c85fdf473de50260801c9161454d565b9170010000000000000000058b90bfbe8e7bcc0260801c9161453d565b91700100000000000000000b17217f7d1cf7990260801c9161452d565b9170010000000000000000162e42fefa39ef340260801c9161451d565b91700100000000000000002c5c85fdf473de6a0260801c9161450d565b917001000000000000000058b90bfbe8e7bcd50260801c916144fd565b9170010000000000000000b17217f7d1cf79ab0260801c916144ed565b917001000000000000000162e42fefa39ef3580260801c916144e1565b9170010000000000000002c5c85fdf473de6b20260801c916144d0565b91700100000000000000058b90bfbe8e7bcd6d0260801c916144bf565b917001000000000000000b17217f7d1cf79afa0260801c916144ae565b91700100000000000000162e42fefa39ef366f0260801c9161449d565b917001000000000000002c5c85fdf473de6eca0260801c9161448c565b9170010000000000000058b90bfbe8e7bce5440260801c9161447b565b91700100000000000000b17217f7d1cf79e9490260801c9161446a565b9170010000000000000162e42fefa39ef44d910260801c91614458565b91700100000000000002c5c85fdf473dea871f0260801c91614446565b917001000000000000058b90bfbe8e7bdcbe2e0260801c91614434565b9170010000000000000b17217f7d1cf7d83c1a0260801c91614422565b917001000000000000162e42fefa39f02b772c0260801c91614410565b9170010000000000002c5c85fdf473e242ea380260801c916143fe565b91700100000000000058b90bfbe8e7cc35c3f00260801c916143ec565b917001000000000000b17217f7d1cfb72b45e10260801c916143da565b91700100000000000162e42fefa39fe95583c20260801c916143ce565b917001000000000002c5c85fdf4741bea6e77e0260801c916143c2565b9170010000000000058b90bfbe8e8b2d3d4ede0260801c916143b6565b91700100000000000b17217f7d1d351a389d400260801c916143aa565b9170010000000000162e42fefa3ae53369388c0260801c9161439e565b91700100000000002c5c85fdf477b662b269450260801c91614392565b917001000000000058b90bfbe8f71cb4e4b33d0260801c91614386565b9170010000000000b17217f7d20cf927c8e94c0260801c9161437a565b917001000000000162e42fefa494f1478fde050260801c9161436e565b9170010000000002c5c85fdf4b15de6f17eb0d0260801c91614362565b91700100000000058b90bfbe9ddbac5e109cce0260801c91614356565b917001000000000b17217f7d5a7716bba4a9ae0260801c9161434a565b91700100000000162e42fefb2fed257559bdaa0260801c9161433e565b917001000000002c5c85fdf84bd62ae30a74cc0260801c91614332565b9170010000000058b90bfbf8479bd5a81b51ad0260801c91614326565b91700100000000b17217f80f4ef5aadda455540260801c9161431a565b9170010000000162e42ff0999ce3541b9fffcf0260801c9161430e565b91700100000002c5c85fe31f35a6a30da1be500260801c91614302565b917001000000058b90bfcdee5acd3c1cedc8230260801c916142f6565b9170010000000b17217fba9c739aa5819f44f90260801c916142ea565b917001000000162e42fff037df38aa2b219f060260801c916142de565b9170010000002c5c8601cc6b9e94213c72737a0260801c916142d2565b91700100000058b90c0b48c6be5df846c5b2ef0260801c916142c6565b917001000000b1721835514b86e6d96efd1bfe0260801c916142ba565b91700100000162e430e5a18f6119e3c02282a50260801c916142ae565b917001000002c5c863b73f016468f6bac5ca2b0260801c916142a2565b9170010000058b90cf1e6d97f9ca14dbcc16280260801c91614296565b91700100000b1721bcfc99d9f890ea069117630260801c9161428a565b9170010000162e43f4f831060e02d839a9d16d0260801c9161427e565b91700100002c5c89d5ec6ca4d7c8acc017b7c90260801c91614272565b917001000058b91b5bc9ae2eed81e9b7d4cfab0260801c91614266565b9170010000b17255775c040618bf4a4ade83fc0260801c9161425a565b917001000162e525ee054754457d59952920260260801c9161424e565b9170010002c5cc37da9491d0985c348c68e7b30260801c91614242565b91700100058ba01fb9f96d6cacd4b180917c3d0260801c91614236565b917001000b175effdc76ba38e31671ca9397250260801c9161422a565b91700100162f3904051fa128bca9c55c31e5df0260801c9161421e565b917001002c605e2e8cec506d21bfc89a23a00f0260801c91614212565b9170010058c86da1c09ea1ff19d294cf2f679b0260801c91614206565b91700100b1afa5abcbed6129ab13ec11dc95430260801c916141fa565b9170010163da9fb33356d84a66ae336dcdfa3f0260801c916141ee565b91700102c9a3e778060ee6f7caca4f7a29bde80260801c916141e2565b917001059b0d31585743ae7c548eb68ca417fd0260801c916141d6565b9170010b5586cf9890f6298b92b71842a983630260801c916141ca565b917001172b83c7d517adcdf7c8c50eb14a791f0260801c916141be565b917001306fe0a31b7152de8d5a46305c85edec0260801c916141b2565b6fb504f333f9de6484597d89b3754abe9f92506141a6565b919060019019910191614198565b50811515614193565b50617fff60f01b92915050565b50713fffffffffffffffffffffffffffffffffff831161417a565b5061203760811b821161416d565b613fef81106156f6575b5090614166565b613fef031c5f6156ef565b600160701b90911790614154565b5081151561410d565b908151811015615729570160200190565b634e487b7160e01b5f52603260045260245ffd5b615747602a612a0a565b90615755604051928361280f565b602a8252615763602a612a0a565b6020830190601f19013682378251156157295760309053815160011015615729576078602183015360295b600181116157e2575061579e5790565b606460405162461bcd60e51b815260206004820152602060248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152fd5b90600f81166010811015615729576f181899199a1a9b1b9c1cb0b131b232b360811b901a6158108385615718565b5360041c9080156112a4575f190161578e565b61582d6042612a0a565b9061583b604051928361280f565b604282526158496042612a0a565b6020830190601f19013682378251156157295760309053815160011015615729576078602183015360415b60018111615884575061579e5790565b90600f81166010811015615729576f181899199a1a9b1b9c1cb0b131b232b360811b901a6158b28385615718565b5360041c9080156112a4575f1901615874565b6158cd612831565b80519081156158dd576020012090565b505060655480156158eb5790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b6159186128d5565b8051908115615928576020012090565b505060665480156158eb5790565b91929015615998575081511561594a575090565b3b156159535790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b8251909150156159ab5750805190602001fd5b60405162461bcd60e51b815260206004820152908190611c2c90602483019061277356fe2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0dda24b4380d7c1044352b34a0472755b2b85c5ec87ad6f44aa3cd7277d1fab7f64a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b89787eeb91fe3101235e4a76063c7023ecb40f923f97916639c598592fa30d6ae65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862aa2153420d844928b4421650203c77babc8b33d7f2e7b450e2966db0c22097753ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efe1dcbdb91df27212a29bc27177c840cf2f819ecf2187432e1fac86c2dd5dfca942a7b7dd785cd69714a189dffb3fd7d7174edc9ece837694ce50f7078f7c31aea2646970667358221220b89ca9042a1211c99dfe2477a82a102311dfc75fc5032a9a5baf639f99750a8664736f6c634300081c0033

Block Transaction Gas Used Reward
view all blocks produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits

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.