ETH Price: $2,324.34 (+1.17%)

Transaction Decoder

Block:
23948886 at Dec-05-2025 07:43:35 PM +UTC
Transaction Fee:
0.000001456369773672 ETH $0.003385
Gas Used:
62,436 Gas / 0.023325802 Gwei

Emitted Events:

435 PriceAndFeeCalculator.UnitPriceUpdated( vault=0x8acf95a9d58ca51ed5f2825324419c92c440342b, price=975997, timestamp=1764963642 )
436 0x76ea144cab1be0258f87716ca181ff07103b39e2.0x9ed366aa782e144d670203560e671c3c1d366cbb36b29f2f881aab50c658c127( 0x9ed366aa782e144d670203560e671c3c1d366cbb36b29f2f881aab50c658c127, 0x0000000000000000000000000ffcaad7794acc19ccd1dd133dae9d500b00cd7f, 0000000000000000000000000000000000000000000000000000000000000020, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000000000020, 0000000000000000000000008f3ffa11cd5915f0e869192663b905504a2ef4a5, 0000000000000000000000000000000000000000000000000000000000000040, 0000000000000000000000000000000000000000000000000000000000000064, d22c09300000000000000000000000008acf95a9d58ca51ed5f2825324419c92, c440342b00000000000000000000000000000000000000000000000000000000, 000ee47d00000000000000000000000000000000000000000000000000000000, 6933353a00000000000000000000000000000000000000000000000000000000 )

Account State Difference:

  Address   Before After State Difference Code
0x0fFcaad7...00B00CD7F
0.096178027051123741 Eth
Nonce: 7083
0.096176570681350069 Eth
Nonce: 7084
0.000001456369773672
(Titan Builder)
8.174288082136926042 Eth8.174288082138424506 Eth0.000000000001498464
0x8F3FfA11...04A2Ef4a5

Execution Trace

0x76ea144cab1be0258f87716ca181ff07103b39e2.baae8abf( )
  • PriceAndFeeCalculator.setUnitPrice( vault=0x8acF95A9d58Ca51ED5f2825324419c92c440342b, price=975997, timestamp=1764963642 )
    • 0x8acf95a9d58ca51ed5f2825324419c92c440342b.STATICCALL( )
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      import { IERC20 } from "@oz/token/ERC20/IERC20.sol";
      import { Math } from "@oz/utils/math/Math.sol";
      import { SafeCast } from "@oz/utils/math/SafeCast.sol";
      import { Auth, Authority } from "@solmate/auth/Auth.sol";
      import { BaseFeeCalculator } from "src/core/BaseFeeCalculator.sol";
      import { ONE_DAY, ONE_IN_BPS, ONE_MINUTE, UNIT_PRICE_PRECISION } from "src/core/Constants.sol";
      import { IERC20 } from "@oz/token/ERC20/IERC20.sol";
      import { HasNumeraire } from "src/core/HasNumeraire.sol";
      import { VaultAccruals, VaultPriceState } from "src/core/Types.sol";
      import { IPriceAndFeeCalculator } from "src/core/interfaces/IPriceAndFeeCalculator.sol";
      import { IOracleRegistry } from "src/periphery/interfaces/IOracleRegistry.sol";
      /// @title PriceAndFeeCalculator
      /// @notice Calculates and manages unit price and fees for multiple vaults that share the same numeraire token. Acts as
      /// a price oracle and fee accrual engine. Vault registration workflow is:
      /// 1. Register a new vault with registerVault()
      /// 2. Set the thresholds for the vault with setThresholds()
      /// 3. Set the initial price state with setInitialPrice()
      /// Once registered, a vault can have its price updated by an authorized entity. Vault owners set thresholds for price
      /// changes, update intervals, and price age. If a price update violates thresholds (too large change, too soon, or too
      /// old), the vault is paused. Paused vaults dont accrue fees and cannot have their price updated until they are
      /// unpaused by the vault owner. Accrues fees on each price update, based on TVL and performance since last update
      /// Supports conversion between vault units, tokens, and numeraire for deposits/withdrawals. All logic and state is
      /// per-vault, supporting many vaults in parallel. Only vault owners can set thresholds pause/unpause their vaults,
      /// whereas accountants can also pause their vaults.
      /// Integrates with an external oracle registry for token price conversions
      contract PriceAndFeeCalculator is IPriceAndFeeCalculator, BaseFeeCalculator, HasNumeraire {
          using SafeCast for uint256;
          ////////////////////////////////////////////////////////////
          //                       Immutables                       //
          ////////////////////////////////////////////////////////////
          /// @notice Oracle registry contract for price feeds
          IOracleRegistry public immutable ORACLE_REGISTRY;
          ////////////////////////////////////////////////////////////
          //                        Storage                         //
          ////////////////////////////////////////////////////////////
          /// @notice Mapping of vault addresses to their state information
          mapping(address vault => VaultPriceState vaultPriceState) internal _vaultPriceStates;
          ////////////////////////////////////////////////////////////
          //                        Modifiers                        //
          ////////////////////////////////////////////////////////////
          modifier requiresVaultAuthOrAccountant(address vault) {
              // Requirements: check that the caller is either the vault's accountant or the vault's owner or has the
              // permission to call the function
              require(
                  msg.sender == vaultAccountant[vault] || msg.sender == Auth(vault).owner()
                      || Auth(vault).authority().canCall(msg.sender, address(this), msg.sig),
                  Aera__CallerIsNotAuthorized()
              );
              _;
          }
          constructor(IERC20 numeraire, IOracleRegistry oracleRegistry, address owner_, Authority authority_)
              BaseFeeCalculator(owner_, authority_)
              HasNumeraire(address(numeraire))
          {
              // Requirements: check that the numeraire token and oracle registry are not zero address
              require(address(oracleRegistry) != address(0), Aera__ZeroAddressOracleRegistry());
              // Effects: set the numeraire token and oracle registry
              ORACLE_REGISTRY = oracleRegistry;
          }
          ////////////////////////////////////////////////////////////
          //              Public / External Functions               //
          ////////////////////////////////////////////////////////////
          /// @notice Register a new vault with the fee calculator
          function registerVault() external override {
              VaultPriceState storage vaultPriceState = _vaultPriceStates[msg.sender];
              // Requirements: check that the vault is not already registered
              require(vaultPriceState.timestamp == 0, Aera__VaultAlreadyRegistered());
              // Effects: initialize the vault state
              // timestamp is set to indicate vault registration
              vaultPriceState.timestamp = uint32(block.timestamp);
              // Log that vault was registered
              emit VaultRegistered(msg.sender);
          }
          /// @inheritdoc IPriceAndFeeCalculator
          function setInitialPrice(address vault, uint128 price, uint32 timestamp) external requiresVaultAuth(vault) {
              require(price != 0, Aera__InvalidPrice());
              VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
              // Requirements: check that the tresholds are set (which implies the vault is registered) and the initial price
              // is not set
              require(vaultPriceState.maxPriceAge != 0, Aera__ThresholdNotSet());
              require(vaultPriceState.unitPrice == 0, Aera__VaultAlreadyInitialized());
              // Requirements: check that the provided timestamp is not too old and implicitly
              // check that timestamp <= block.timestamp
              require(block.timestamp - timestamp <= vaultPriceState.maxPriceAge, Aera__StalePrice());
              uint32 timestampU32 = uint32(block.timestamp);
              // Effects: set the initial price state
              vaultPriceState.unitPrice = price;
              vaultPriceState.highestPrice = price;
              vaultPriceState.timestamp = timestampU32;
              vaultPriceState.accrualLag = 0;
              vaultPriceState.lastTotalSupply = IERC20(vault).totalSupply().toUint128();
              // Log initial price state
              emit UnitPriceUpdated(vault, price, timestampU32);
          }
          /// @inheritdoc IPriceAndFeeCalculator
          function setThresholds(
              address vault,
              uint16 minPriceToleranceRatio,
              uint16 maxPriceToleranceRatio,
              uint16 minUpdateIntervalMinutes,
              uint8 maxPriceAge,
              uint8 maxUpdateDelayDays
          ) external requiresVaultAuth(vault) {
              // Requirements: check that the min price decrease ratio is <= 100%
              require(minPriceToleranceRatio <= ONE_IN_BPS, Aera__InvalidMinPriceToleranceRatio());
              // Requirements: check that the max price increase ratio is >= 100%
              require(maxPriceToleranceRatio >= ONE_IN_BPS, Aera__InvalidMaxPriceToleranceRatio());
              // Requirements: check that the max price age is greater than zero
              require(maxPriceAge > 0, Aera__InvalidMaxPriceAge());
              // Requirements: check that the max update delay is greater than zero
              require(maxUpdateDelayDays > 0, Aera__InvalidMaxUpdateDelayDays());
              VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
              // Requirements: check that the vault is registered
              require(vaultPriceState.timestamp != 0, Aera__VaultNotRegistered());
              // Effects: set the thresholds
              vaultPriceState.minPriceToleranceRatio = minPriceToleranceRatio;
              vaultPriceState.maxPriceToleranceRatio = maxPriceToleranceRatio;
              vaultPriceState.minUpdateIntervalMinutes = minUpdateIntervalMinutes;
              vaultPriceState.maxPriceAge = maxPriceAge;
              vaultPriceState.maxUpdateDelayDays = maxUpdateDelayDays;
              // Log that the thresholds were set
              emit ThresholdsSet(vault, minPriceToleranceRatio, maxPriceToleranceRatio, minUpdateIntervalMinutes, maxPriceAge);
          }
          /// @inheritdoc IPriceAndFeeCalculator
          function setUnitPrice(address vault, uint128 price, uint32 timestamp) external onlyVaultAccountant(vault) {
              VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
              // Requirements: validate the price update
              _validatePriceUpdate(vaultPriceState, price, timestamp);
              if (!vaultPriceState.paused) {
                  if (_shouldPause(vaultPriceState, price, timestamp)) {
                      // Effects + Log: pause the vault
                      _setVaultPaused(vaultPriceState, vault, true);
                      // Effects: write the accrual lag
                      unchecked {
                          // Cant overflow because _validatePriceUpdate requires timestamp > vaultPriceState.timestamp
                          vaultPriceState.accrualLag = uint24(timestamp - vaultPriceState.timestamp);
                      }
                  } else {
                      // Effects: accrue fees
                      _accrueFees(vault, price, timestamp);
                  }
              } else {
                  // Effects: set the accrual lag
                  unchecked {
                      // Cant overflow because _validatePriceUpdate requires timestamp > vaultPriceState.timestamp
                      vaultPriceState.accrualLag = uint24(timestamp - vaultPriceState.timestamp + vaultPriceState.accrualLag);
                  }
              }
              // Effects: set the unit price and last update timestamp
              vaultPriceState.unitPrice = price;
              vaultPriceState.timestamp = timestamp;
              // Log the unit price update
              emit UnitPriceUpdated(vault, price, timestamp);
          }
          /// @inheritdoc IPriceAndFeeCalculator
          function pauseVault(address vault) external requiresVaultAuthOrAccountant(vault) {
              VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
              // Requirements: check that the vault is not already paused
              require(!vaultPriceState.paused, Aera__VaultPaused());
              // Effects + Log: pause the vault
              _setVaultPaused(vaultPriceState, vault, true);
          }
          /// @inheritdoc IPriceAndFeeCalculator
          function unpauseVault(address vault, uint128 price, uint32 timestamp) external requiresVaultAuth(vault) {
              VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
              // Requirements: check that the vault is paused, and unitPrice and timestamp match what is expected
              require(vaultPriceState.paused, Aera__VaultNotPaused());
              require(vaultPriceState.unitPrice == price, Aera__UnitPriceMismatch());
              require(vaultPriceState.timestamp == timestamp, Aera__TimestampMismatch());
              // Effects: accrue fees
              _accrueFees(vault, price, timestamp);
              // Effects + Log: unpause the vault
              _setVaultPaused(vaultPriceState, vault, false);
          }
          /// @inheritdoc IPriceAndFeeCalculator
          function resetHighestPrice(address vault) external requiresVaultAuth(vault) {
              VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
              uint128 currentPrice = vaultPriceState.unitPrice;
              // Requirements: check that the vault is initialized
              require(currentPrice != 0, Aera__VaultNotInitialized());
              // Requirements: check that we're resetting from a higher mark to a lower one
              require(currentPrice < vaultPriceState.highestPrice, Aera__CurrentPriceAboveHighestPrice());
              // Effects: reset the highest price to the current unit price
              vaultPriceState.highestPrice = currentPrice;
              // Log the highest price reset
              emit HighestPriceReset(vault, currentPrice);
          }
          /// @inheritdoc IPriceAndFeeCalculator
          function convertUnitsToToken(address vault, IERC20 token, uint256 unitsAmount)
              external
              view
              returns (uint256 tokenAmount)
          {
              return _convertUnitsToToken(vault, token, unitsAmount, _vaultPriceStates[vault].unitPrice, Math.Rounding.Floor);
          }
          /// @inheritdoc IPriceAndFeeCalculator
          function convertUnitsToTokenIfActive(address vault, IERC20 token, uint256 unitsAmount, Math.Rounding rounding)
              external
              view
              returns (uint256 tokenAmount)
          {
              VaultPriceState storage vaultState = _vaultPriceStates[vault];
              // check that the vault is not paused
              require(!vaultState.paused, Aera__VaultPaused());
              return _convertUnitsToToken(vault, token, unitsAmount, vaultState.unitPrice, rounding);
          }
          /// @inheritdoc IPriceAndFeeCalculator
          function convertUnitsToNumeraire(address vault, uint256 unitsAmount) external view returns (uint256) {
              VaultPriceState storage vaultState = _vaultPriceStates[vault];
              // check that the vault is not paused
              require(!vaultState.paused, Aera__VaultPaused());
              return unitsAmount * vaultState.unitPrice / UNIT_PRICE_PRECISION;
          }
          /// @inheritdoc IPriceAndFeeCalculator
          function convertTokenToUnits(address vault, IERC20 token, uint256 tokenAmount)
              external
              view
              returns (uint256 unitsAmount)
          {
              return _convertTokenToUnits(vault, token, tokenAmount, _vaultPriceStates[vault].unitPrice, Math.Rounding.Floor);
          }
          /// @inheritdoc IPriceAndFeeCalculator
          function convertTokenToUnitsIfActive(address vault, IERC20 token, uint256 tokenAmount, Math.Rounding rounding)
              external
              view
              returns (uint256 unitsAmount)
          {
              VaultPriceState storage vaultState = _vaultPriceStates[vault];
              // check that the vault is not paused
              require(!vaultState.paused, Aera__VaultPaused());
              return _convertTokenToUnits(vault, token, tokenAmount, vaultState.unitPrice, rounding);
          }
          /// @inheritdoc IPriceAndFeeCalculator
          function getVaultState(address vault) external view returns (VaultPriceState memory, VaultAccruals memory) {
              return (_vaultPriceStates[vault], _vaultAccruals[vault]);
          }
          /// @inheritdoc IPriceAndFeeCalculator
          function getVaultsPriceAge(address vault) external view returns (uint256) {
              unchecked {
                  // Cant overflow because timestamp is required to be <= block.timestamp at the time of the update
                  return block.timestamp - _vaultPriceStates[vault].timestamp;
              }
          }
          /// @inheritdoc IPriceAndFeeCalculator
          function isVaultPaused(address vault) external view returns (bool) {
              return _vaultPriceStates[vault].paused;
          }
          /// @inheritdoc BaseFeeCalculator
          function previewFees(address vault, uint256 feeTokenBalance) external view override returns (uint256, uint256) {
              VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
              uint256 claimableProtocolFee = Math.min(feeTokenBalance, vaultAccruals.accruedProtocolFees);
              uint256 claimableVaultFee;
              unchecked {
                  claimableVaultFee = Math.min(feeTokenBalance - claimableProtocolFee, vaultAccruals.accruedFees);
              }
              return (claimableVaultFee, claimableProtocolFee);
          }
          ////////////////////////////////////////////////////////////
          //              Internal / Private Functions              //
          ////////////////////////////////////////////////////////////
          /// @notice Accrues fees for a vault
          /// @param vault The address of the vault
          /// @param price The price of a single vault unit
          /// @param timestamp The timestamp of the price update
          /// @dev It is assumed that validation has already been done
          /// Tvl is calculated as the product of the minimum of the current and last price and the minimum of the current and
          /// last total supply. This is to minimize potential issues with price spikes
          function _accrueFees(address vault, uint256 price, uint256 timestamp) internal {
              VaultPriceState storage vaultPriceState = _vaultPriceStates[vault];
              uint256 timeDelta;
              unchecked {
                  timeDelta = timestamp - vaultPriceState.timestamp + vaultPriceState.accrualLag;
              }
              // Interactions: get the current total supply
              uint256 currentTotalSupply = IERC20(vault).totalSupply();
              uint256 minTotalSupply = Math.min(currentTotalSupply, uint256(vaultPriceState.lastTotalSupply));
              uint256 minUnitPrice = Math.min(price, uint256(vaultPriceState.unitPrice));
              uint256 tvl = minUnitPrice * minTotalSupply / UNIT_PRICE_PRECISION;
              VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
              uint256 vaultFeesEarned = _calculateTvlFee(tvl, vaultAccruals.fees.tvl, timeDelta);
              uint256 protocolFeesEarned = _calculateTvlFee(tvl, protocolFees.tvl, timeDelta);
              if (price > vaultPriceState.highestPrice) {
                  uint256 profit = (price - vaultPriceState.highestPrice) * minTotalSupply / UNIT_PRICE_PRECISION;
                  vaultFeesEarned += _calculatePerformanceFee(profit, vaultAccruals.fees.performance);
                  protocolFeesEarned += _calculatePerformanceFee(profit, protocolFees.performance);
                  // Effects: update the highest price
                  vaultPriceState.highestPrice = uint128(price);
              }
              // Effects: update the accrued fees
              vaultAccruals.accruedFees += vaultFeesEarned.toUint112();
              vaultAccruals.accruedProtocolFees += protocolFeesEarned.toUint112();
              // Effects: update the last total supply and last fee accrual
              vaultPriceState.lastTotalSupply = currentTotalSupply.toUint128();
              vaultPriceState.accrualLag = 0;
          }
          /// @notice Sets the paused state for a vault
          /// @param vaultPriceState The storage pointer to the vault's price state
          /// @param vault The address of the vault
          /// @param paused The new paused state
          function _setVaultPaused(VaultPriceState storage vaultPriceState, address vault, bool paused) internal {
              // Effects: set the vault paused state
              vaultPriceState.paused = paused;
              // Log the vault paused state change
              emit VaultPausedChanged(vault, paused);
          }
          /// @notice Converts a token amount to units
          /// @param vault The address of the vault
          /// @param token The token to convert
          /// @param tokenAmount The amount of tokens to convert
          /// @param unitPrice The price of a single vault unit
          /// @return unitsAmount The amount of units
          function _convertTokenToUnits(
              address vault,
              IERC20 token,
              uint256 tokenAmount,
              uint256 unitPrice,
              Math.Rounding rounding
          ) internal view returns (uint256 unitsAmount) {
              if (address(token) != NUMERAIRE) {
                  tokenAmount = ORACLE_REGISTRY.getQuoteForUser(tokenAmount, address(token), NUMERAIRE, vault);
              }
              return Math.mulDiv(tokenAmount, UNIT_PRICE_PRECISION, unitPrice, rounding);
          }
          /// @notice Converts a units amount to tokens
          /// @param vault The address of the vault
          /// @param token The token to convert
          /// @param unitsAmount The amount of units to convert
          /// @param unitPrice The price of a single vault unit
          /// @return tokenAmount The amount of tokens
          function _convertUnitsToToken(
              address vault,
              IERC20 token,
              uint256 unitsAmount,
              uint256 unitPrice,
              Math.Rounding rounding
          ) internal view returns (uint256 tokenAmount) {
              uint256 numeraireAmount = Math.mulDiv(unitsAmount, unitPrice, UNIT_PRICE_PRECISION, rounding);
              if (address(token) == NUMERAIRE) {
                  return numeraireAmount;
              }
              return ORACLE_REGISTRY.getQuoteForUser(numeraireAmount, NUMERAIRE, address(token), vault);
          }
          /// @notice Validates a price update
          /// @dev Price is invalid if it is 0, before the last update, in the future, or if the price age is stale
          /// @param vaultPriceState The storage pointer to the vault's price state
          /// @param price The price of a single vault unit
          /// @param timestamp The timestamp of the price update
          function _validatePriceUpdate(VaultPriceState storage vaultPriceState, uint256 price, uint256 timestamp)
              internal
              view
          {
              // check that the price is not 0
              require(price != 0, Aera__InvalidPrice());
              // check that the price is not before the last update
              require(timestamp > vaultPriceState.timestamp, Aera__TimestampMustBeAfterLastUpdate());
              // check that the price is not in the future
              require(block.timestamp >= timestamp, Aera__TimestampCantBeInFuture());
              uint256 maxPriceAge = vaultPriceState.maxPriceAge;
              // check that the thresholds are set
              require(maxPriceAge != 0, Aera__ThresholdNotSet());
              // check update price age
              require(maxPriceAge + timestamp >= block.timestamp, Aera__StalePrice());
          }
          /// @notice Determines if a price update should pause the vault
          /// @dev Vault should pause if the price increase or decrease is too large, or if the min update interval has not
          /// passed
          /// @param state The storage pointer to the vault's price state
          /// @param price The price of a single vault unit
          /// @param timestamp The timestamp of the price update
          /// @return shouldPause True if the price update should pause the vault, false otherwise
          function _shouldPause(VaultPriceState storage state, uint256 price, uint32 timestamp)
              internal
              view
              returns (bool)
          {
              unchecked {
                  uint256 lastUpdateTime = state.timestamp;
                  // Cant overflow because minUpdateIntervalMinutes is uint16 and timestamp is uint32
                  if (timestamp < state.minUpdateIntervalMinutes * ONE_MINUTE + lastUpdateTime) {
                      return true;
                  }
                  // Cant overflow because timestamp is required to be > lastUpdateTime in _validatePriceUpdate
                  if (timestamp - lastUpdateTime > state.maxUpdateDelayDays * ONE_DAY) {
                      return true;
                  }
                  uint256 currentPrice = state.unitPrice;
                  if (price > currentPrice) {
                      // Cant overflow because maxPriceToleranceRatio is uint16 and currentPrice is uint128
                      return price * ONE_IN_BPS > currentPrice * state.maxPriceToleranceRatio;
                  } else {
                      // Cant overflow because minPriceToleranceRatio is uint16 and currentPrice is uint128
                      return price * ONE_IN_BPS < currentPrice * state.minPriceToleranceRatio;
                  }
              }
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
      pragma solidity ^0.8.20;
      /**
       * @dev Interface of the ERC-20 standard as defined in the ERC.
       */
      interface IERC20 {
          /**
           * @dev Emitted when `value` tokens are moved from one account (`from`) to
           * another (`to`).
           *
           * Note that `value` may be zero.
           */
          event Transfer(address indexed from, address indexed to, uint256 value);
          /**
           * @dev Emitted when the allowance of a `spender` for an `owner` is set by
           * a call to {approve}. `value` is the new allowance.
           */
          event Approval(address indexed owner, address indexed spender, uint256 value);
          /**
           * @dev Returns the value of tokens in existence.
           */
          function totalSupply() external view returns (uint256);
          /**
           * @dev Returns the value of tokens owned by `account`.
           */
          function balanceOf(address account) external view returns (uint256);
          /**
           * @dev Moves a `value` amount of tokens from the caller's account to `to`.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transfer(address to, uint256 value) external returns (bool);
          /**
           * @dev Returns the remaining number of tokens that `spender` will be
           * allowed to spend on behalf of `owner` through {transferFrom}. This is
           * zero by default.
           *
           * This value changes when {approve} or {transferFrom} are called.
           */
          function allowance(address owner, address spender) external view returns (uint256);
          /**
           * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
           * caller's tokens.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * IMPORTANT: Beware that changing an allowance with this method brings the risk
           * that someone may use both the old and the new allowance by unfortunate
           * transaction ordering. One possible solution to mitigate this race
           * condition is to first reduce the spender's allowance to 0 and set the
           * desired value afterwards:
           * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
           *
           * Emits an {Approval} event.
           */
          function approve(address spender, uint256 value) external returns (bool);
          /**
           * @dev Moves a `value` amount of tokens from `from` to `to` using the
           * allowance mechanism. `value` is then deducted from the caller's
           * allowance.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transferFrom(address from, address to, uint256 value) external returns (bool);
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
      pragma solidity ^0.8.20;
      import {Panic} from "../Panic.sol";
      import {SafeCast} from "./SafeCast.sol";
      /**
       * @dev Standard math utilities missing in the Solidity language.
       */
      library Math {
          enum Rounding {
              Floor, // Toward negative infinity
              Ceil, // Toward positive infinity
              Trunc, // Toward zero
              Expand // Away from zero
          }
          /**
           * @dev Returns the addition of two unsigned integers, with an success flag (no overflow).
           */
          function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
              unchecked {
                  uint256 c = a + b;
                  if (c < a) return (false, 0);
                  return (true, c);
              }
          }
          /**
           * @dev Returns the subtraction of two unsigned integers, with an success flag (no overflow).
           */
          function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
              unchecked {
                  if (b > a) return (false, 0);
                  return (true, a - b);
              }
          }
          /**
           * @dev Returns the multiplication of two unsigned integers, with an success flag (no overflow).
           */
          function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
              unchecked {
                  // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                  // benefit is lost if 'b' is also tested.
                  // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                  if (a == 0) return (true, 0);
                  uint256 c = a * b;
                  if (c / a != b) return (false, 0);
                  return (true, c);
              }
          }
          /**
           * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
           */
          function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
              unchecked {
                  if (b == 0) return (false, 0);
                  return (true, a / b);
              }
          }
          /**
           * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
           */
          function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
              unchecked {
                  if (b == 0) return (false, 0);
                  return (true, a % b);
              }
          }
          /**
           * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
           *
           * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
           * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
           * one branch when needed, making this function more expensive.
           */
          function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
              unchecked {
                  // branchless ternary works because:
                  // b ^ (a ^ b) == a
                  // b ^ 0 == b
                  return b ^ ((a ^ b) * SafeCast.toUint(condition));
              }
          }
          /**
           * @dev Returns the largest of two numbers.
           */
          function max(uint256 a, uint256 b) internal pure returns (uint256) {
              return ternary(a > b, a, b);
          }
          /**
           * @dev Returns the smallest of two numbers.
           */
          function min(uint256 a, uint256 b) internal pure returns (uint256) {
              return ternary(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 towards infinity instead
           * of rounding towards zero.
           */
          function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
              if (b == 0) {
                  // Guarantee the same behavior as in a regular Solidity division.
                  Panic.panic(Panic.DIVISION_BY_ZERO);
              }
              // The following calculation ensures accurate ceiling division without overflow.
              // Since a is non-zero, (a - 1) / b will not overflow.
              // The largest possible result occurs when (a - 1) / b is type(uint256).max,
              // but the largest value we can obtain is type(uint256).max - 1, which happens
              // when a = type(uint256).max and b = 1.
              unchecked {
                  return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
              }
          }
          /**
           * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
           * denominator == 0.
           *
           * 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²⁵⁶ and mod 2²⁵⁶ - 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²⁵⁶ + prod0.
                  uint256 prod0 = x * y; // Least significant 256 bits of the product
                  uint256 prod1; // Most significant 256 bits of the product
                  assembly {
                      let mm := mulmod(x, y, not(0))
                      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²⁵⁶. Also prevents denominator == 0.
                  if (denominator <= prod1) {
                      Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_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.
                  uint256 twos = denominator & (0 - denominator);
                  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²⁵⁶ / 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²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
                  // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
                  // four bits. That is, denominator * inv ≡ 1 mod 2⁴.
                  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⁸
                  inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
                  inverse *= 2 - denominator * inverse; // inverse mod 2³²
                  inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
                  inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
                  inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶
                  // 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²⁵⁶. Since the preconditions guarantee that the outcome is
                  // less than 2²⁵⁶, 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;
              }
          }
          /**
           * @dev 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) {
              return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
          }
          /**
           * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
           *
           * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
           * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
           *
           * If the input value is not inversible, 0 is returned.
           *
           * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
           * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
           */
          function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
              unchecked {
                  if (n == 0) return 0;
                  // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
                  // Used to compute integers x and y such that: ax + ny = gcd(a, n).
                  // When the gcd is 1, then the inverse of a modulo n exists and it's x.
                  // ax + ny = 1
                  // ax = 1 + (-y)n
                  // ax ≡ 1 (mod n) # x is the inverse of a modulo n
                  // If the remainder is 0 the gcd is n right away.
                  uint256 remainder = a % n;
                  uint256 gcd = n;
                  // Therefore the initial coefficients are:
                  // ax + ny = gcd(a, n) = n
                  // 0a + 1n = n
                  int256 x = 0;
                  int256 y = 1;
                  while (remainder != 0) {
                      uint256 quotient = gcd / remainder;
                      (gcd, remainder) = (
                          // The old remainder is the next gcd to try.
                          remainder,
                          // Compute the next remainder.
                          // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
                          // where gcd is at most n (capped to type(uint256).max)
                          gcd - remainder * quotient
                      );
                      (x, y) = (
                          // Increment the coefficient of a.
                          y,
                          // Decrement the coefficient of n.
                          // Can overflow, but the result is casted to uint256 so that the
                          // next value of y is "wrapped around" to a value between 0 and n - 1.
                          x - y * int256(quotient)
                      );
                  }
                  if (gcd != 1) return 0; // No inverse exists.
                  return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
              }
          }
          /**
           * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
           *
           * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
           * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
           * `a**(p-2)` is the modular multiplicative inverse of a in Fp.
           *
           * NOTE: this function does NOT check that `p` is a prime greater than `2`.
           */
          function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
              unchecked {
                  return Math.modExp(a, p - 2, p);
              }
          }
          /**
           * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
           *
           * Requirements:
           * - modulus can't be zero
           * - underlying staticcall to precompile must succeed
           *
           * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make
           * sure the chain you're using it on supports the precompiled contract for modular exponentiation
           * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,
           * the underlying function will succeed given the lack of a revert, but the result may be incorrectly
           * interpreted as 0.
           */
          function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
              (bool success, uint256 result) = tryModExp(b, e, m);
              if (!success) {
                  Panic.panic(Panic.DIVISION_BY_ZERO);
              }
              return result;
          }
          /**
           * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
           * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
           * to operate modulo 0 or if the underlying precompile reverted.
           *
           * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain
           * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in
           * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack
           * of a revert, but the result may be incorrectly interpreted as 0.
           */
          function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {
              if (m == 0) return (false, 0);
              assembly ("memory-safe") {
                  let ptr := mload(0x40)
                  // | Offset    | Content    | Content (Hex)                                                      |
                  // |-----------|------------|--------------------------------------------------------------------|
                  // | 0x00:0x1f | size of b  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
                  // | 0x20:0x3f | size of e  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
                  // | 0x40:0x5f | size of m  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
                  // | 0x60:0x7f | value of b | 0x<.............................................................b> |
                  // | 0x80:0x9f | value of e | 0x<.............................................................e> |
                  // | 0xa0:0xbf | value of m | 0x<.............................................................m> |
                  mstore(ptr, 0x20)
                  mstore(add(ptr, 0x20), 0x20)
                  mstore(add(ptr, 0x40), 0x20)
                  mstore(add(ptr, 0x60), b)
                  mstore(add(ptr, 0x80), e)
                  mstore(add(ptr, 0xa0), m)
                  // Given the result < m, it's guaranteed to fit in 32 bytes,
                  // so we can use the memory scratch space located at offset 0.
                  success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)
                  result := mload(0x00)
              }
          }
          /**
           * @dev Variant of {modExp} that supports inputs of arbitrary length.
           */
          function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {
              (bool success, bytes memory result) = tryModExp(b, e, m);
              if (!success) {
                  Panic.panic(Panic.DIVISION_BY_ZERO);
              }
              return result;
          }
          /**
           * @dev Variant of {tryModExp} that supports inputs of arbitrary length.
           */
          function tryModExp(
              bytes memory b,
              bytes memory e,
              bytes memory m
          ) internal view returns (bool success, bytes memory result) {
              if (_zeroBytes(m)) return (false, new bytes(0));
              uint256 mLen = m.length;
              // Encode call args in result and move the free memory pointer
              result = abi.encodePacked(b.length, e.length, mLen, b, e, m);
              assembly ("memory-safe") {
                  let dataPtr := add(result, 0x20)
                  // Write result on top of args to avoid allocating extra memory.
                  success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)
                  // Overwrite the length.
                  // result.length > returndatasize() is guaranteed because returndatasize() == m.length
                  mstore(result, mLen)
                  // Set the memory pointer after the returned data.
                  mstore(0x40, add(dataPtr, mLen))
              }
          }
          /**
           * @dev Returns whether the provided byte array is zero.
           */
          function _zeroBytes(bytes memory byteArray) private pure returns (bool) {
              for (uint256 i = 0; i < byteArray.length; ++i) {
                  if (byteArray[i] != 0) {
                      return false;
                  }
              }
              return true;
          }
          /**
           * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
           * towards zero.
           *
           * This method is based on Newton's method for computing square roots; the algorithm is restricted to only
           * using integer operations.
           */
          function sqrt(uint256 a) internal pure returns (uint256) {
              unchecked {
                  // Take care of easy edge cases when a == 0 or a == 1
                  if (a <= 1) {
                      return a;
                  }
                  // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a
                  // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between
                  // the current value as `ε_n = | x_n - sqrt(a) |`.
                  //
                  // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root
                  // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is
                  // bigger than any uint256.
                  //
                  // By noticing that
                  // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`
                  // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar
                  // to the msb function.
                  uint256 aa = a;
                  uint256 xn = 1;
                  if (aa >= (1 << 128)) {
                      aa >>= 128;
                      xn <<= 64;
                  }
                  if (aa >= (1 << 64)) {
                      aa >>= 64;
                      xn <<= 32;
                  }
                  if (aa >= (1 << 32)) {
                      aa >>= 32;
                      xn <<= 16;
                  }
                  if (aa >= (1 << 16)) {
                      aa >>= 16;
                      xn <<= 8;
                  }
                  if (aa >= (1 << 8)) {
                      aa >>= 8;
                      xn <<= 4;
                  }
                  if (aa >= (1 << 4)) {
                      aa >>= 4;
                      xn <<= 2;
                  }
                  if (aa >= (1 << 2)) {
                      xn <<= 1;
                  }
                  // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).
                  //
                  // We can refine our estimation by noticing that the middle of that interval minimizes the error.
                  // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).
                  // This is going to be our x_0 (and ε_0)
                  xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)
                  // From here, Newton's method give us:
                  // x_{n+1} = (x_n + a / x_n) / 2
                  //
                  // One should note that:
                  // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a
                  //              = ((x_n² + a) / (2 * x_n))² - a
                  //              = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a
                  //              = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)
                  //              = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)
                  //              = (x_n² - a)² / (2 * x_n)²
                  //              = ((x_n² - a) / (2 * x_n))²
                  //              ≥ 0
                  // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n
                  //
                  // This gives us the proof of quadratic convergence of the sequence:
                  // ε_{n+1} = | x_{n+1} - sqrt(a) |
                  //         = | (x_n + a / x_n) / 2 - sqrt(a) |
                  //         = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |
                  //         = | (x_n - sqrt(a))² / (2 * x_n) |
                  //         = | ε_n² / (2 * x_n) |
                  //         = ε_n² / | (2 * x_n) |
                  //
                  // For the first iteration, we have a special case where x_0 is known:
                  // ε_1 = ε_0² / | (2 * x_0) |
                  //     ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))
                  //     ≤ 2**(2*e-4) / (3 * 2**(e-1))
                  //     ≤ 2**(e-3) / 3
                  //     ≤ 2**(e-3-log2(3))
                  //     ≤ 2**(e-4.5)
                  //
                  // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:
                  // ε_{n+1} = ε_n² / | (2 * x_n) |
                  //         ≤ (2**(e-k))² / (2 * 2**(e-1))
                  //         ≤ 2**(2*e-2*k) / 2**e
                  //         ≤ 2**(e-2*k)
                  xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5)  -- special case, see above
                  xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9)    -- general case with k = 4.5
                  xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18)   -- general case with k = 9
                  xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36)   -- general case with k = 18
                  xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72)   -- general case with k = 36
                  xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144)  -- general case with k = 72
                  // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision
                  // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either
                  // sqrt(a) or sqrt(a) + 1.
                  return xn - SafeCast.toUint(xn > a / xn);
              }
          }
          /**
           * @dev 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 + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);
              }
          }
          /**
           * @dev Return the log in base 2 of a positive value rounded towards zero.
           * Returns 0 if given 0.
           */
          function log2(uint256 value) internal pure returns (uint256) {
              uint256 result = 0;
              uint256 exp;
              unchecked {
                  exp = 128 * SafeCast.toUint(value > (1 << 128) - 1);
                  value >>= exp;
                  result += exp;
                  exp = 64 * SafeCast.toUint(value > (1 << 64) - 1);
                  value >>= exp;
                  result += exp;
                  exp = 32 * SafeCast.toUint(value > (1 << 32) - 1);
                  value >>= exp;
                  result += exp;
                  exp = 16 * SafeCast.toUint(value > (1 << 16) - 1);
                  value >>= exp;
                  result += exp;
                  exp = 8 * SafeCast.toUint(value > (1 << 8) - 1);
                  value >>= exp;
                  result += exp;
                  exp = 4 * SafeCast.toUint(value > (1 << 4) - 1);
                  value >>= exp;
                  result += exp;
                  exp = 2 * SafeCast.toUint(value > (1 << 2) - 1);
                  value >>= exp;
                  result += exp;
                  result += SafeCast.toUint(value > 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 + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);
              }
          }
          /**
           * @dev Return the log in base 10 of a positive value rounded towards zero.
           * 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 + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);
              }
          }
          /**
           * @dev Return the log in base 256 of a positive value rounded towards zero.
           * 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;
              uint256 isGt;
              unchecked {
                  isGt = SafeCast.toUint(value > (1 << 128) - 1);
                  value >>= isGt * 128;
                  result += isGt * 16;
                  isGt = SafeCast.toUint(value > (1 << 64) - 1);
                  value >>= isGt * 64;
                  result += isGt * 8;
                  isGt = SafeCast.toUint(value > (1 << 32) - 1);
                  value >>= isGt * 32;
                  result += isGt * 4;
                  isGt = SafeCast.toUint(value > (1 << 16) - 1);
                  value >>= isGt * 16;
                  result += isGt * 2;
                  result += SafeCast.toUint(value > (1 << 8) - 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 + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);
              }
          }
          /**
           * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
           */
          function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
              return uint8(rounding) % 2 == 1;
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SafeCast.sol)
      // This file was procedurally generated from scripts/generate/templates/SafeCast.js.
      pragma solidity ^0.8.20;
      /**
       * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow
       * checks.
       *
       * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
       * easily result in undesired exploitation or bugs, since developers usually
       * assume that overflows raise errors. `SafeCast` restores this intuition by
       * reverting the transaction when such an operation overflows.
       *
       * Using this library instead of the unchecked operations eliminates an entire
       * class of bugs, so it's recommended to use it always.
       */
      library SafeCast {
          /**
           * @dev Value doesn't fit in an uint of `bits` size.
           */
          error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);
          /**
           * @dev An int value doesn't fit in an uint of `bits` size.
           */
          error SafeCastOverflowedIntToUint(int256 value);
          /**
           * @dev Value doesn't fit in an int of `bits` size.
           */
          error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);
          /**
           * @dev An uint value doesn't fit in an int of `bits` size.
           */
          error SafeCastOverflowedUintToInt(uint256 value);
          /**
           * @dev Returns the downcasted uint248 from uint256, reverting on
           * overflow (when the input is greater than largest uint248).
           *
           * Counterpart to Solidity's `uint248` operator.
           *
           * Requirements:
           *
           * - input must fit into 248 bits
           */
          function toUint248(uint256 value) internal pure returns (uint248) {
              if (value > type(uint248).max) {
                  revert SafeCastOverflowedUintDowncast(248, value);
              }
              return uint248(value);
          }
          /**
           * @dev Returns the downcasted uint240 from uint256, reverting on
           * overflow (when the input is greater than largest uint240).
           *
           * Counterpart to Solidity's `uint240` operator.
           *
           * Requirements:
           *
           * - input must fit into 240 bits
           */
          function toUint240(uint256 value) internal pure returns (uint240) {
              if (value > type(uint240).max) {
                  revert SafeCastOverflowedUintDowncast(240, value);
              }
              return uint240(value);
          }
          /**
           * @dev Returns the downcasted uint232 from uint256, reverting on
           * overflow (when the input is greater than largest uint232).
           *
           * Counterpart to Solidity's `uint232` operator.
           *
           * Requirements:
           *
           * - input must fit into 232 bits
           */
          function toUint232(uint256 value) internal pure returns (uint232) {
              if (value > type(uint232).max) {
                  revert SafeCastOverflowedUintDowncast(232, value);
              }
              return uint232(value);
          }
          /**
           * @dev Returns the downcasted uint224 from uint256, reverting on
           * overflow (when the input is greater than largest uint224).
           *
           * Counterpart to Solidity's `uint224` operator.
           *
           * Requirements:
           *
           * - input must fit into 224 bits
           */
          function toUint224(uint256 value) internal pure returns (uint224) {
              if (value > type(uint224).max) {
                  revert SafeCastOverflowedUintDowncast(224, value);
              }
              return uint224(value);
          }
          /**
           * @dev Returns the downcasted uint216 from uint256, reverting on
           * overflow (when the input is greater than largest uint216).
           *
           * Counterpart to Solidity's `uint216` operator.
           *
           * Requirements:
           *
           * - input must fit into 216 bits
           */
          function toUint216(uint256 value) internal pure returns (uint216) {
              if (value > type(uint216).max) {
                  revert SafeCastOverflowedUintDowncast(216, value);
              }
              return uint216(value);
          }
          /**
           * @dev Returns the downcasted uint208 from uint256, reverting on
           * overflow (when the input is greater than largest uint208).
           *
           * Counterpart to Solidity's `uint208` operator.
           *
           * Requirements:
           *
           * - input must fit into 208 bits
           */
          function toUint208(uint256 value) internal pure returns (uint208) {
              if (value > type(uint208).max) {
                  revert SafeCastOverflowedUintDowncast(208, value);
              }
              return uint208(value);
          }
          /**
           * @dev Returns the downcasted uint200 from uint256, reverting on
           * overflow (when the input is greater than largest uint200).
           *
           * Counterpart to Solidity's `uint200` operator.
           *
           * Requirements:
           *
           * - input must fit into 200 bits
           */
          function toUint200(uint256 value) internal pure returns (uint200) {
              if (value > type(uint200).max) {
                  revert SafeCastOverflowedUintDowncast(200, value);
              }
              return uint200(value);
          }
          /**
           * @dev Returns the downcasted uint192 from uint256, reverting on
           * overflow (when the input is greater than largest uint192).
           *
           * Counterpart to Solidity's `uint192` operator.
           *
           * Requirements:
           *
           * - input must fit into 192 bits
           */
          function toUint192(uint256 value) internal pure returns (uint192) {
              if (value > type(uint192).max) {
                  revert SafeCastOverflowedUintDowncast(192, value);
              }
              return uint192(value);
          }
          /**
           * @dev Returns the downcasted uint184 from uint256, reverting on
           * overflow (when the input is greater than largest uint184).
           *
           * Counterpart to Solidity's `uint184` operator.
           *
           * Requirements:
           *
           * - input must fit into 184 bits
           */
          function toUint184(uint256 value) internal pure returns (uint184) {
              if (value > type(uint184).max) {
                  revert SafeCastOverflowedUintDowncast(184, value);
              }
              return uint184(value);
          }
          /**
           * @dev Returns the downcasted uint176 from uint256, reverting on
           * overflow (when the input is greater than largest uint176).
           *
           * Counterpart to Solidity's `uint176` operator.
           *
           * Requirements:
           *
           * - input must fit into 176 bits
           */
          function toUint176(uint256 value) internal pure returns (uint176) {
              if (value > type(uint176).max) {
                  revert SafeCastOverflowedUintDowncast(176, value);
              }
              return uint176(value);
          }
          /**
           * @dev Returns the downcasted uint168 from uint256, reverting on
           * overflow (when the input is greater than largest uint168).
           *
           * Counterpart to Solidity's `uint168` operator.
           *
           * Requirements:
           *
           * - input must fit into 168 bits
           */
          function toUint168(uint256 value) internal pure returns (uint168) {
              if (value > type(uint168).max) {
                  revert SafeCastOverflowedUintDowncast(168, value);
              }
              return uint168(value);
          }
          /**
           * @dev Returns the downcasted uint160 from uint256, reverting on
           * overflow (when the input is greater than largest uint160).
           *
           * Counterpart to Solidity's `uint160` operator.
           *
           * Requirements:
           *
           * - input must fit into 160 bits
           */
          function toUint160(uint256 value) internal pure returns (uint160) {
              if (value > type(uint160).max) {
                  revert SafeCastOverflowedUintDowncast(160, value);
              }
              return uint160(value);
          }
          /**
           * @dev Returns the downcasted uint152 from uint256, reverting on
           * overflow (when the input is greater than largest uint152).
           *
           * Counterpart to Solidity's `uint152` operator.
           *
           * Requirements:
           *
           * - input must fit into 152 bits
           */
          function toUint152(uint256 value) internal pure returns (uint152) {
              if (value > type(uint152).max) {
                  revert SafeCastOverflowedUintDowncast(152, value);
              }
              return uint152(value);
          }
          /**
           * @dev Returns the downcasted uint144 from uint256, reverting on
           * overflow (when the input is greater than largest uint144).
           *
           * Counterpart to Solidity's `uint144` operator.
           *
           * Requirements:
           *
           * - input must fit into 144 bits
           */
          function toUint144(uint256 value) internal pure returns (uint144) {
              if (value > type(uint144).max) {
                  revert SafeCastOverflowedUintDowncast(144, value);
              }
              return uint144(value);
          }
          /**
           * @dev Returns the downcasted uint136 from uint256, reverting on
           * overflow (when the input is greater than largest uint136).
           *
           * Counterpart to Solidity's `uint136` operator.
           *
           * Requirements:
           *
           * - input must fit into 136 bits
           */
          function toUint136(uint256 value) internal pure returns (uint136) {
              if (value > type(uint136).max) {
                  revert SafeCastOverflowedUintDowncast(136, value);
              }
              return uint136(value);
          }
          /**
           * @dev Returns the downcasted uint128 from uint256, reverting on
           * overflow (when the input is greater than largest uint128).
           *
           * Counterpart to Solidity's `uint128` operator.
           *
           * Requirements:
           *
           * - input must fit into 128 bits
           */
          function toUint128(uint256 value) internal pure returns (uint128) {
              if (value > type(uint128).max) {
                  revert SafeCastOverflowedUintDowncast(128, value);
              }
              return uint128(value);
          }
          /**
           * @dev Returns the downcasted uint120 from uint256, reverting on
           * overflow (when the input is greater than largest uint120).
           *
           * Counterpart to Solidity's `uint120` operator.
           *
           * Requirements:
           *
           * - input must fit into 120 bits
           */
          function toUint120(uint256 value) internal pure returns (uint120) {
              if (value > type(uint120).max) {
                  revert SafeCastOverflowedUintDowncast(120, value);
              }
              return uint120(value);
          }
          /**
           * @dev Returns the downcasted uint112 from uint256, reverting on
           * overflow (when the input is greater than largest uint112).
           *
           * Counterpart to Solidity's `uint112` operator.
           *
           * Requirements:
           *
           * - input must fit into 112 bits
           */
          function toUint112(uint256 value) internal pure returns (uint112) {
              if (value > type(uint112).max) {
                  revert SafeCastOverflowedUintDowncast(112, value);
              }
              return uint112(value);
          }
          /**
           * @dev Returns the downcasted uint104 from uint256, reverting on
           * overflow (when the input is greater than largest uint104).
           *
           * Counterpart to Solidity's `uint104` operator.
           *
           * Requirements:
           *
           * - input must fit into 104 bits
           */
          function toUint104(uint256 value) internal pure returns (uint104) {
              if (value > type(uint104).max) {
                  revert SafeCastOverflowedUintDowncast(104, value);
              }
              return uint104(value);
          }
          /**
           * @dev Returns the downcasted uint96 from uint256, reverting on
           * overflow (when the input is greater than largest uint96).
           *
           * Counterpart to Solidity's `uint96` operator.
           *
           * Requirements:
           *
           * - input must fit into 96 bits
           */
          function toUint96(uint256 value) internal pure returns (uint96) {
              if (value > type(uint96).max) {
                  revert SafeCastOverflowedUintDowncast(96, value);
              }
              return uint96(value);
          }
          /**
           * @dev Returns the downcasted uint88 from uint256, reverting on
           * overflow (when the input is greater than largest uint88).
           *
           * Counterpart to Solidity's `uint88` operator.
           *
           * Requirements:
           *
           * - input must fit into 88 bits
           */
          function toUint88(uint256 value) internal pure returns (uint88) {
              if (value > type(uint88).max) {
                  revert SafeCastOverflowedUintDowncast(88, value);
              }
              return uint88(value);
          }
          /**
           * @dev Returns the downcasted uint80 from uint256, reverting on
           * overflow (when the input is greater than largest uint80).
           *
           * Counterpart to Solidity's `uint80` operator.
           *
           * Requirements:
           *
           * - input must fit into 80 bits
           */
          function toUint80(uint256 value) internal pure returns (uint80) {
              if (value > type(uint80).max) {
                  revert SafeCastOverflowedUintDowncast(80, value);
              }
              return uint80(value);
          }
          /**
           * @dev Returns the downcasted uint72 from uint256, reverting on
           * overflow (when the input is greater than largest uint72).
           *
           * Counterpart to Solidity's `uint72` operator.
           *
           * Requirements:
           *
           * - input must fit into 72 bits
           */
          function toUint72(uint256 value) internal pure returns (uint72) {
              if (value > type(uint72).max) {
                  revert SafeCastOverflowedUintDowncast(72, value);
              }
              return uint72(value);
          }
          /**
           * @dev Returns the downcasted uint64 from uint256, reverting on
           * overflow (when the input is greater than largest uint64).
           *
           * Counterpart to Solidity's `uint64` operator.
           *
           * Requirements:
           *
           * - input must fit into 64 bits
           */
          function toUint64(uint256 value) internal pure returns (uint64) {
              if (value > type(uint64).max) {
                  revert SafeCastOverflowedUintDowncast(64, value);
              }
              return uint64(value);
          }
          /**
           * @dev Returns the downcasted uint56 from uint256, reverting on
           * overflow (when the input is greater than largest uint56).
           *
           * Counterpart to Solidity's `uint56` operator.
           *
           * Requirements:
           *
           * - input must fit into 56 bits
           */
          function toUint56(uint256 value) internal pure returns (uint56) {
              if (value > type(uint56).max) {
                  revert SafeCastOverflowedUintDowncast(56, value);
              }
              return uint56(value);
          }
          /**
           * @dev Returns the downcasted uint48 from uint256, reverting on
           * overflow (when the input is greater than largest uint48).
           *
           * Counterpart to Solidity's `uint48` operator.
           *
           * Requirements:
           *
           * - input must fit into 48 bits
           */
          function toUint48(uint256 value) internal pure returns (uint48) {
              if (value > type(uint48).max) {
                  revert SafeCastOverflowedUintDowncast(48, value);
              }
              return uint48(value);
          }
          /**
           * @dev Returns the downcasted uint40 from uint256, reverting on
           * overflow (when the input is greater than largest uint40).
           *
           * Counterpart to Solidity's `uint40` operator.
           *
           * Requirements:
           *
           * - input must fit into 40 bits
           */
          function toUint40(uint256 value) internal pure returns (uint40) {
              if (value > type(uint40).max) {
                  revert SafeCastOverflowedUintDowncast(40, value);
              }
              return uint40(value);
          }
          /**
           * @dev Returns the downcasted uint32 from uint256, reverting on
           * overflow (when the input is greater than largest uint32).
           *
           * Counterpart to Solidity's `uint32` operator.
           *
           * Requirements:
           *
           * - input must fit into 32 bits
           */
          function toUint32(uint256 value) internal pure returns (uint32) {
              if (value > type(uint32).max) {
                  revert SafeCastOverflowedUintDowncast(32, value);
              }
              return uint32(value);
          }
          /**
           * @dev Returns the downcasted uint24 from uint256, reverting on
           * overflow (when the input is greater than largest uint24).
           *
           * Counterpart to Solidity's `uint24` operator.
           *
           * Requirements:
           *
           * - input must fit into 24 bits
           */
          function toUint24(uint256 value) internal pure returns (uint24) {
              if (value > type(uint24).max) {
                  revert SafeCastOverflowedUintDowncast(24, value);
              }
              return uint24(value);
          }
          /**
           * @dev Returns the downcasted uint16 from uint256, reverting on
           * overflow (when the input is greater than largest uint16).
           *
           * Counterpart to Solidity's `uint16` operator.
           *
           * Requirements:
           *
           * - input must fit into 16 bits
           */
          function toUint16(uint256 value) internal pure returns (uint16) {
              if (value > type(uint16).max) {
                  revert SafeCastOverflowedUintDowncast(16, value);
              }
              return uint16(value);
          }
          /**
           * @dev Returns the downcasted uint8 from uint256, reverting on
           * overflow (when the input is greater than largest uint8).
           *
           * Counterpart to Solidity's `uint8` operator.
           *
           * Requirements:
           *
           * - input must fit into 8 bits
           */
          function toUint8(uint256 value) internal pure returns (uint8) {
              if (value > type(uint8).max) {
                  revert SafeCastOverflowedUintDowncast(8, value);
              }
              return uint8(value);
          }
          /**
           * @dev Converts a signed int256 into an unsigned uint256.
           *
           * Requirements:
           *
           * - input must be greater than or equal to 0.
           */
          function toUint256(int256 value) internal pure returns (uint256) {
              if (value < 0) {
                  revert SafeCastOverflowedIntToUint(value);
              }
              return uint256(value);
          }
          /**
           * @dev Returns the downcasted int248 from int256, reverting on
           * overflow (when the input is less than smallest int248 or
           * greater than largest int248).
           *
           * Counterpart to Solidity's `int248` operator.
           *
           * Requirements:
           *
           * - input must fit into 248 bits
           */
          function toInt248(int256 value) internal pure returns (int248 downcasted) {
              downcasted = int248(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(248, value);
              }
          }
          /**
           * @dev Returns the downcasted int240 from int256, reverting on
           * overflow (when the input is less than smallest int240 or
           * greater than largest int240).
           *
           * Counterpart to Solidity's `int240` operator.
           *
           * Requirements:
           *
           * - input must fit into 240 bits
           */
          function toInt240(int256 value) internal pure returns (int240 downcasted) {
              downcasted = int240(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(240, value);
              }
          }
          /**
           * @dev Returns the downcasted int232 from int256, reverting on
           * overflow (when the input is less than smallest int232 or
           * greater than largest int232).
           *
           * Counterpart to Solidity's `int232` operator.
           *
           * Requirements:
           *
           * - input must fit into 232 bits
           */
          function toInt232(int256 value) internal pure returns (int232 downcasted) {
              downcasted = int232(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(232, value);
              }
          }
          /**
           * @dev Returns the downcasted int224 from int256, reverting on
           * overflow (when the input is less than smallest int224 or
           * greater than largest int224).
           *
           * Counterpart to Solidity's `int224` operator.
           *
           * Requirements:
           *
           * - input must fit into 224 bits
           */
          function toInt224(int256 value) internal pure returns (int224 downcasted) {
              downcasted = int224(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(224, value);
              }
          }
          /**
           * @dev Returns the downcasted int216 from int256, reverting on
           * overflow (when the input is less than smallest int216 or
           * greater than largest int216).
           *
           * Counterpart to Solidity's `int216` operator.
           *
           * Requirements:
           *
           * - input must fit into 216 bits
           */
          function toInt216(int256 value) internal pure returns (int216 downcasted) {
              downcasted = int216(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(216, value);
              }
          }
          /**
           * @dev Returns the downcasted int208 from int256, reverting on
           * overflow (when the input is less than smallest int208 or
           * greater than largest int208).
           *
           * Counterpart to Solidity's `int208` operator.
           *
           * Requirements:
           *
           * - input must fit into 208 bits
           */
          function toInt208(int256 value) internal pure returns (int208 downcasted) {
              downcasted = int208(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(208, value);
              }
          }
          /**
           * @dev Returns the downcasted int200 from int256, reverting on
           * overflow (when the input is less than smallest int200 or
           * greater than largest int200).
           *
           * Counterpart to Solidity's `int200` operator.
           *
           * Requirements:
           *
           * - input must fit into 200 bits
           */
          function toInt200(int256 value) internal pure returns (int200 downcasted) {
              downcasted = int200(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(200, value);
              }
          }
          /**
           * @dev Returns the downcasted int192 from int256, reverting on
           * overflow (when the input is less than smallest int192 or
           * greater than largest int192).
           *
           * Counterpart to Solidity's `int192` operator.
           *
           * Requirements:
           *
           * - input must fit into 192 bits
           */
          function toInt192(int256 value) internal pure returns (int192 downcasted) {
              downcasted = int192(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(192, value);
              }
          }
          /**
           * @dev Returns the downcasted int184 from int256, reverting on
           * overflow (when the input is less than smallest int184 or
           * greater than largest int184).
           *
           * Counterpart to Solidity's `int184` operator.
           *
           * Requirements:
           *
           * - input must fit into 184 bits
           */
          function toInt184(int256 value) internal pure returns (int184 downcasted) {
              downcasted = int184(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(184, value);
              }
          }
          /**
           * @dev Returns the downcasted int176 from int256, reverting on
           * overflow (when the input is less than smallest int176 or
           * greater than largest int176).
           *
           * Counterpart to Solidity's `int176` operator.
           *
           * Requirements:
           *
           * - input must fit into 176 bits
           */
          function toInt176(int256 value) internal pure returns (int176 downcasted) {
              downcasted = int176(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(176, value);
              }
          }
          /**
           * @dev Returns the downcasted int168 from int256, reverting on
           * overflow (when the input is less than smallest int168 or
           * greater than largest int168).
           *
           * Counterpart to Solidity's `int168` operator.
           *
           * Requirements:
           *
           * - input must fit into 168 bits
           */
          function toInt168(int256 value) internal pure returns (int168 downcasted) {
              downcasted = int168(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(168, value);
              }
          }
          /**
           * @dev Returns the downcasted int160 from int256, reverting on
           * overflow (when the input is less than smallest int160 or
           * greater than largest int160).
           *
           * Counterpart to Solidity's `int160` operator.
           *
           * Requirements:
           *
           * - input must fit into 160 bits
           */
          function toInt160(int256 value) internal pure returns (int160 downcasted) {
              downcasted = int160(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(160, value);
              }
          }
          /**
           * @dev Returns the downcasted int152 from int256, reverting on
           * overflow (when the input is less than smallest int152 or
           * greater than largest int152).
           *
           * Counterpart to Solidity's `int152` operator.
           *
           * Requirements:
           *
           * - input must fit into 152 bits
           */
          function toInt152(int256 value) internal pure returns (int152 downcasted) {
              downcasted = int152(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(152, value);
              }
          }
          /**
           * @dev Returns the downcasted int144 from int256, reverting on
           * overflow (when the input is less than smallest int144 or
           * greater than largest int144).
           *
           * Counterpart to Solidity's `int144` operator.
           *
           * Requirements:
           *
           * - input must fit into 144 bits
           */
          function toInt144(int256 value) internal pure returns (int144 downcasted) {
              downcasted = int144(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(144, value);
              }
          }
          /**
           * @dev Returns the downcasted int136 from int256, reverting on
           * overflow (when the input is less than smallest int136 or
           * greater than largest int136).
           *
           * Counterpart to Solidity's `int136` operator.
           *
           * Requirements:
           *
           * - input must fit into 136 bits
           */
          function toInt136(int256 value) internal pure returns (int136 downcasted) {
              downcasted = int136(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(136, value);
              }
          }
          /**
           * @dev Returns the downcasted int128 from int256, reverting on
           * overflow (when the input is less than smallest int128 or
           * greater than largest int128).
           *
           * Counterpart to Solidity's `int128` operator.
           *
           * Requirements:
           *
           * - input must fit into 128 bits
           */
          function toInt128(int256 value) internal pure returns (int128 downcasted) {
              downcasted = int128(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(128, value);
              }
          }
          /**
           * @dev Returns the downcasted int120 from int256, reverting on
           * overflow (when the input is less than smallest int120 or
           * greater than largest int120).
           *
           * Counterpart to Solidity's `int120` operator.
           *
           * Requirements:
           *
           * - input must fit into 120 bits
           */
          function toInt120(int256 value) internal pure returns (int120 downcasted) {
              downcasted = int120(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(120, value);
              }
          }
          /**
           * @dev Returns the downcasted int112 from int256, reverting on
           * overflow (when the input is less than smallest int112 or
           * greater than largest int112).
           *
           * Counterpart to Solidity's `int112` operator.
           *
           * Requirements:
           *
           * - input must fit into 112 bits
           */
          function toInt112(int256 value) internal pure returns (int112 downcasted) {
              downcasted = int112(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(112, value);
              }
          }
          /**
           * @dev Returns the downcasted int104 from int256, reverting on
           * overflow (when the input is less than smallest int104 or
           * greater than largest int104).
           *
           * Counterpart to Solidity's `int104` operator.
           *
           * Requirements:
           *
           * - input must fit into 104 bits
           */
          function toInt104(int256 value) internal pure returns (int104 downcasted) {
              downcasted = int104(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(104, value);
              }
          }
          /**
           * @dev Returns the downcasted int96 from int256, reverting on
           * overflow (when the input is less than smallest int96 or
           * greater than largest int96).
           *
           * Counterpart to Solidity's `int96` operator.
           *
           * Requirements:
           *
           * - input must fit into 96 bits
           */
          function toInt96(int256 value) internal pure returns (int96 downcasted) {
              downcasted = int96(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(96, value);
              }
          }
          /**
           * @dev Returns the downcasted int88 from int256, reverting on
           * overflow (when the input is less than smallest int88 or
           * greater than largest int88).
           *
           * Counterpart to Solidity's `int88` operator.
           *
           * Requirements:
           *
           * - input must fit into 88 bits
           */
          function toInt88(int256 value) internal pure returns (int88 downcasted) {
              downcasted = int88(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(88, value);
              }
          }
          /**
           * @dev Returns the downcasted int80 from int256, reverting on
           * overflow (when the input is less than smallest int80 or
           * greater than largest int80).
           *
           * Counterpart to Solidity's `int80` operator.
           *
           * Requirements:
           *
           * - input must fit into 80 bits
           */
          function toInt80(int256 value) internal pure returns (int80 downcasted) {
              downcasted = int80(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(80, value);
              }
          }
          /**
           * @dev Returns the downcasted int72 from int256, reverting on
           * overflow (when the input is less than smallest int72 or
           * greater than largest int72).
           *
           * Counterpart to Solidity's `int72` operator.
           *
           * Requirements:
           *
           * - input must fit into 72 bits
           */
          function toInt72(int256 value) internal pure returns (int72 downcasted) {
              downcasted = int72(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(72, value);
              }
          }
          /**
           * @dev Returns the downcasted int64 from int256, reverting on
           * overflow (when the input is less than smallest int64 or
           * greater than largest int64).
           *
           * Counterpart to Solidity's `int64` operator.
           *
           * Requirements:
           *
           * - input must fit into 64 bits
           */
          function toInt64(int256 value) internal pure returns (int64 downcasted) {
              downcasted = int64(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(64, value);
              }
          }
          /**
           * @dev Returns the downcasted int56 from int256, reverting on
           * overflow (when the input is less than smallest int56 or
           * greater than largest int56).
           *
           * Counterpart to Solidity's `int56` operator.
           *
           * Requirements:
           *
           * - input must fit into 56 bits
           */
          function toInt56(int256 value) internal pure returns (int56 downcasted) {
              downcasted = int56(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(56, value);
              }
          }
          /**
           * @dev Returns the downcasted int48 from int256, reverting on
           * overflow (when the input is less than smallest int48 or
           * greater than largest int48).
           *
           * Counterpart to Solidity's `int48` operator.
           *
           * Requirements:
           *
           * - input must fit into 48 bits
           */
          function toInt48(int256 value) internal pure returns (int48 downcasted) {
              downcasted = int48(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(48, value);
              }
          }
          /**
           * @dev Returns the downcasted int40 from int256, reverting on
           * overflow (when the input is less than smallest int40 or
           * greater than largest int40).
           *
           * Counterpart to Solidity's `int40` operator.
           *
           * Requirements:
           *
           * - input must fit into 40 bits
           */
          function toInt40(int256 value) internal pure returns (int40 downcasted) {
              downcasted = int40(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(40, value);
              }
          }
          /**
           * @dev Returns the downcasted int32 from int256, reverting on
           * overflow (when the input is less than smallest int32 or
           * greater than largest int32).
           *
           * Counterpart to Solidity's `int32` operator.
           *
           * Requirements:
           *
           * - input must fit into 32 bits
           */
          function toInt32(int256 value) internal pure returns (int32 downcasted) {
              downcasted = int32(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(32, value);
              }
          }
          /**
           * @dev Returns the downcasted int24 from int256, reverting on
           * overflow (when the input is less than smallest int24 or
           * greater than largest int24).
           *
           * Counterpart to Solidity's `int24` operator.
           *
           * Requirements:
           *
           * - input must fit into 24 bits
           */
          function toInt24(int256 value) internal pure returns (int24 downcasted) {
              downcasted = int24(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(24, value);
              }
          }
          /**
           * @dev Returns the downcasted int16 from int256, reverting on
           * overflow (when the input is less than smallest int16 or
           * greater than largest int16).
           *
           * Counterpart to Solidity's `int16` operator.
           *
           * Requirements:
           *
           * - input must fit into 16 bits
           */
          function toInt16(int256 value) internal pure returns (int16 downcasted) {
              downcasted = int16(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(16, value);
              }
          }
          /**
           * @dev Returns the downcasted int8 from int256, reverting on
           * overflow (when the input is less than smallest int8 or
           * greater than largest int8).
           *
           * Counterpart to Solidity's `int8` operator.
           *
           * Requirements:
           *
           * - input must fit into 8 bits
           */
          function toInt8(int256 value) internal pure returns (int8 downcasted) {
              downcasted = int8(value);
              if (downcasted != value) {
                  revert SafeCastOverflowedIntDowncast(8, value);
              }
          }
          /**
           * @dev Converts an unsigned uint256 into a signed int256.
           *
           * Requirements:
           *
           * - input must be less than or equal to maxInt256.
           */
          function toInt256(uint256 value) internal pure returns (int256) {
              // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
              if (value > uint256(type(int256).max)) {
                  revert SafeCastOverflowedUintToInt(value);
              }
              return int256(value);
          }
          /**
           * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
           */
          function toUint(bool b) internal pure returns (uint256 u) {
              assembly ("memory-safe") {
                  u := iszero(iszero(b))
              }
          }
      }
      // SPDX-License-Identifier: AGPL-3.0-only
      pragma solidity 0.8.29;
      /// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic.
      /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
      /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
      abstract contract Auth {
          event OwnershipTransferred(address indexed user, address indexed newOwner);
          event AuthorityUpdated(address indexed user, Authority indexed newAuthority);
          address public owner;
          Authority public authority;
          constructor(address _owner, Authority _authority) {
              owner = _owner;
              authority = _authority;
              emit OwnershipTransferred(msg.sender, _owner);
              emit AuthorityUpdated(msg.sender, _authority);
          }
          modifier requiresAuth() virtual {
              require(isAuthorized(msg.sender, msg.sig), "UNAUTHORIZED");
              _;
          }
          function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
              Authority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas.
              // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be
              // aware that this makes protected functions uncallable even to the owner if the authority is out of order.
              return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner;
          }
          function setAuthority(Authority newAuthority) public virtual {
              // We check if the caller is the owner first because we want to ensure they can
              // always swap out the authority even if it's reverting or using up a lot of gas.
              require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig));
              authority = newAuthority;
              emit AuthorityUpdated(msg.sender, newAuthority);
          }
          function transferOwnership(address newOwner) public virtual requiresAuth {
              owner = newOwner;
              emit OwnershipTransferred(msg.sender, newOwner);
          }
      }
      /// @notice A generic interface for a contract which provides authorization data to an Auth instance.
      /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
      /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
      interface Authority {
          function canCall(address user, address target, bytes4 functionSig) external view returns (bool);
      }
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      import { Math } from "@oz/utils/math/Math.sol";
      import { Authority } from "@solmate/auth/Auth.sol";
      import { Auth2Step } from "src/core/Auth2Step.sol";
      import { MAX_PERFORMANCE_FEE, MAX_TVL_FEE, ONE_IN_BPS, SECONDS_PER_YEAR } from "src/core/Constants.sol";
      import { Fee, VaultAccruals } from "src/core/Types.sol";
      import { VaultAuth } from "src/core/VaultAuth.sol";
      import { IBaseFeeCalculator } from "src/core/interfaces/IBaseFeeCalculator.sol";
      import { IFeeCalculator } from "src/core/interfaces/IFeeCalculator.sol";
      /// @title BaseFeeCalculator
      /// @notice Module used with FeeVault to allow an off-chain accountant to submit necessary inputs that help
      /// compute TVL and performance fees owed to the vault. Serves as a central registry for all vaults
      /// and their associated fees
      abstract contract BaseFeeCalculator is IBaseFeeCalculator, IFeeCalculator, Auth2Step, VaultAuth {
          ////////////////////////////////////////////////////////////
          //                        Storage                         //
          ////////////////////////////////////////////////////////////
          /// @notice The protocol's fee configuration
          Fee public protocolFees;
          /// @notice The address that receives the protocol's fees
          address public protocolFeeRecipient;
          /// @notice A mapping of vault addresses to their associated state
          mapping(address vault => VaultAccruals vaultAccruals) internal _vaultAccruals;
          /// @notice A mapping of vault addresses to their assigned accountant
          mapping(address vault => address accountant) public vaultAccountant;
          ////////////////////////////////////////////////////////////
          //                       Modifiers                        //
          ////////////////////////////////////////////////////////////
          /// @notice Modifier that checks the caller is the accountant assigned to the specified vault
          /// @param vault The address of the vault
          modifier onlyVaultAccountant(address vault) {
              require(msg.sender == vaultAccountant[vault], Aera__CallerIsNotVaultAccountant());
              _;
          }
          constructor(address initialOwner, Authority initialAuthority) Auth2Step(initialOwner, initialAuthority) { }
          ////////////////////////////////////////////////////////////
          //              Public / External Functions               //
          ////////////////////////////////////////////////////////////
          /// @inheritdoc IBaseFeeCalculator
          function setProtocolFeeRecipient(address feeRecipient) external requiresAuth {
              // Requirements: check that the fee recipient is not the zero address
              require(feeRecipient != address(0), Aera__ZeroAddressProtocolFeeRecipient());
              // Effects: set the protocol fee recipient
              protocolFeeRecipient = feeRecipient;
              // Log new protocol fee recipient
              emit ProtocolFeeRecipientSet(feeRecipient);
          }
          /// @inheritdoc IBaseFeeCalculator
          function setProtocolFees(uint16 tvl, uint16 performance) external requiresAuth {
              // Requirements: check that the fees are less than the maximum allowed
              require(tvl <= MAX_TVL_FEE, Aera__TvlFeeTooHigh());
              require(performance <= MAX_PERFORMANCE_FEE, Aera__PerformanceFeeTooHigh());
              require(protocolFeeRecipient != address(0), Aera__ZeroAddressProtocolFeeRecipient());
              // Effects: set the protocol fees
              protocolFees = Fee({ tvl: tvl, performance: performance });
              // Log new protocol fees
              emit ProtocolFeesSet(tvl, performance);
          }
          /// @inheritdoc IBaseFeeCalculator
          function setVaultAccountant(address vault, address accountant) external requiresVaultAuth(vault) {
              // Effects: update the vault's accountant
              vaultAccountant[vault] = accountant;
              // Log the updated accountant for the vault
              emit VaultAccountantSet(vault, accountant);
          }
          /// @inheritdoc IFeeCalculator
          // solhint-disable-next-line no-empty-blocks
          function registerVault() external virtual { }
          /// @inheritdoc IBaseFeeCalculator
          function setVaultFees(address vault, uint16 tvl, uint16 performance) external requiresVaultAuth(vault) {
              // Requirements: check that the fees are less than the maximum allowed
              require(tvl <= MAX_TVL_FEE, Aera__TvlFeeTooHigh());
              require(performance <= MAX_PERFORMANCE_FEE, Aera__PerformanceFeeTooHigh());
              // Effects: set the vault fees
              VaultAccruals storage vaultAccruals = _vaultAccruals[vault];
              vaultAccruals.fees = Fee({ tvl: tvl, performance: performance });
              // Log new vault fees
              emit VaultFeesSet(vault, tvl, performance);
          }
          /// @inheritdoc IFeeCalculator
          function claimFees(uint256 feeTokenBalance) external virtual returns (uint256, uint256, address) {
              // Effects: hook called before claiming fees
              _beforeClaimFees();
              VaultAccruals storage vaultAccruals = _vaultAccruals[msg.sender];
              uint256 vaultEarnedFees = vaultAccruals.accruedFees;
              uint256 protocolEarnedFees = vaultAccruals.accruedProtocolFees;
              uint256 claimableProtocolFee = Math.min(feeTokenBalance, protocolEarnedFees);
              uint256 claimableVaultFee;
              unchecked {
                  claimableVaultFee = Math.min(feeTokenBalance - claimableProtocolFee, vaultEarnedFees);
              }
              // Effects: update accrued fees
              unchecked {
                  vaultAccruals.accruedProtocolFees = uint112(protocolEarnedFees - claimableProtocolFee);
                  vaultAccruals.accruedFees = uint112(vaultEarnedFees - claimableVaultFee);
              }
              return (claimableVaultFee, claimableProtocolFee, protocolFeeRecipient);
          }
          /// @inheritdoc IFeeCalculator
          function claimProtocolFees(uint256 feeTokenBalance) external virtual returns (uint256, address) {
              // Effects: hook called before claiming protocol fees
              _beforeClaimProtocolFees();
              VaultAccruals storage vaultAccruals = _vaultAccruals[msg.sender];
              uint256 accruedFees = vaultAccruals.accruedProtocolFees;
              uint256 claimableProtocolFee = Math.min(feeTokenBalance, accruedFees);
              // Effects: update accrued protocol fees
              unchecked {
                  vaultAccruals.accruedProtocolFees = uint112(accruedFees - claimableProtocolFee);
              }
              return (claimableProtocolFee, protocolFeeRecipient);
          }
          /// @inheritdoc IFeeCalculator
          // solhint-disable-next-line no-empty-blocks
          function previewFees(address vault, uint256 feeTokenBalance) external view virtual returns (uint256, uint256);
          ////////////////////////////////////////////////////////////
          //              Private / Internal Functions              //
          ////////////////////////////////////////////////////////////
          /// @notice Hook called before claiming fees
          /// @dev Can be overridden by child contracts to add custom logic
          // solhint-disable-next-line no-empty-blocks
          function _beforeClaimFees() internal virtual { }
          /// @notice Hook called before claiming protocol fees
          /// @dev Can be overridden by child contracts to add custom logic
          // solhint-disable-next-line no-empty-blocks
          function _beforeClaimProtocolFees() internal virtual { }
          /// @notice Calculates the TVL fee for a given period
          /// @dev Fee is annualized and prorated for the time period
          /// @param averageValue The average value during the period
          /// @param tvlFee The TVL fee rate in basis points
          /// @param timeDelta The duration of the fee period in seconds
          /// @return The earned TVL fee
          function _calculateTvlFee(uint256 averageValue, uint256 tvlFee, uint256 timeDelta)
              internal
              pure
              returns (uint256)
          {
              unchecked {
                  // safe because averageValue is uint160, tvlFee is uint16, timeDelta is uint32
                  return averageValue * tvlFee * timeDelta / ONE_IN_BPS / SECONDS_PER_YEAR;
              }
          }
          /// @notice Calculates the performance fee for a given period
          /// @param profit The profit during the period
          /// @param feeRate The performance fee rate in basis points
          /// @return The earned performance fee
          function _calculatePerformanceFee(uint256 profit, uint256 feeRate) internal pure returns (uint256) {
              unchecked {
                  // safe because profit is uint128, feeRate is uint16
                  return profit * feeRate / ONE_IN_BPS;
              }
          }
      }
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      ////////////////////////////////////////////////////////////
      //                    Memory & Calldata                   //
      ////////////////////////////////////////////////////////////
      // Size of a word in bytes
      uint256 constant WORD_SIZE = 32;
      // Size of a function selector in bytes
      uint256 constant SELECTOR_SIZE = 4;
      // Minimum valid calldata size (selector + one word = 36)
      uint256 constant MINIMUM_CALLDATA_LENGTH = WORD_SIZE + SELECTOR_SIZE;
      // Offset to skip selector and first word in calldata
      uint256 constant CALLDATA_OFFSET = MINIMUM_CALLDATA_LENGTH;
      // Offset for extracting spender address from approval calldata
      uint256 constant ERC20_SPENDER_OFFSET = 36;
      // Size of an address in bits
      uint256 constant ADDRESS_SIZE_BITS = 160;
      ////////////////////////////////////////////////////////////
      //                     Hooks Constants                    //
      ////////////////////////////////////////////////////////////
      // Mask for a bit indicating whether a hooks has before submit call
      uint256 constant BEFORE_HOOK_MASK = 1;
      // Mask for a bit indicating whether a hooks has after submit call
      uint256 constant AFTER_HOOK_MASK = 2;
      // Mask for a bit indicating whether a hooks exists
      uint256 constant HOOKS_FLAG_MASK = 0x80;
      // Mask for 7 bits indicating the number of configurable hooks offsets
      uint256 constant CONFIGURABLE_HOOKS_LENGTH_MASK = 0x7F;
      ////////////////////////////////////////////////////////////
      //                     Bit Operations                     //
      ////////////////////////////////////////////////////////////
      // Mask for extracting 8-bit values
      uint256 constant MASK_8_BIT = 0xff;
      // Mask for extracting 16-bit values
      uint256 constant MASK_16_BIT = 0xffff;
      ////////////////////////////////////////////////////////////
      //                    Pipeline Constants                  //
      ////////////////////////////////////////////////////////////
      // Bit offset for results index in packed clipboard data
      uint256 constant RESULTS_INDEX_OFFSET = 24;
      // Bit offset for copy word position in packed clipboard data
      uint256 constant COPY_WORD_OFFSET = 16;
      ////////////////////////////////////////////////////////////
      //                   Extractor Constants                  //
      ////////////////////////////////////////////////////////////
      // Number of bits per extraction offset
      uint256 constant EXTRACT_OFFSET_SIZE_BITS = 16;
      // Number of bits to shift to get the offset (256 - 16)
      uint256 constant EXTRACTION_OFFSET_SHIFT_BITS = 240;
      /// @dev Maximum number of extraction offsets(16) + 1
      uint256 constant MAX_EXTRACT_OFFSETS_EXCLUSIVE = 17;
      ////////////////////////////////////////////////////////////
      //                   Callback Constants                   //
      ////////////////////////////////////////////////////////////
      // Maximum value for uint16, used to indicate no callback data
      uint16 constant NO_CALLBACK_DATA = type(uint16).max;
      // Offset for selector in callback data
      uint256 constant SELECTOR_OFFSET = 48;
      // Offset for callback data
      uint256 constant CALLBACK_DATA_OFFSET = 160;
      ////////////////////////////////////////////////////////////
      //                   Fee Constants                        //
      ////////////////////////////////////////////////////////////
      // Basis points denominator (100%)
      uint256 constant ONE_IN_BPS = 1e4;
      // Maximum TVL fee
      uint256 constant MAX_TVL_FEE = 2000; // 20%
      // Maximum performance fee
      uint256 constant MAX_PERFORMANCE_FEE = ONE_IN_BPS;
      // Seconds in a year for fee calculations
      uint256 constant SECONDS_PER_YEAR = 365 days;
      // Maximum dispute period
      uint256 constant MAX_DISPUTE_PERIOD = 30 days;
      ////////////////////////////////////////////////////////////
      //                   Unit Price Constants                //
      ////////////////////////////////////////////////////////////
      /// @dev Precision for unit price calculations (18 decimals)
      uint256 constant UNIT_PRICE_PRECISION = 1e18;
      /// @dev One minute in seconds
      uint256 constant ONE_MINUTE = 1 minutes;
      /// @dev One day in seconds
      uint256 constant ONE_DAY = 1 days;
      ////////////////////////////////////////////////////////////
      //                   Provisioner Constants                //
      ////////////////////////////////////////////////////////////
      /// @dev Minimum deposit multiplier 50%
      uint256 constant MIN_DEPOSIT_MULTIPLIER = 5000;
      /// @dev Minimum redeem multiplier 50%
      uint256 constant MIN_REDEEM_MULTIPLIER = 5000;
      /// @dev Deposit/Redeem flag in RequestType enum
      uint256 constant DEPOSIT_REDEEM_FLAG = 1;
      /// @dev Auto/Fixed price flag in RequestType enum
      uint256 constant AUTO_PRICE_FIXED_PRICE_FLAG = 2;
      /// @dev One unit with 18 decimals
      uint256 constant ONE_UNIT = 1e18;
      /// @dev Maximum seconds between request deadline and current timestamp
      uint256 constant MAX_SECONDS_TO_DEADLINE = 365 days;
      /// @dev Upper bound for depositRefundTimeout to prevent indefinite user lockout
      uint256 constant MAX_DEPOSIT_REFUND_TIMEOUT = 30 days;
      ////////////////////////////////////////////////////////////
      //                   Whitelist Constants                  //
      ////////////////////////////////////////////////////////////
      /// @dev Whitelist flag in AddressToUintMap
      uint8 constant IS_WHITELISTED_FLAG = 1;
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      import { IHasNumeraire } from "src/core/interfaces/IHasNumeraire.sol";
      /// @title HasNumeraire
      /// @notice Abstract contract for contracts with an immutable numeraire token to be used for pricing
      abstract contract HasNumeraire is IHasNumeraire {
          ////////////////////////////////////////////////////////////
          //                       Immutables                       //
          ////////////////////////////////////////////////////////////
          /// @notice Address of the numeraire token
          address public immutable NUMERAIRE;
          ////////////////////////////////////////////////////////////
          //                      Constructor                       //
          ////////////////////////////////////////////////////////////
          constructor(address numeraire_) {
              // Requirements: check that the numeraire address is not zero
              require(numeraire_ != address(0), Aera__ZeroAddressNumeraire());
              // Effects: set the numeraire address
              NUMERAIRE = numeraire_;
          }
          ////////////////////////////////////////////////////////////
          //              Internal / Private Functions              //
          ////////////////////////////////////////////////////////////
          /// @notice Get the numeraire address
          /// @return The address of the numeraire token
          function _getNumeraire() internal view virtual returns (address) {
              return NUMERAIRE;
          }
      }
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      import { IERC20 } from "@oz/interfaces/IERC20.sol";
      import { Authority } from "@solmate/auth/Auth.sol";
      import { IFeeCalculator } from "src/core/interfaces/IFeeCalculator.sol";
      import { ISubmitHooks } from "src/core/interfaces/ISubmitHooks.sol";
      import { IWhitelist } from "src/core/interfaces/IWhitelist.sol";
      import { IOracle } from "src/dependencies/oracles/IOracle.sol";
      /// @notice Type of request: deposit/redeem and auto/fixed price
      /// @dev
      /// - The order is chosen so each bit encodes a property:
      ///   - Bit 0: 0 = deposit, 1 = redeem
      ///   - Bit 1: 0 = auto price, 1 = fixed price
      enum RequestType {
          DEPOSIT_AUTO_PRICE, // 00: deposit, auto price
          REDEEM_AUTO_PRICE, // 01: redeem, auto price
          DEPOSIT_FIXED_PRICE, // 10: deposit, fixed price
          REDEEM_FIXED_PRICE // 11: redeem, fixed price
      }
      /// @notice Type of return value: no return, static return, dynamic return
      /// @dev
      /// - 00: no return
      /// - 01: static return - hardcoded return data
      /// - 10: dynamic return - return data is extracted from the results array
      enum ReturnValueType {
          NO_RETURN,
          STATIC_RETURN,
          DYNAMIC_RETURN
      }
      /// @notice Type of hook call: before, after, or none
      enum HookCallType {
          NONE,
          BEFORE,
          AFTER
      }
      /// @notice Operation struct for vault operations
      /// @dev This struct is not used directly in core logic, but included for reference and clarity
      ///      It illustrates the full structure of an operation without storage packing
      struct Operation {
          /// @notice Target contract address to call
          address target;
          /// @notice Calldata for the target contract
          bytes data;
          /// @notice Array of clipboard operations for copying return data
          Clipboard[] clipboards;
          /// @notice Whether to perform a static call
          bool isStaticCall;
          /// @notice Callback data for post-operation processing
          CallbackData callbackData;
          /// @notice Address of the hooks contract
          address hooks;
          /// @notice Array of offsets for extracting calldata
          uint16[] configurableHooksOffsets;
          /// @notice Merkle proof for operation verification
          bytes32[] proof;
          /// @notice ETH value to send with the call
          uint256 value;
      }
      /// @notice Operation execution context data
      /// @dev Used to avoid stack too deep in BaseVault._executeSubmit function
      struct OperationContext {
          /// @notice Address of the target contract to call
          address target;
          /// @notice Function selector extracted from calldata
          bytes4 selector;
          /// @notice Callback data packed
          uint208 callbackData;
          /// @notice ETH value to send with the call
          uint256 value;
          /// @notice Address of the operation-specific hooks contract
          address operationHooks;
          /// @notice Offset of the calldata extraction offsets packed in uint256
          uint256 configurableOperationHooks;
      }
      /// @notice Struct for payable operations
      struct OperationPayable {
          /// @notice Target contract address
          address target;
          /// @notice Calldata for the target contract
          bytes data;
          /// @notice ETH value to send with the call
          uint256 value;
      }
      /// @notice Struct for token approvals
      struct Approval {
          /// @notice Token address to approve
          address token;
          /// @notice Address to approve spending for
          address spender;
      }
      /// @notice Struct for token amounts
      struct TokenAmount {
          /// @notice ERC20 token address
          IERC20 token;
          /// @notice Amount of tokens
          uint256 amount;
      }
      /// @notice Struct for clipboard operations
      struct Clipboard {
          /// @notice Index of the result to copy from
          uint8 resultIndex;
          /// @notice Which word to copy from the result
          uint8 copyWord;
          /// @notice Offset to paste the copied data
          uint16 pasteOffset;
      }
      /// @notice Struct for callback data
      struct CallbackData {
          /// @notice Address allowed to execute the callback
          address caller;
          /// @notice Function selector for the callback
          bytes4 selector;
          /// @notice Offset in calldata for the callback
          uint16 calldataOffset;
      }
      /// @notice Vault parameters for vault deployment
      struct BaseVaultParameters {
          /// @notice Initial owner address
          address owner;
          /// @notice Initial authority address
          Authority authority;
          /// @notice Submit hooks address
          ISubmitHooks submitHooks;
          /// @notice Whitelist contract address
          IWhitelist whitelist;
      }
      /// @notice Parameters for fee vault deployment
      struct FeeVaultParameters {
          /// @notice The fee calculator address
          IFeeCalculator feeCalculator;
          /// @notice The fee token address
          IERC20 feeToken;
          /// @notice The fee recipient address
          address feeRecipient;
      }
      /// @notice Parameters for ERC20 deployment
      struct ERC20Parameters {
          /// @notice ERC20 token name
          string name;
          /// @notice ERC20 token symbol
          string symbol;
      }
      /// @notice Fee structure for TVL and performance fees
      /// @dev All fees are in basis points (1/10000)
      struct Fee {
          /// @notice TVL fee in basis points
          uint16 tvl;
          /// @notice Performance fee in basis points
          uint16 performance;
      }
      /// @notice Tracks fee configuration and accrued fees for a vault
      struct VaultAccruals {
          /// @notice Current fee rates for the vault
          Fee fees;
          /// @notice Accrued fees for the vault fee recipient
          uint112 accruedFees;
          /// @notice Total protocol fees accrued but not claimed
          uint112 accruedProtocolFees;
      }
      /// @notice Complete state of a vault's fee configuration and accruals
      struct VaultSnapshot {
          /// @notice Timestamp of last fee accrual
          uint32 lastFeeAccrual;
          /// @notice Timestamp when snapshot was taken
          uint32 timestamp;
          /// @notice Timestamp when snapshot is finalized after dispute period
          uint32 finalizedAt;
          /// @notice Average value of vault assets during snapshot period
          uint160 averageValue;
          /// @notice Highest profit achieved during snapshot period
          uint128 highestProfit;
          /// @notice Highest profit achieved in previous periods
          uint128 lastHighestProfit;
      }
      /// @notice Struct for target and calldata
      struct TargetCalldata {
          /// @notice Target contract address
          address target;
          /// @notice Calldata for the target contract
          bytes data;
      }
      /// @notice Vault price information and configuration
      struct VaultPriceState {
          /// @notice Whether vault price updates are paused
          bool paused;
          /// @notice Maximum age of price data in seconds before it is considered stale
          uint8 maxPriceAge;
          /// @notice Minimum time between price updates in minutes
          uint16 minUpdateIntervalMinutes;
          /// @notice Maximum allowed price increase ratio in basis points
          uint16 maxPriceToleranceRatio;
          /// @notice Minimum allowed price decrease ratio in basis points
          uint16 minPriceToleranceRatio;
          /// @notice Maximum allowed delay in price updates in days
          uint8 maxUpdateDelayDays;
          /// @notice Timestamp of last price update
          uint32 timestamp;
          /// @notice Seconds between last fee accrual and last price update
          uint24 accrualLag;
          /// @notice Current unit price
          uint128 unitPrice;
          /// @notice Highest historical unit price
          uint128 highestPrice;
          /// @notice Total supply at last price update
          uint128 lastTotalSupply;
      }
      /// @notice Token configuration for deposits and redemptions
      struct TokenDetails {
          /// @notice Whether async deposits are enabled
          bool asyncDepositEnabled;
          /// @notice Whether async redemptions are enabled
          bool asyncRedeemEnabled;
          /// @notice Whether sync deposits are enabled
          bool syncDepositEnabled;
          /// @notice Premium multiplier applied to deposits in basis points (9999 = 0.1% premium)
          uint16 depositMultiplier;
          /// @notice Premium multiplier applied to redemptions in basis points (9999 = 0.1% premium)
          uint16 redeemMultiplier;
      }
      /// @notice Request parameters for deposits and redemptions
      /// @dev
      /// - For deposits:
      ///   - units: minimum units the user wants to receive (minUnitsOut)
      ///   - tokens: amount of tokens the user is providing (tokensIn)
      /// - For redemptions:
      ///   - units: amount of units the user is redeeming (unitsIn)
      ///   - tokens: minimum tokens the user wants to receive (minTokensOut)
      struct Request {
          /// @notice Request type(deposit/redeem + auto/fixed price)
          RequestType requestType;
          /// @notice User address making the request
          address user;
          /// @notice Amount of vault units
          uint256 units;
          /// @notice Amount of underlying tokens
          uint256 tokens;
          /// @notice Tip paid to solver, always in tokens
          uint256 solverTip;
          /// @notice Timestamp after which request expires
          uint256 deadline;
          /// @notice Maximum age of price data allowed
          uint256 maxPriceAge;
      }
      /// @notice Oracle data for a base/quote pair, including current, pending, and status flags
      struct OracleData {
          /// @notice True if an oracle update is scheduled
          bool isScheduledForUpdate;
          /// @notice True if the current oracle is disabled
          bool isDisabled;
          /// @notice The currently active oracle
          IOracle oracle;
          /// @notice The pending oracle to be activated after delay
          IOracle pendingOracle;
          /// @notice Timestamp at which the pending oracle can be committed
          uint32 commitTimestamp;
      }
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      import { IERC20 } from "@oz/token/ERC20/IERC20.sol";
      import { Math } from "@oz/utils/math/Math.sol";
      import { VaultAccruals, VaultPriceState } from "src/core/Types.sol";
      /// @title IPriceAndFeeCalculator
      /// @notice Interface for the unit price provider
      interface IPriceAndFeeCalculator {
          ////////////////////////////////////////////////////////////
          //                         Events                         //
          ////////////////////////////////////////////////////////////
          /// @notice Emitted when thresholds are set for a vault
          /// @param vault The address of the vault
          /// @param minPriceToleranceRatio Minimum ratio (of a price decrease) in basis points
          /// @param maxPriceToleranceRatio Maximum ratio (of a price increase) in basis points
          /// @param minUpdateIntervalMinutes The minimum interval between updates in minutes
          /// @param maxPriceAge Max delay between when a vault was priced and when the price is acceptable
          event ThresholdsSet(
              address indexed vault,
              uint16 minPriceToleranceRatio,
              uint16 maxPriceToleranceRatio,
              uint16 minUpdateIntervalMinutes,
              uint8 maxPriceAge
          );
          /// @notice Emitted when a vault's unit price is updated
          /// @param vault The address of the vault
          /// @param price The new unit price
          /// @param timestamp The timestamp when the price was updated
          event UnitPriceUpdated(address indexed vault, uint128 price, uint32 timestamp);
          /// @notice Emitted when a vault's paused state is changed
          /// @param vault The address of the vault
          /// @param paused Whether the vault is paused
          event VaultPausedChanged(address indexed vault, bool paused);
          /// @notice Emitted when a vault's highest price is reset
          /// @param vault The address of the vault
          /// @param newHighestPrice The new highest price
          event HighestPriceReset(address indexed vault, uint128 newHighestPrice);
          ////////////////////////////////////////////////////////////
          //                         Errors                         //
          ////////////////////////////////////////////////////////////
          error Aera__StalePrice();
          error Aera__TimestampMustBeAfterLastUpdate();
          error Aera__TimestampCantBeInFuture();
          error Aera__ZeroAddressOracleRegistry();
          error Aera__InvalidMaxPriceToleranceRatio();
          error Aera__InvalidMinPriceToleranceRatio();
          error Aera__InvalidMaxPriceAge();
          error Aera__InvalidMaxUpdateDelayDays();
          error Aera__ThresholdNotSet();
          error Aera__VaultPaused();
          error Aera__VaultNotPaused();
          error Aera__UnitPriceMismatch();
          error Aera__TimestampMismatch();
          error Aera__VaultAlreadyInitialized();
          error Aera__VaultNotInitialized();
          error Aera__InvalidPrice();
          error Aera__CurrentPriceAboveHighestPrice();
          ////////////////////////////////////////////////////////////
          //                       Functions                        //
          ////////////////////////////////////////////////////////////
          /// @notice Set the initial price state for the vault
          /// @param vault Address of the vault
          /// @param price New unit price
          /// @param timestamp Timestamp when the price was measured
          function setInitialPrice(address vault, uint128 price, uint32 timestamp) external;
          /// @notice Set vault thresholds
          /// @param vault Address of the vault
          /// @param minPriceToleranceRatio Minimum ratio (of a price decrease) in basis points
          /// @param maxPriceToleranceRatio Maximum ratio (of a price increase) in basis points
          /// @param minUpdateIntervalMinutes The minimum interval between updates in minutes
          /// @param maxPriceAge Max delay between when a vault was priced and when the price is acceptable
          /// @param maxUpdateDelayDays Max delay between two price updates
          function setThresholds(
              address vault,
              uint16 minPriceToleranceRatio,
              uint16 maxPriceToleranceRatio,
              uint16 minUpdateIntervalMinutes,
              uint8 maxPriceAge,
              uint8 maxUpdateDelayDays
          ) external;
          /// @notice Set the unit price for the vault in numeraire terms
          /// @param vault Address of the vault
          /// @param price New unit price
          /// @param timestamp Timestamp when the price was measured
          function setUnitPrice(address vault, uint128 price, uint32 timestamp) external;
          /// @notice Pause the vault
          /// @param vault Address of the vault
          function pauseVault(address vault) external;
          /// @notice Unpause the vault
          /// @param vault Address of the vault
          /// @param price Expected price of the last update
          /// @param timestamp Expected timestamp of the last update
          /// @dev MUST revert if price or timestamp don't exactly match last update
          function unpauseVault(address vault, uint128 price, uint32 timestamp) external;
          /// @notice Resets the highest price for a vault to the current price
          /// @param vault Address of the vault
          function resetHighestPrice(address vault) external;
          /// @notice Convert units to token amount
          /// @param vault Address of the vault
          /// @param token Address of the token
          /// @param unitsAmount Amount of units
          /// @return tokenAmount Amount of tokens
          function convertUnitsToToken(address vault, IERC20 token, uint256 unitsAmount)
              external
              view
              returns (uint256 tokenAmount);
          /// @notice Convert units to token amount if vault is not paused
          /// @param vault Address of the vault
          /// @param token Address of the token
          /// @param unitsAmount Amount of units
          /// @param rounding The rounding mode
          /// @return tokenAmount Amount of tokens
          /// @dev MUST revert if vault is paused
          function convertUnitsToTokenIfActive(address vault, IERC20 token, uint256 unitsAmount, Math.Rounding rounding)
              external
              view
              returns (uint256 tokenAmount);
          /// @notice Convert token amount to units
          /// @param vault Address of the vault
          /// @param token Address of the token
          /// @param tokenAmount Amount of tokens
          /// @return unitsAmount Amount of units
          function convertTokenToUnits(address vault, IERC20 token, uint256 tokenAmount)
              external
              view
              returns (uint256 unitsAmount);
          /// @notice Convert token amount to units if vault is not paused
          /// @param vault Address of the vault
          /// @param token Address of the token
          /// @param tokenAmount Amount of tokens
          /// @param rounding The rounding mode
          /// @return unitsAmount Amount of units
          /// @dev MUST revert if vault is paused
          function convertTokenToUnitsIfActive(address vault, IERC20 token, uint256 tokenAmount, Math.Rounding rounding)
              external
              view
              returns (uint256 unitsAmount);
          /// @notice Convert units to numeraire token amount
          /// @param vault Address of the vault
          /// @param unitsAmount Amount of units
          /// @return numeraireAmount Amount of numeraire
          function convertUnitsToNumeraire(address vault, uint256 unitsAmount)
              external
              view
              returns (uint256 numeraireAmount);
          /// @notice Return the state of the vault
          /// @param vault Address of the vault
          /// @return vaultPriceState The price state of the vault
          /// @return vaultAccruals The accruals state of the vault
          function getVaultState(address vault) external view returns (VaultPriceState memory, VaultAccruals memory);
          /// @notice Returns the age of the last submitted price for a vault
          /// @param vault Address of the vault
          /// @return priceAge The difference between block.timestamp and vault's unit price timestamp
          function getVaultsPriceAge(address vault) external view returns (uint256);
          /// @notice Check if a vault is paused
          /// @param vault The address of the vault
          /// @return True if the vault is paused, false otherwise
          function isVaultPaused(address vault) external view returns (bool);
      }
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      import { OracleData } from "src/core/Types.sol";
      import { IOracle } from "src/dependencies/oracles/IOracle.sol";
      /// @title IOracleRegistry
      /// @notice Interface for an Oracle Registry
      interface IOracleRegistry is IOracle {
          ////////////////////////////////////////////////////////////
          //                         Events                         //
          ////////////////////////////////////////////////////////////
          /// @notice Emitted when an oracle is added
          /// @param base Base asset address
          /// @param quote Quote asset address
          /// @param oracle Added oracle
          event OracleSet(address indexed base, address indexed quote, IOracle indexed oracle);
          /// @notice Emitted when an oracle update is scheduled
          /// @param base Base asset address
          /// @param quote Quote asset address
          /// @param pendingOracle Pending oracle
          /// @param commitTimestamp The timestamp when the oracle data can be commited
          event OracleScheduled(
              address indexed base, address indexed quote, IOracle indexed pendingOracle, uint32 commitTimestamp
          );
          /// @notice Emitted when an oracle update is cancelled
          /// @param base Base asset address
          /// @param quote Quote asset address
          event OracleUpdateCancelled(address indexed base, address indexed quote);
          /// @notice Emitted when an oracle is disabled
          /// @param base Base asset address
          /// @param quote Quote asset address
          /// @param oracle Oracle that is disabled
          event OracleDisabled(address indexed base, address indexed quote, IOracle indexed oracle);
          /// @notice Emitted when a user accepts an oracle update early
          /// @param user Address of the user which accepted the oracle data
          /// @param base Base asset address
          /// @param quote Quote asset address
          /// @param oracle Oracle which was accepted
          event PendingOracleAccepted(address indexed user, address indexed base, address indexed quote, IOracle oracle);
          /// @notice Emitted when an oracle override is removed
          /// @param user Address of the user which removed the oracle override
          /// @param base Base asset address
          /// @param quote Quote asset address
          event OracleOverrideRemoved(address indexed user, address indexed base, address indexed quote);
          error AeraPeriphery__CallerIsNotAuthorized();
          error AeraPeriphery__OracleMismatch();
          error AeraPeriphery__CommitTimestampNotReached();
          error AeraPeriphery__OracleUpdateDelayTooLong();
          error AeraPeriphery__OracleConvertsOneBaseTokenToZeroQuoteTokens(address base, address quote);
          error AeraPeriphery__NoPendingOracleUpdate();
          error AeraPeriphery__OracleIsDisabled(address base, address quote, IOracle oracle);
          error AeraPeriphery__CannotScheduleOracleUpdateForTheSameOracle();
          error AeraPeriphery__OracleUpdateAlreadyScheduled();
          error AeraPeriphery__ZeroAddressOracle();
          error AeraPeriphery__OracleNotSet();
          error AeraPeriphery__OracleAlreadySet();
          error AeraPeriphery__OracleAlreadyDisabled();
          error AeraPeriphery__ZeroAddressOwner();
          ////////////////////////////////////////////////////////////
          //                       Functions                        //
          ////////////////////////////////////////////////////////////
          /// @notice Adds an oracle for the provided base and quote assets
          /// @dev MUST REVERT if not called by the authorized address
          /// @dev MUST REVERT if the oracle is already set
          /// @param base Base asset address
          /// @param quote Quote asset address
          /// @param oracle Oracle to add
          function addOracle(address base, address quote, IOracle oracle) external;
          /// @notice Schedules an oracle update for the base/quote asset pair
          ///         The update process is a two-step process: first, the new oracle data is set using
          ///         this function; second, the update is committed using the commitOracleUpdate function
          /// @dev MUST REVERT if not called by the authorized address
          /// @dev MUST REVERT if the oracle data is already scheduled for an update
          /// @dev MUST REVERT if the oracle data is the same as the current oracle
          /// @param base Base asset address
          /// @param quote Quote asset address
          /// @param oracle Oracle to schedule
          function scheduleOracleUpdate(address base, address quote, IOracle oracle) external;
          /// @notice Commits the oracle update for the base/quote asset pair
          ///         Can be called by anyone after the update process is initiated using `scheduleOracleUpdate`
          ///         and the update delay has passed
          /// @dev MUST REVERT if the update is not initiated
          /// @dev MUST REVERT if the update delay has not passed
          /// @param base Base asset address
          /// @param quote Quote asset address
          function commitOracleUpdate(address base, address quote) external;
          /// @notice Cancels the scheduled update for the base/quote asset pair
          /// @dev MUST REVERT if not called by the authorized address
          /// @dev MUST REVERT if the update is not initiated
          /// @param base Base asset address
          /// @param quote Quote asset address
          function cancelScheduledOracleUpdate(address base, address quote) external;
          /// @notice Disables the oracle for the base/quote asset pair
          /// @dev Performs a soft delete to forbid calling `addOracle` with the same base and quote assets and
          ///      avoid front-running attack
          /// @dev MUST REVERT if not called by the authorized address
          /// @dev MUST REVERT if the oracle data is not set
          /// @dev MUST REVERT if the oracle data is already disabled
          /// @param base Base asset address
          /// @param quote Quote asset address
          /// @param oracle Oracle that is to be disabled
          function disableOracle(address base, address quote, IOracle oracle) external;
          /// @notice Allows a user to accept the pending oracle for a given base/quote pair during the delay period
          ///         Can be called by the user to use the new oracle early
          /// @dev MUST REVERT if the caller is not the user or its owner
          /// @dev MUST REVERT if the oracle is not set
          /// @dev MUST REVERT if current pending oracle doesn't match the oracle to be accepted
          /// @param base Base asset address
          /// @param quote Quote asset address
          /// @param user Vault that is accepting the pending oracle
          /// @param oracle Oracle that is to be accepted
          function acceptPendingOracle(address base, address quote, address user, IOracle oracle) external;
          /// @notice Allows a user to remove the oracle override for a given base/quote pair
          /// @dev MUST REVERT if the caller is not the user or its owner
          /// @param base Base asset address
          /// @param quote Quote asset address
          function removeOracleOverride(address base, address quote, address user) external;
          /// @notice Returns the value of the base asset in terms of the quote asset with using the provided
          /// oracle data for the provided user (respects user-specific overrides)
          /// @param baseAmount Amount of base asset
          /// @param base Base asset address
          /// @param quote Quote asset address
          /// @param user Vault address
          /// @return value of the base asset in terms of the quote asset
          function getQuoteForUser(uint256 baseAmount, address base, address quote, address user)
              external
              view
              returns (uint256);
          /// @notice Return oracle metadata for base/quote
          /// @param base Base asset address
          /// @param quote Quote asset address
          /// @return data Oracle data
          function getOracleData(address base, address quote) external view returns (OracleData memory data);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.20;
      /**
       * @dev Helper library for emitting standardized panic codes.
       *
       * ```solidity
       * contract Example {
       *      using Panic for uint256;
       *
       *      // Use any of the declared internal constants
       *      function foo() { Panic.GENERIC.panic(); }
       *
       *      // Alternatively
       *      function foo() { Panic.panic(Panic.GENERIC); }
       * }
       * ```
       *
       * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].
       *
       * _Available since v5.1._
       */
      // slither-disable-next-line unused-state
      library Panic {
          /// @dev generic / unspecified error
          uint256 internal constant GENERIC = 0x00;
          /// @dev used by the assert() builtin
          uint256 internal constant ASSERT = 0x01;
          /// @dev arithmetic underflow or overflow
          uint256 internal constant UNDER_OVERFLOW = 0x11;
          /// @dev division or modulo by zero
          uint256 internal constant DIVISION_BY_ZERO = 0x12;
          /// @dev enum conversion error
          uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;
          /// @dev invalid encoding in storage
          uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;
          /// @dev empty array pop
          uint256 internal constant EMPTY_ARRAY_POP = 0x31;
          /// @dev array out of bounds access
          uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;
          /// @dev resource error (too large allocation or too large array)
          uint256 internal constant RESOURCE_ERROR = 0x41;
          /// @dev calling invalid internal function
          uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;
          /// @dev Reverts with a panic code. Recommended to use with
          /// the internal constants with predefined codes.
          function panic(uint256 code) internal pure {
              assembly ("memory-safe") {
                  mstore(0x00, 0x4e487b71)
                  mstore(0x20, code)
                  revert(0x1c, 0x24)
              }
          }
      }
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      import { Auth, Authority } from "@solmate/auth/Auth.sol";
      import { IAuth2Step } from "src/core/interfaces/IAuth2Step.sol";
      /// @title Auth2Step
      /// @notice An extension of Auth.sol that supports two-step ownership transfer
      contract Auth2Step is IAuth2Step, Auth {
          ////////////////////////////////////////////////////////////
          //                        Storage                         //
          ////////////////////////////////////////////////////////////
          /// @notice Address of the pending owner
          address public pendingOwner;
          ////////////////////////////////////////////////////////////
          //                       Modifiers                        //
          ////////////////////////////////////////////////////////////
          modifier onlyOwner() virtual {
              require(msg.sender == owner, Aera__Unauthorized());
              _;
          }
          constructor(address newOwner_, Authority authority_) Auth(newOwner_, authority_) { }
          ////////////////////////////////////////////////////////////
          //              Public / External Functions               //
          ////////////////////////////////////////////////////////////
          /// @inheritdoc IAuth2Step
          function acceptOwnership() external virtual override {
              address pendingOwner_ = pendingOwner;
              // Requirements: the caller must be the pending owner
              require(msg.sender == pendingOwner_, Aera__Unauthorized());
              // Effects: set the owner to the pending owner and delete the pending owner
              owner = pendingOwner_;
              delete pendingOwner;
              // Log the ownership transfer
              emit OwnershipTransferred(msg.sender, pendingOwner_);
          }
          /// @notice Start the ownership transfer of the contract to a new account
          /// @param newOwner Address to transfer ownership to
          /// @dev Replaces the pending transfer if there is one
          /// @dev Overrides the `Auth` contract's `transferOwnership` function
          /// @dev Zero check is not needed because pendingOwner can always be overwritten
          function transferOwnership(address newOwner) public virtual override onlyOwner {
              // Effects: set the pending owner
              //slither-disable-next-line missing-zero-check
              pendingOwner = newOwner;
              // Log the ownership transfer start
              emit OwnershipTransferStarted(owner, newOwner);
          }
      }
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      import { Auth } from "@solmate/auth/Auth.sol";
      /// @title VaultAuth
      /// @notice Abstract contract that provides authorization check for vault operations
      /// @dev Used by contracts that need to verify if a caller has permission to perform vault-specific actions. The
      /// authorization can come from either being the vault owner or having explicit permission through the vault's authority
      abstract contract VaultAuth {
          ////////////////////////////////////////////////////////////
          //                        Errors                          //
          ////////////////////////////////////////////////////////////
          error Aera__CallerIsNotAuthorized();
          ////////////////////////////////////////////////////////////
          //                       Modifiers                        //
          ////////////////////////////////////////////////////////////
          modifier requiresVaultAuth(address vault) {
              // Requirements: check that the caller is either the vault owner or has permission to call the function
              require(
                  msg.sender == Auth(vault).owner() || Auth(vault).authority().canCall(msg.sender, address(this), msg.sig),
                  Aera__CallerIsNotAuthorized()
              );
              _;
          }
      }
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      /// @title IBaseFeeCalculator
      /// @notice Base interface for a contract that calculates TVL and performance fees for a vault and protocol
      interface IBaseFeeCalculator {
          ////////////////////////////////////////////////////////////
          //                         Events                         //
          ////////////////////////////////////////////////////////////
          /// @notice Emitted when a vault's fees are updated
          /// @param vault The address of the vault
          /// @param tvlFee The new TVL fee rate in basis points
          /// @param performanceFee The new performance fee rate in basis points
          event VaultFeesSet(address indexed vault, uint16 tvlFee, uint16 performanceFee);
          /// @notice Emitted when the protocol fee recipient is updated
          /// @param feeRecipient The address of the protocol fee recipient
          event ProtocolFeeRecipientSet(address indexed feeRecipient);
          /// @notice Emitted when protocol fees are updated
          /// @param tvlFee The new protocol TVL fee rate in basis points
          /// @param performanceFee The new protocol performance fee rate in basis points
          event ProtocolFeesSet(uint16 tvlFee, uint16 performanceFee);
          /// @notice Emitted when the accountant for a vault is updated
          /// @param vault The address of the vault whose accountant is being updated
          /// @param accountant The address of the new accountant assigned to the vault
          event VaultAccountantSet(address vault, address accountant);
          ////////////////////////////////////////////////////////////
          //                         Errors                         //
          ////////////////////////////////////////////////////////////
          /// @notice Thrown when attempting to set an TVL fee higher than the maximum allowed
          error Aera__TvlFeeTooHigh();
          /// @notice Thrown when attempting to set a performance fee higher than the maximum allowed
          error Aera__PerformanceFeeTooHigh();
          /// @notice Thrown when attempting to set a protocol fee recipient to the zero address
          error Aera__ZeroAddressProtocolFeeRecipient();
          /// @notice Thrown when attempting to set vault fees for a vault that is not owned by the caller
          error Aera__CallerIsNotVaultOwner();
          /// @notice Thrown when attempting to perform an action on a vault by someone who is not its assigned accountant
          error Aera__CallerIsNotVaultAccountant();
          /// @notice Thrown during a vault is not registered and action requires a registered vault
          error Aera__VaultNotRegistered();
          ////////////////////////////////////////////////////////////
          //                       Functions                        //
          ////////////////////////////////////////////////////////////
          /// @notice Set the protocol fee recipient
          /// @param feeRecipient The address of the protocol fee recipient
          function setProtocolFeeRecipient(address feeRecipient) external;
          /// @notice Set the protocol fee rates
          /// @param tvl The TVL fee rate in basis points
          /// @param performance The performance fee rate in basis points
          function setProtocolFees(uint16 tvl, uint16 performance) external;
          /// @notice Set the vault-specific fee rates
          /// @param vault The address of the vault
          /// @param tvl The TVL fee rate in basis points
          /// @param performance The performance fee rate in basis points
          function setVaultFees(address vault, uint16 tvl, uint16 performance) external;
          /// @notice Set the accountant for a vault
          /// @param vault The address of the vault
          /// @param accountant The address of the new accountant
          function setVaultAccountant(address vault, address accountant) external;
      }
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      /// @title IFeeCalculator
      /// @notice Interface for a contract that calculates fees for a vault and protocol
      interface IFeeCalculator {
          ////////////////////////////////////////////////////////////
          //                         Events                         //
          ////////////////////////////////////////////////////////////
          /// @notice Emitted when a new vault is registered
          /// @param vault The address of the registered vault
          event VaultRegistered(address indexed vault);
          ////////////////////////////////////////////////////////////
          //                         Errors                         //
          ////////////////////////////////////////////////////////////
          /// @notice Thrown when attempting to register an already registered vault
          error Aera__VaultAlreadyRegistered();
          ////////////////////////////////////////////////////////////
          //                       Functions                        //
          ////////////////////////////////////////////////////////////
          /// @notice Register a new vault with the fee calculator
          function registerVault() external;
          /// @notice Process a fee claim for a specific vault
          /// @param feeTokenBalance Available fee token balance to distribute
          /// @return earnedFees The amount of fees to be claimed by the fee recipient
          /// @return protocolEarnedFees The amount of protocol fees to be claimed by the protocol
          /// @return protocolFeeRecipient The address of the protocol fee recipient
          /// @dev Expected to be called by the vault only when claiming fees
          /// Only accrues fees and updates stored values; does not transfer tokens
          /// Caller must perform the actual transfers to avoid permanent fee loss
          function claimFees(uint256 feeTokenBalance) external returns (uint256, uint256, address);
          /// @notice Process a protocol fee claim for a vault
          /// @param feeTokenBalance Available fee token balance to distribute
          /// @return accruedFees The amount of protocol fees claimed
          /// @return protocolFeeRecipient The address of the protocol fee recipient
          /// @dev Expected to be called by the vault only when claiming protocol fees
          /// Only accrues protocol fees and updates stored values; does not transfer tokens
          /// Caller must perform the actual transfers to avoid permanent protocol fee loss
          function claimProtocolFees(uint256 feeTokenBalance) external returns (uint256, address);
          /// @notice Returns the current claimable fees for the given vault, as if a claim was made now
          /// @param vault The address of the vault to preview fees for
          /// @param feeTokenBalance Available fee token balance to distribute
          /// If set to `type(uint256).max`, the function returns all accrued fees
          /// If set to an actual balance, the result is capped to that claimable amount
          /// @return vaultFees The amount of claimable fees for the vault
          /// @return protocolFees The amount of claimable protocol fees
          function previewFees(address vault, uint256 feeTokenBalance)
              external
              view
              returns (uint256 vaultFees, uint256 protocolFees);
          /// @notice Returns the address that receives protocol fees
          /// @return The address that receives the protocol fees
          function protocolFeeRecipient() external view returns (address);
      }
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      /// @title IHasNumeraire
      /// @notice Interface for a contract with a numeraire token
      interface IHasNumeraire {
          ////////////////////////////////////////////////////////////
          //                         Errors                         //
          ////////////////////////////////////////////////////////////
          error Aera__ZeroAddressNumeraire();
          ////////////////////////////////////////////////////////////
          //                       Functions                        //
          ////////////////////////////////////////////////////////////
          /// @notice Get the vault's numeraire token
          /// @return The address of the numeraire token
          // solhint-disable-next-line func-name-mixedcase
          function NUMERAIRE() external view returns (address);
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
      pragma solidity ^0.8.20;
      import {IERC20} from "../token/ERC20/IERC20.sol";
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      /// @title ISubmitHooks
      /// @notice Interface for hooks that execute before and after submit calls
      interface ISubmitHooks {
          ////////////////////////////////////////////////////////////
          //                       Functions                        //
          ////////////////////////////////////////////////////////////
          /// @notice Called before a submit
          /// @param data Encoded data of the submit
          /// @param guardian Address of the guardian that submitted
          function beforeSubmit(bytes memory data, address guardian) external;
          /// @notice Called after a submit
          /// @param data Encoded data of the submit
          /// @param guardian Address of the guardian that submitted
          function afterSubmit(bytes memory data, address guardian) external;
      }
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      interface IWhitelist {
          ////////////////////////////////////////////////////////////
          //                        Events                          //
          ////////////////////////////////////////////////////////////
          event WhitelistSet(address indexed addr, bool isAddressWhitelisted);
          ////////////////////////////////////////////////////////////
          //                       Functions                        //
          ////////////////////////////////////////////////////////////
          /// @notice Set the address whitelisted status
          /// @param addr The address to add/remove from the whitelist
          /// @param isAddressWhitelisted Whether address should be whitelisted going forward
          function setWhitelisted(address addr, bool isAddressWhitelisted) external;
          /// @notice Checks if the address is whitelisted
          /// @param addr The address to check
          /// @return True if the addr is whitelisted, false otherwise
          function isWhitelisted(address addr) external view returns (bool);
          /// @notice Get all whitelisted addresses
          /// @return An array of all whitelisted addresses
          function getAllWhitelisted() external view returns (address[] memory);
      }
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      /// @dev Implements the spec at https://eips.ethereum.org/EIPS/eip-7726
      interface IOracle {
          ////////////////////////////////////////////////////////////
          //                       Functions                        //
          ////////////////////////////////////////////////////////////
          /// @notice Returns the value of `baseAmount` of `base` in `quote` terms
          /// @dev MUST round down towards 0
          /// MUST revert with `OracleUnsupportedPair` if not capable to provide data for the specified `base`
          /// and `quote` pair
          /// MUST revert with `OracleUntrustedData` if not capable to provide data within a degree of
          /// confidence publicly specified
          /// @param baseAmount The amount of `base` to convert
          /// @param base The asset that the user needs to know the value for
          /// @param quote The asset in which the user needs to value the base
          /// @return quoteAmount The value of `baseAmount` of `base` in `quote` terms
          function getQuote(uint256 baseAmount, address base, address quote) external view returns (uint256 quoteAmount);
      }
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity 0.8.29;
      /// @title IAuth2Step
      /// @notice Interface for the Auth2Step contract
      interface IAuth2Step {
          ////////////////////////////////////////////////////////////
          //                         Events                         //
          ////////////////////////////////////////////////////////////
          /// @notice Emitted when ownership transfer is initiated
          event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
          ////////////////////////////////////////////////////////////
          //                         Errors                         //
          ////////////////////////////////////////////////////////////
          error Aera__ZeroAddressAuthority();
          error Aera__Unauthorized();
          ////////////////////////////////////////////////////////////
          //                       Functions                        //
          ////////////////////////////////////////////////////////////
          /// @notice Accept ownership transfer
          function acceptOwnership() external;
      }