ETH Price: $1,802.90 (-0.20%)

Transaction Decoder

Block:
22262800 at Apr-13-2025 09:39:59 PM +UTC
Transaction Fee:
0.000163562390137996 ETH $0.29
Gas Used:
43,084 Gas / 3.796360369 Gwei

Emitted Events:

397 Fyde.ProtocolAumUpdated( 168901548592737540104242 )

Account State Difference:

  Address   Before After State Difference Code
0x24e38D80...b7c63A09F
0.052588320194590018 Eth
Nonce: 1746
0.052424757804452022 Eth
Nonce: 1747
0.000163562390137996
(Titan Builder)
5.224833279721140865 Eth5.224962531721140865 Eth0.000129252
0x87Cc45fF...7eC7df2Ee

Execution Trace

RelayerV2.updateProtocolAUM( nAum=168901548592737540104242 )
updateProtocolAUM[RelayerV2 (ln:366)]
File 1 of 2: RelayerV2
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
// Core
import {QuarantineList} from "./core/QuarantineList.sol";
// Structs
import {UserRequest, RequestData, ProcessParam, AssetInfo} from "./core/Structs.sol";
// Utils
import {Ownable} from "./utils/Ownable.sol";
import {PercentageMath} from "./utils/PercentageMath.sol";
import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
// Interfaces
import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import {IFyde} from "./interfaces/IFyde.sol";
import {IGovernanceModule} from "./interfaces/IGovernanceModule.sol";
import {IOracle} from "./interfaces/IOracle.sol";
import {ITaxModule} from "./interfaces/ITaxModule.sol";
///@title RelayerV2
///@notice The relayer is the entry point contract for users to interact with the protocol.
///        The relayer is monitored by an off-chain keeper that will update the protocol AUM.
contract RelayerV2 is QuarantineList {
  using SafeERC20 for IERC20;
  /*//////////////////////////////////////////////////////////////
                                 STORAGE
    //////////////////////////////////////////////////////////////*/
  ///@notice Fyde contract
  IFyde public fyde;
  ///@notice OracleModule contract
  IOracle public oracleModule;
  //@notice GovernanceModule contract
  IGovernanceModule public immutable GOVERNANCE_MODULE;
  //@notice calculates the tax for protocol actions
  ITaxModule public taxModule;
  ///@dev Only used for tracking events offchain
  uint32 public nonce;
  ///@notice Threshold of deviation for updating AUM
  uint16 public deviationThreshold;
  ///@notice State of the protocol
  bool public paused;
  //@notice Swap state
  bool public swapPaused;
  /*//////////////////////////////////////////////////////////////
                                 ERROR
    //////////////////////////////////////////////////////////////*/
  error ValueOutOfBounds();
  error ActionPaused();
  error SlippageExceed();
  error SwapDisabled(address asset);
  error AssetNotAllowedInGovernancePool(address asset);
  error DuplicatesAssets();
  /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/
  event Pause(uint256 timestamp);
  event Unpause(uint256 timestamp);
  event Deposit(uint32 requestId, RequestData request);
  event Withdraw(uint32 requestId, RequestData request);
  event Swap(uint32 requestId, RequestData request);
  /*//////////////////////////////////////////////////////////////
                            CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/
  constructor(address _oracleModule, address _govModule, uint8 _deviationThreshold)
    Ownable(msg.sender)
  {
    oracleModule = IOracle(_oracleModule);
    GOVERNANCE_MODULE = IGovernanceModule(_govModule);
    updateDeviationThreshold(_deviationThreshold);
  }
  /*//////////////////////////////////////////////////////////////
                                GUARD
    //////////////////////////////////////////////////////////////*/
  ///@notice Pause the protocol
  function pauseProtocol() public onlyGuard {
    paused = true;
    emit Pause(block.timestamp);
  }
  ///@notice Pause the swaps
  function pauseSwap() external onlyGuard {
    swapPaused = true;
    emit Pause(block.timestamp);
  }
  /*//////////////////////////////////////////////////////////////
                                 OWNER
    //////////////////////////////////////////////////////////////*/
  ///@notice sets the addres of fyde contract
  ///@param _fyde address of fyde
  function setFyde(address _fyde) external onlyOwner {
    fyde = IFyde(_fyde);
  }
  ///@notice Set the oracle module
  function setOracleModule(address _oracleModule) external onlyOwner {
    oracleModule = IOracle(_oracleModule);
  }
  ///@notice Set the tax module
  function setTaxModule(address _taxModule) external onlyOwner {
    taxModule = ITaxModule(_taxModule);
  }
  ///@notice Change the deviation threshold
  ///@dev 50 = 0.5 % of deviation
  function updateDeviationThreshold(uint16 _threshold) public onlyOwner {
    // We bound the threshold between 0.1 % to 10%
    if (_threshold < 10 || _threshold > 1000) revert ValueOutOfBounds();
    deviationThreshold = _threshold;
  }
  ///@notice Approve Fyde to transfer token from relayer, should be called once per asset
  function approveFyde(address[] calldata _assets) external onlyOwner {
    for (uint256 i; i < _assets.length; ++i) {
      IERC20(_assets[i]).safeApprove(address(fyde), type(uint256).max);
    }
  }
  ///@notice Collect and send token fees (from tax fees) to an external address
  ///@param _asset Address to send fees to
  ///@param _recipient Address to send fees to
  ///@param _amount Amount to send
  function collectFees(address _asset, address _recipient, uint256 _amount) external onlyOwner {
    IERC20(_asset).safeTransfer(_recipient, _amount);
  }
  ///@notice Unpause the protocol
  function unpauseProtocol() external onlyOwner {
    paused = false;
    emit Unpause(block.timestamp);
  }
  ///@notice Unpause the swaps
  function unpauseSwap() external onlyOwner {
    swapPaused = false;
    emit Unpause(block.timestamp);
  }
  /*//////////////////////////////////////////////////////////////
                            EXT USER ENTRY POINT
    //////////////////////////////////////////////////////////////*/
  ///@notice Entry function for depositing, can be a standard deposit or a governance
  /// deposit
  ///@param _userRequest struct containing data
  ///@param _keepGovRights If true make a governance
  ///@param _minTRSYExpected Slippage parameter ensuring minimum amout of TRSY to be received
  function deposit(
    UserRequest[] calldata _userRequest,
    bool _keepGovRights,
    uint256 _minTRSYExpected
  ) external whenNotPaused onlyUser {
    address[] memory assetIn = new address[](_userRequest.length);
    uint256[] memory amountIn = new uint256[](_userRequest.length);
    for (uint256 i; i < _userRequest.length; ++i) {
      // Unpack data
      assetIn[i] = _userRequest[i].asset;
      amountIn[i] = _userRequest[i].amount;
    }
    _checkForDuplicates(assetIn);
    if (_keepGovRights) _checkIsAllowedInGov(assetIn);
    RequestData memory req = RequestData({
      id: nonce,
      requestor: address(this),
      assetIn: assetIn,
      amountIn: amountIn,
      assetOut: new address[](0),
      amountOut: new uint256[](0),
      keepGovRights: _keepGovRights,
      slippageChecker: _minTRSYExpected
    });
    nonce++;
    uint256 currentAUM = fyde.getProtocolAUM();
    // Cache prices in oracle (gas savings when fyde reads prices)
    _enableOracleCache(assetIn);
    // get params for tax calculation from tax module
    (ProcessParam[] memory processParam, uint256 sharesToMint,,) =
      taxModule.getProcessParamDeposit(req, currentAUM);
    // Slippage checker
    if (req.slippageChecker > sharesToMint) revert SlippageExceed();
    // Transfer assets to Relayer
    for (uint256 i; i < req.assetIn.length; ++i) {
      IERC20(req.assetIn[i]).safeTransferFrom(msg.sender, address(this), req.amountIn[i]);
    }
    // Deposit
    fyde.processDeposit(currentAUM, req);
    if (_keepGovRights) {
      for (uint256 i; i < processParam.length; ++i) {
        // send staked trsy to the user
        address sTrsy = GOVERNANCE_MODULE.assetToStrsy(req.assetIn[i]);
        uint256 strsyBal = IERC20(sTrsy).balanceOf(address(this));
        uint256 toTransfer =
          strsyBal >= processParam[i].sharesAfterTax ? processParam[i].sharesAfterTax : strsyBal;
        IERC20(sTrsy).transfer(msg.sender, toTransfer);
        // unstake tax amount to get standard trsy
        uint256 taxTrsy = strsyBal - toTransfer;
        if (taxTrsy != 0) GOVERNANCE_MODULE.unstakeGov(taxTrsy, req.assetIn[i]);
      }
    } else {
      // send trsy to user
      IERC20(address(fyde)).transfer(msg.sender, sharesToMint);
    }
    _disableOracleCache();
    emit Deposit(req.id, req);
  }
  ///@notice Entry function for withdrawing
  ///@param _userRequest struct containing data
  ///@param _maxTRSYToPay Slippage parameter ensure maximum amout of TRSY willing to pay
  function withdraw(UserRequest[] calldata _userRequest, uint256 _maxTRSYToPay)
    external
    whenNotPaused
    onlyUser
  {
    address[] memory assetOut = new address[](_userRequest.length);
    uint256[] memory amountOut = new uint256[](_userRequest.length);
    for (uint256 i; i < _userRequest.length; i++) {
      assetOut[i] = _userRequest[i].asset;
      amountOut[i] = _userRequest[i].amount;
    }
    _checkForDuplicates(assetOut);
    RequestData memory req = RequestData({
      id: nonce,
      requestor: address(this),
      assetIn: new address[](0),
      amountIn: new uint256[](0),
      assetOut: assetOut,
      amountOut: amountOut,
      keepGovRights: false,
      slippageChecker: _maxTRSYToPay
    });
    nonce++;
    uint256 currentAUM = fyde.getProtocolAUM();
    _enableOracleCache(assetOut);
    // get params for tax calculation from tax module
    (, uint256 totalSharesToBurn,,,) = taxModule.getProcessParamWithdraw(req, currentAUM);
    if (totalSharesToBurn > req.slippageChecker) revert SlippageExceed();
    // Transfer TRSY to Relayer
    IERC20(address(fyde)).transferFrom(msg.sender, address(this), totalSharesToBurn);
    // Withdraw
    fyde.processWithdraw(currentAUM, req);
    // Transfer assets to user
    for (uint256 i; i < req.assetOut.length; ++i) {
      IERC20(req.assetOut[i]).safeTransfer(msg.sender, req.amountOut[i]);
    }
    _disableOracleCache();
    emit Withdraw(req.id, req);
  }
  ///@notice Function used by user to make a (single-token) withdrawal from their governance proxy
  ///@param _userRequest struct containing data
  ///@param _user address of user who makes the withdraw
  ///@param _maxTRSYToPay maximum amout of stTRSY willing to pay, otherwise withdraw reverts
  ///@dev owner of fyde can force withdraw for other users
  function governanceWithdraw(UserRequest memory _userRequest, address _user, uint256 _maxTRSYToPay)
    external
    whenNotPaused
    onlyUser
  {
    if (msg.sender != _user && msg.sender != owner) revert Unauthorized();
    address[] memory assetOut = new address[](1);
    uint256[] memory amountOut = new uint256[](1);
    assetOut[0] = _userRequest.asset;
    amountOut[0] = _userRequest.amount;
    // for withdraw, assetIn and amountIn are set to empty array
    RequestData memory request = RequestData({
      id: nonce,
      requestor: _user,
      assetIn: new address[](0),
      amountIn: new uint256[](0),
      assetOut: assetOut,
      amountOut: amountOut,
      keepGovRights: true,
      slippageChecker: _maxTRSYToPay
    });
    nonce++;
    uint256 currentAUM = fyde.getProtocolAUM();
    fyde.processWithdraw(currentAUM, request);
    emit Withdraw(request.id, request);
  }
  /*//////////////////////////////////////////////////////////////
                               SWAP
    //////////////////////////////////////////////////////////////*/
  function swap(address _assetIn, uint256 _amountIn, address _assetOut, uint256 _minAmountOut)
    external
    whenSwapNotPaused
    onlySwapper
  {
    address[] memory assetIn = new address[](1);
    uint256[] memory amountIn = new uint256[](1);
    address[] memory assetOut = new address[](1);
    uint256[] memory amountOut = new uint256[](1);
    assetIn[0] = _assetIn;
    amountIn[0] = _amountIn;
    assetOut[0] = _assetOut;
    RequestData memory req = RequestData({
      id: nonce,
      requestor: address(this),
      assetIn: assetIn,
      amountIn: amountIn,
      assetOut: assetOut,
      amountOut: amountOut,
      keepGovRights: false,
      slippageChecker: _minAmountOut
    });
    nonce++;
    uint256 currentAUM = fyde.getProtocolAUM();
    address[] memory assetsSwap = new address[](2);
    assetsSwap[0] = _assetIn;
    assetsSwap[1] = _assetOut;
    _enableOracleCache(assetsSwap);
    // get params for tax calculation from taxModule
    (uint256 amountOutTaxed,) =
      taxModule.getSwapAmountOut(req.assetIn[0], req.amountIn[0], req.assetOut[0], currentAUM);
    // Transfer asset to Relayer
    IERC20(req.assetIn[0]).safeTransferFrom(msg.sender, address(this), req.amountIn[0]);
    fyde.processSwap(currentAUM, req);
    uint256 tokenBalance = IERC20(assetOut[0]).balanceOf(address(this));
    amountOutTaxed = amountOutTaxed > tokenBalance ? tokenBalance : amountOutTaxed;
    if (amountOutTaxed < req.slippageChecker) revert SlippageExceed();
    // Transfer assets to swapper
    IERC20(req.assetOut[0]).safeTransfer(msg.sender, amountOutTaxed);
    emit Swap(req.id, req);
    // deposit tax to receive trsy
    amountIn[0] = tokenBalance - amountOutTaxed;
    if (amountIn[0] > 0) {
      req = RequestData({
        id: nonce,
        requestor: address(this),
        assetIn: assetOut,
        amountIn: amountIn,
        assetOut: new address[](0),
        amountOut: new uint256[](0),
        keepGovRights: false,
        slippageChecker: 0
      });
      nonce++;
      currentAUM = fyde.getProtocolAUM();
      fyde.processDeposit(currentAUM, req);
    }
    _disableOracleCache();
  }
  /*//////////////////////////////////////////////////////////////
                            Keeper FUNCTIONS
    //////////////////////////////////////////////////////////////*/
  ///@notice Offchain checker for AUM deviation
  function checkUpkeep(bytes calldata checkData)
    external
    view
    returns (bool upkeepNeeded, bytes memory performData)
  {
    (uint256 updateFactor, uint256 pauseFactor, bool isChainlink) =
      abi.decode(checkData, (uint256, uint256, bool));
    uint256 aum = fyde.getProtocolAUM();
    uint256 nAum = fyde.computeProtocolAUM();
    // AUM in range of deviation threshold times update factor do nothing
    if (PercentageMath._isInRange(aum, nAum, updateFactor * deviationThreshold / 100)) {
      return (false, "AUM is in range");
    }
    // if stored AUM exceeds the maximum deviation threshold by the pause factor
    // something is wrong and we stop the protocol
    if (!PercentageMath._isInRange(aum, nAum, pauseFactor * deviationThreshold / 100) && !paused) {
      if (isChainlink) return (true, abi.encode(false, 0));
      return (true, abi.encodeCall(this.performUpkeep, (abi.encode(false, 0))));
    }
    // if not in range and not outside the wider range, update AUM
    int256 diffAUM = int256(nAum) - int256(aum);
    if (isChainlink) return (true, abi.encode(true, diffAUM));
    return (true, abi.encodeCall(this.performUpkeep, (abi.encode(true, diffAUM))));
  }
  function performUpkeep(bytes calldata performData) external {
    (bool updateAum, int256 diffAUM) = abi.decode(performData, (bool, int256));
    if (!updateAum) {
      pauseProtocol();
    } else {
      uint256 nAum = uint256(int256(fyde.getProtocolAUM()) + diffAUM);
      updateProtocolAUM(nAum);
    }
  }
  ///@notice Update the protocol AUM, called by Keeper
  function updateProtocolAUM(uint256 nAum) public onlyKeeper {
    fyde.updateProtocolAUM(nAum);
  }
  /*//////////////////////////////////////////////////////////////
                                 INTERNAL
    //////////////////////////////////////////////////////////////*/
  function _enableOracleCache(address[] memory assets) internal {
    AssetInfo[] memory assetInfos = new AssetInfo[](assets.length);
    for (uint256 i; i < assets.length; ++i) {
      assetInfos[i] = fyde.assetInfo(assets[i]);
    }
    oracleModule.useCache(assets, assetInfos);
  }
  function _disableOracleCache() internal {
    oracleModule.disableCache();
  }
  function _checkIsAllowedInGov(address[] memory _assets) internal view {
    address notAllowedInGovAsset = GOVERNANCE_MODULE.isAnyNotOnGovWhitelist(_assets);
    if (notAllowedInGovAsset != address(0x0)) {
      revert AssetNotAllowedInGovernancePool(notAllowedInGovAsset);
    }
  }
  function _checkForDuplicates(address[] memory _assetList) internal pure {
    for (uint256 idx; idx < _assetList.length - 1; idx++) {
      for (uint256 idx2 = idx + 1; idx2 < _assetList.length; idx2++) {
        if (_assetList[idx] == _assetList[idx2]) revert DuplicatesAssets();
      }
    }
  }
  function _uint2str(uint256 _i) internal pure returns (string memory) {
    if (_i == 0) return "0";
    uint256 j = _i;
    uint256 len;
    while (j != 0) {
      len++;
      j /= 10;
    }
    bytes memory bstr = new bytes(len);
    uint256 k = len;
    while (_i != 0) {
      k = k - 1;
      uint8 temp = (48 + uint8(_i - (_i / 10) * 10));
      bytes1 b1 = bytes1(temp);
      bstr[k] = b1;
      _i /= 10;
    }
    return string(bstr);
  }
  modifier whenNotPaused() {
    if (paused) revert ActionPaused();
    _;
  }
  modifier whenSwapNotPaused() {
    if (swapPaused) revert ActionPaused();
    _;
  }
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
import {AccessControl} from "./AccessControl.sol";
///@title QuarantineList contract
///@notice Handle the logic for the quarantine list
abstract contract QuarantineList is AccessControl {
  /// -----------------------------
  ///         Storage
  /// -----------------------------
  uint256 public min_quarantine_duration = 1 days;
  mapping(address => uint128) public quarantineList;
  /// -----------------------------
  ///         Events
  /// -----------------------------
  event AddedToQuarantine(address asset, uint128 expirationTime);
  event RemovedFromQuarantine(address asset);
  /// -----------------------------
  ///         Errors
  /// -----------------------------
  error AssetIsQuarantined(address asset);
  error AssetIsNotQuarantined(address asset);
  error ShortenedExpiration(uint128 currentExpiration, uint128 expiration);
  error ShortQurantineDuration(uint128 duration);
  /// -----------------------------
  ///         Admin external
  /// -----------------------------
  ///@notice Set the minimum quarantine duration
  ///@param _min_quarantine_duration the new minimum quarantine duration
  function set_min_quarantine_duration(uint256 _min_quarantine_duration) external onlyOwner {
    min_quarantine_duration = _min_quarantine_duration;
  }
  ///@notice Add an asset to the quarantine list
  ///@param _asset the address of the assset to be added to the quarantine list
  ///@param _duration the time (in seconds) that the asset should stay in quarantine
  function addToQuarantine(address _asset, uint128 _duration) external onlyGuard {
    if (_duration < min_quarantine_duration) revert ShortQurantineDuration(_duration);
    // gas savings
    uint128 expiration = uint128(block.timestamp) + _duration;
    uint128 currentExpiration = quarantineList[_asset];
    // the new expiration cannot be before the curent one i.e. expiration cannot be reduced, just
    // extended
    if (expiration <= currentExpiration) revert ShortenedExpiration(currentExpiration, expiration);
    quarantineList[_asset] = expiration;
    emit AddedToQuarantine(_asset, expiration);
  }
  ///@notice Remove an asset from the quarantine list
  ///@param _asset the address of the assset to be removed from the quarantine list
  function removeFromQuarantine(address _asset) external onlyGuard {
    // If the asset has nto been quarantined or the duarion period has expired then revert
    if (quarantineList[_asset] < uint128(block.timestamp)) revert AssetIsNotQuarantined(_asset);
    // just set the duration to zero to remove from quarantine
    quarantineList[_asset] = 0;
    emit RemovedFromQuarantine(_asset);
  }
  /// -----------------------------
  ///    External view functions
  /// -----------------------------
  ///@notice Check if an asset is quarantined
  ///@param _asset the address of the assset to be checked
  function isQuarantined(address _asset) public view returns (bool) {
    return quarantineList[_asset] >= uint128(block.timestamp);
  }
  ///@notice Check if any asset from a given list is quarantined
  ///@param _assets an array of asset addresses that need to be checked
  ///@return address of first quarantined asset or address(0x0) if none quarantined
  function isAnyQuarantined(address[] memory _assets) public view returns (address) {
    for (uint256 i = 0; i < _assets.length;) {
      if (isQuarantined(_assets[i])) return _assets[i];
      unchecked {
        ++i;
      }
    }
    return address(0x0);
  }
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
struct AssetInfo {
  uint72 targetConcentration;
  address uniswapPool;
  int72 incentiveFactor;
  uint8 assetDecimals;
  uint8 quoteTokenDecimals;
  address uniswapQuoteToken;
  bool isSupported;
}
struct ProtocolData {
  ///@notice Protocol AUM in USD
  uint256 aum;
  ///@notice multiplicator for the tax equation, 100% = 100e18
  uint72 taxFactor;
  ///@notice Max deviation allowed between AUM from keeper and registry
  uint16 maxAumDeviationAllowed; // Default val 200 == 2 %
  ///@notice block number where AUM was last updated
  uint48 lastAUMUpdateBlock;
  ///@notice annual fee on AUM, in % per year 100% = 100e18
  uint72 managementFee;
  ///@notice last block.timestamp when fee was collected
  uint48 lastFeeCollectionTime;
}
struct UserRequest {
  address asset;
  uint256 amount;
}
struct RequestData {
  uint32 id;
  address requestor;
  address[] assetIn;
  uint256[] amountIn;
  address[] assetOut;
  uint256[] amountOut;
  bool keepGovRights;
  uint256 slippageChecker;
}
struct RequestQ {
  uint64 start;
  uint64 end;
  mapping(uint64 => RequestData) requestData;
}
struct ProcessParam {
  uint256 targetConc;
  uint256 currentConc;
  uint256 usdValue;
  uint256 taxableAmount;
  uint256 taxInUSD;
  uint256 sharesBeforeTax;
  uint256 sharesAfterTax;
}
struct RebalanceParam {
  address asset;
  uint256 assetTotalAmount;
  uint256 assetProxyAmount;
  uint256 assetPrice;
  uint256 sTrsyTotalSupply;
  uint256 trsyPrice;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
///@title Ownable contract
/// @notice Simple 2step owner authorization combining solmate and OZ implementation
abstract contract Ownable {
  /*//////////////////////////////////////////////////////////////
                             STORAGE
    //////////////////////////////////////////////////////////////*/
  ///@notice Address of the owner
  address public owner;
  ///@notice Address of the pending owner
  address public pendingOwner;
  /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/
  event OwnershipTransferred(address indexed user, address indexed newOner);
  event OwnershipTransferStarted(address indexed user, address indexed newOwner);
  event OwnershipTransferCanceled(address indexed pendingOwner);
  /*//////////////////////////////////////////////////////////////
                                 ERROR
    //////////////////////////////////////////////////////////////*/
  error Unauthorized();
  /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/
  constructor(address _owner) {
    owner = _owner;
    emit OwnershipTransferred(address(0), _owner);
  }
  /*//////////////////////////////////////////////////////////////
                             OWNERSHIP LOGIC
    //////////////////////////////////////////////////////////////*/
  ///@notice Transfer ownership to a new address
  ///@param newOwner address of the new owner
  ///@dev newOwner have to acceptOwnership
  function transferOwnership(address newOwner) external onlyOwner {
    pendingOwner = newOwner;
    emit OwnershipTransferStarted(msg.sender, pendingOwner);
  }
  ///@notice NewOwner accept the ownership, it transfer the ownership to newOwner
  function acceptOwnership() external {
    if (msg.sender != pendingOwner) revert Unauthorized();
    address oldOwner = owner;
    owner = pendingOwner;
    delete pendingOwner;
    emit OwnershipTransferred(oldOwner, owner);
  }
  ///@notice Cancel the ownership transfer
  function cancelTransferOwnership() external onlyOwner {
    emit OwnershipTransferCanceled(pendingOwner);
    delete pendingOwner;
  }
  modifier onlyOwner() {
    if (msg.sender != owner) revert Unauthorized();
    _;
  }
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
library PercentageMath {
  ///\tCONSTANTS ///
  uint256 internal constant PERCENTAGE_FACTOR = 1e4; // 100.00%
  uint256 internal constant HALF_PERCENTAGE_FACTOR = 0.5e4; // 50.00%
  uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
  uint256 internal constant MAX_UINT256_MINUS_HALF_PERCENTAGE = 2 ** 256 - 1 - 0.5e4;
  /// INTERNAL ///
  ///@notice Check if value are within the range
  function _isInRange(uint256 valA, uint256 valB, uint256 deviationThreshold)
    internal
    pure
    returns (bool)
  {
    uint256 lowerBound = percentSub(valA, deviationThreshold);
    uint256 upperBound = percentAdd(valA, deviationThreshold);
    if (valB < lowerBound || valB > upperBound) return false;
    else return true;
  }
  /// @notice Executes a percentage addition (x * (1 + p)), rounded up.
  /// @param x The value to which to add the percentage.
  /// @param percentage The percentage of the value to add.
  /// @return y The result of the addition.
  function percentAdd(uint256 x, uint256 percentage) internal pure returns (uint256 y) {
    // Must revert if
    // PERCENTAGE_FACTOR + percentage > type(uint256).max
    //     or x * (PERCENTAGE_FACTOR + percentage) + HALF_PERCENTAGE_FACTOR > type(uint256).max
    // <=> percentage > type(uint256).max - PERCENTAGE_FACTOR
    //     or x > (type(uint256).max - HALF_PERCENTAGE_FACTOR) / (PERCENTAGE_FACTOR + percentage)
    // Note: PERCENTAGE_FACTOR + percentage >= PERCENTAGE_FACTOR > 0
    assembly {
      y := add(PERCENTAGE_FACTOR, percentage) // Temporary assignment to save gas.
      if or(
        gt(percentage, sub(MAX_UINT256, PERCENTAGE_FACTOR)),
        gt(x, div(MAX_UINT256_MINUS_HALF_PERCENTAGE, y))
      ) { revert(0, 0) }
      y := div(add(mul(x, y), HALF_PERCENTAGE_FACTOR), PERCENTAGE_FACTOR)
    }
  }
  /// @notice Executes a percentage subtraction (x * (1 - p)), rounded up.
  /// @param x The value to which to subtract the percentage.
  /// @param percentage The percentage of the value to subtract.
  /// @return y The result of the subtraction.
  function percentSub(uint256 x, uint256 percentage) internal pure returns (uint256 y) {
    // Must revert if
    // percentage > PERCENTAGE_FACTOR
    //     or x * (PERCENTAGE_FACTOR - percentage) + HALF_PERCENTAGE_FACTOR > type(uint256).max
    // <=> percentage > PERCENTAGE_FACTOR
    //     or ((PERCENTAGE_FACTOR - percentage) > 0 and x > (type(uint256).max -
    // HALF_PERCENTAGE_FACTOR) / (PERCENTAGE_FACTOR - percentage))
    assembly {
      y := sub(PERCENTAGE_FACTOR, percentage) // Temporary assignment to save gas.
      if or(
        gt(percentage, PERCENTAGE_FACTOR), mul(y, gt(x, div(MAX_UINT256_MINUS_HALF_PERCENTAGE, y)))
      ) { revert(0, 0) }
      y := div(add(mul(x, y), HALF_PERCENTAGE_FACTOR), PERCENTAGE_FACTOR)
    }
  }
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;
    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }
    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }
    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }
    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }
    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }
    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }
    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }
    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.
        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }
    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.
        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    }
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface 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 amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);
    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);
    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);
    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);
    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);
    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
import {RequestData, RebalanceParam, ProcessParam, AssetInfo} from "src/core/Structs.sol";
interface IFyde {
  function protocolData() external view returns (uint256, uint72, uint16, uint48, uint72, uint48);
  function assetInfo(address) external view returns (AssetInfo memory);
  function isAnyNotSupported(address[] calldata _assets) external view returns (address);
  function isSwapAllowed(address[] calldata _assets) external view returns (address);
  function computeProtocolAUM() external view returns (uint256);
  function getProtocolAUM() external view returns (uint256);
  function updateProtocolAUM(uint256) external;
  function processDeposit(uint256, RequestData calldata) external returns (uint256);
  function processWithdraw(uint256, RequestData calldata) external returns (uint256);
  function totalSupply() external view returns (uint256);
  function setOracleModule(address _oracle) external;
  function oracleModule() external view returns (address);
  function setRelayer(address _relayer) external;
  function RELAYER() external view returns (address);
  function totalAssetAccounting(address) external view returns (uint256);
  function proxyAssetAccounting(address) external view returns (uint256);
  function standardAssetAccounting(address) external view returns (uint256);
  function getQuote(address, uint256) external view returns (uint256);
  function getAssetDecimals(address) external view returns (uint8);
  function collectManagementFee() external;
  function getAssetsListLength() external view returns (uint256);
  function assetsList(uint256 index) external view returns (address);
  function processSwap(uint256, RequestData calldata) external returns (int256);
  function owner() external view returns (address);
  function getProcessParamDeposit(RequestData memory _req, uint256 _protocolAUM)
    external
    view
    returns (
      ProcessParam[] memory processParam,
      uint256 sharesToMint,
      uint256 taxInTRSY,
      uint256 totalUsdDeposit
    );
  function getProcessParamWithdraw(RequestData calldata _req, uint256 _protocolAUM)
    external
    view
    returns (
      ProcessParam[] memory processParam,
      uint256 totalSharesToBurn,
      uint256 sharesToBurnBeforeTax,
      uint256 taxInTRSY,
      uint256 totalUsdWithdraw
    );
  function acceptOwnership() external;
  // GOVERNANCE ACCESS FUNCTIONS
  function transferAsset(address _asset, address _recipient, uint256 _amount) external;
  function getRebalanceParams(address _asset) external view returns (RebalanceParam memory);
  function updateAssetProxyAmount(address _asset, uint256 _amount) external;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
interface IGovernanceModule {
  function fyde() external view returns (address);
  function proxyImplementation() external view returns (address);
  function proxyBalance(address proxy, address asset) external view returns (uint256);
  function strsyBalance(address _user, address _govToken) external view returns (uint256 balance);
  function assetToStrsy(address _asset) external view returns (address);
  function userToProxy(address _user) external view returns (address);
  function proxyToUser(address _proxy) external view returns (address);
  function isOnGovernanceWhitelist(address _asset) external view returns (bool);
  function getAllGovUsers() external view returns (address[] memory);
  function isAnyNotOnGovWhitelist(address[] calldata _assets) external view returns (address);
  function getUserGTAllowance(uint256 _TRSYAmount, address _token) external view returns (uint256);
  function govDeposit(
    address _depositor,
    address[] calldata _govToken,
    uint256[] calldata _amount,
    uint256[] calldata _amountTRSY,
    uint256 _totalTRSY
  ) external returns (address proxy);
  function govWithdraw(
    address _user,
    address _asset,
    uint256 _amountToWithdraw,
    uint256 _trsyToBurn
  ) external;
  function onStrsyTransfer(address sender, address _recipient) external;
  function unstakeGov(uint256 _amount, address _asset) external;
  function rebalanceProxy(address _proxy, address _asset, address[] memory _usersToRebalance)
    external;
  function acceptOwnership() external;
}
//SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
import {AssetInfo} from "../core/Structs.sol";
interface IOracle {
  function getPriceInUSD(address, AssetInfo calldata) external view returns (uint256);
  function getGweiPrice() external view returns (uint256);
  function useCache(address[] calldata addr, AssetInfo[] calldata assetInfo) external;
  function disableCache() external;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
import {RequestData, ProcessParam} from "src/core/Structs.sol";
interface ITaxModule {
  function getProcessParamDeposit(RequestData memory _req, uint256 _protocolAUM)
    external
    view
    returns (
      ProcessParam[] memory processParam,
      uint256 sharesToMint,
      uint256 taxInTRSY,
      uint256 totalUsdDeposit
    );
  function getProcessParamWithdraw(RequestData calldata _req, uint256 _protocolAUM)
    external
    view
    returns (
      ProcessParam[] memory processParam,
      uint256 totalSharesToBurn,
      uint256 sharesToBurnBeforeTax,
      uint256 taxInTRSY,
      uint256 totalUsdWithdraw
    );
  function getSwapAmountOut(
    address _assetIn,
    uint256 _amountIn,
    address _assetOut,
    uint256 _protocolAUM
  ) external view returns (uint256, int256);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
import {Ownable} from "src/utils/Ownable.sol";
///@title AccessControl
/// @notice Handles the different access authorizations for the relayer
abstract contract AccessControl is Ownable {
  /*//////////////////////////////////////////////////////////////
                             STORAGE
    //////////////////////////////////////////////////////////////*/
  ///@notice exclusive user - when != address(0x0), other users are removed from whitelist
  ///        intended for short term use during incidence response, escrow, migration
  address public exclusiveUser;
  ///@notice guard authorization
  mapping(address => bool) public isGuard;
  ///@notice keeper authorization
  mapping(address => bool) public isKeeper;
  ///@notice user authorization, can use deposit, withdraw
  mapping(address => bool) public isUser;
  ///@notice swapper authorization, can use swap
  mapping(address => bool) public isSwapper;
  ///@notice incentive manager authorization, can set incentives for swaps on Fyde
  mapping(address => bool) public isIncentiveManager;
  /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/
  event ExclusiveUserSet(address indexed);
  event GuardAdded(address indexed);
  event GuardRemoved(address indexed);
  event KeeperAdded(address indexed);
  event KeeperRemoved(address indexed);
  event UserAdded(address indexed);
  event UserRemoved(address indexed);
  event IncentiveManagerAdded(address indexed);
  event IncentiveManagerRemoved(address indexed);
  event SwapperAdded(address indexed);
  event SwapperRemoved(address indexed);
  /*//////////////////////////////////////////////////////////////
                               SETTER
    //////////////////////////////////////////////////////////////*/
  function setExclusiveUser(address _exclusiveUser) external onlyOwner {
    exclusiveUser = _exclusiveUser;
    emit ExclusiveUserSet(_exclusiveUser);
  }
  function addGuard(address _guard) external onlyOwner {
    isGuard[_guard] = true;
    emit GuardAdded(_guard);
  }
  function removeGuard(address _guard) external onlyOwner {
    isGuard[_guard] = false;
    emit GuardRemoved(_guard);
  }
  function addKeeper(address _keeper) external onlyOwner {
    isKeeper[_keeper] = true;
    emit KeeperAdded(_keeper);
  }
  function removeKeeper(address _keeper) external onlyOwner {
    isKeeper[_keeper] = false;
    emit KeeperRemoved(_keeper);
  }
  function addUser(address[] calldata _user) external onlyOwner {
    for (uint256 i; i < _user.length; ++i) {
      isUser[_user[i]] = true;
      emit UserAdded(_user[i]);
    }
  }
  function removeUser(address[] calldata _user) external onlyOwner {
    for (uint256 i; i < _user.length; ++i) {
      isUser[_user[i]] = false;
      emit UserRemoved(_user[i]);
    }
  }
  function addIncentiveManager(address _incentiveManager) external onlyOwner {
    isIncentiveManager[_incentiveManager] = true;
    emit IncentiveManagerAdded(_incentiveManager);
  }
  function removeIncentiveManager(address _incentiveManager) external onlyOwner {
    isIncentiveManager[_incentiveManager] = false;
    emit IncentiveManagerRemoved(_incentiveManager);
  }
  function addSwapper(address _swapper) external onlyOwner {
    isSwapper[_swapper] = true;
    emit SwapperAdded(_swapper);
  }
  function removeSwapper(address _swapper) external onlyOwner {
    isSwapper[_swapper] = false;
    emit SwapperRemoved(_swapper);
  }
  /*//////////////////////////////////////////////////////////////
                               MODIFIER
    //////////////////////////////////////////////////////////////*/
  ///@notice only a registered keeper can access
  modifier onlyKeeper() {
    if (!isKeeper[msg.sender]) revert Unauthorized();
    _;
  }
  ///@notice only a registered guard can access
  modifier onlyGuard() {
    if (!isGuard[msg.sender]) revert Unauthorized();
    _;
  }
  ///@dev whitelisting address(0x0) disables whitelist -> full permissionless access
  ///@dev setting exclusiveUser to != address(0x0) blocks everyone else
  ///     - intended for escrow, incidence response and migration
  modifier onlyUser() {
    // if whitelist is not disabeld and user not whitelisted -> no access
    if (!isUser[address(0x0)] && !isUser[msg.sender]) revert Unauthorized();
    // if exclusive user exists and is not user -> no accesss
    if (exclusiveUser != address(0x0) && exclusiveUser != msg.sender) revert Unauthorized();
    _;
  }
  ///@dev whitelisting address(0x0) disables whitelist -> full permissionless access
  ///@dev setting exclusiveUser to != address(0x0) blocks everyone else
  ///     - intended for escrow, incidence response and migration
  modifier onlySwapper() {
    if (!isSwapper[address(0x0)] && !isSwapper[msg.sender]) revert Unauthorized();
    if (exclusiveUser != address(0x0) && exclusiveUser != msg.sender) revert Unauthorized();
    _;
  }
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;
    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);
    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.
        return account.code.length > 0;
    }
    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");
        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }
    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }
    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }
    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }
    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }
    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }
    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }
    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }
    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }
    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }
    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }
    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

File 2 of 2: Fyde
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
/// Import from Core /////
import {TRSY} from "./core/TRSY.sol";
import {AssetRegistry} from "./core/AssetRegistry.sol";
import {AddressRegistry} from "./core/AddressRegistry.sol";
import {ProtocolState} from "./core/ProtocolState.sol";
import {Tax} from "./core/Tax.sol";
import {GovernanceAccess} from "./core/GovernanceAccess.sol";
/// Structs /////
import {RequestData, ProcessParam, AssetInfo} from "./core/Structs.sol";
/// Utils /////
import {Ownable} from "./utils/Ownable.sol";
import {PercentageMath} from "./utils/PercentageMath.sol";
import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
//Interfaces
import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import {IOracle} from "src/interfaces/IOracle.sol";
import {IRelayer} from "src/interfaces/IRelayer.sol";
///@title Fyde contract
///@notice Fyde is the main contract of the protocol, it handles logic of deposit and withdraw in
/// the protocol
///        Deposit and withdraw occurs a mint or a burn of TRSY (ERC20 that represent shares of the
/// procotol in USD value)
///        Users can both deposit/withdraw in standard or governance pool
contract Fyde is TRSY, AddressRegistry, ProtocolState, AssetRegistry, GovernanceAccess, Tax {
  using SafeERC20 for IERC20;
  /*//////////////////////////////////////////////////////////////
                            EVENTS
  //////////////////////////////////////////////////////////////*/
  event FeesCollected(address indexed recipient, uint256 trsyFeesCollected);
  event Deposit(uint32 requestId, uint256 trsyPrice, uint256 usdDepositValue, uint256 trsyMinted);
  event Withdraw(uint32 requestId, uint256 trsyPrice, uint256 usdWithdrawValue, uint256 trsyBurned);
  event Swap(uint32 requestId, address assetOut, uint256 amountOut);
  event ManagementFeeCollected(uint256 feeToMint);
  /*//////////////////////////////////////////////////////////////
                              ERROR
  //////////////////////////////////////////////////////////////*/
  error AumNotInRange();
  error OnlyOneUpdatePerBlock();
  error SlippageExceed();
  error FydeBalanceInsufficient();
  error InsufficientTRSYBalance();
  error AssetPriceNotAvailable();
  error SwapAmountNotAvailable();
  error AssetNotSupported(address asset);
  error SwapDisabled(address asset);
  error AssetIsQuarantined(address asset);
  /*//////////////////////////////////////////////////////////////
                            CONSTRUCTOR
  //////////////////////////////////////////////////////////////*/
  constructor(
    address _relayer,
    address _oracleModule,
    address _governanceModule,
    uint16 _maxAumDeviationAllowed,
    uint72 _taxFactor,
    uint72 _managementFee
  ) Ownable(msg.sender) AddressRegistry(_governanceModule, _relayer) {
    oracleModule = IOracle(_oracleModule);
    updateMaxAumDeviationAllowed(_maxAumDeviationAllowed);
    updateTaxFactor(_taxFactor);
    updateManagementFee(_managementFee);
    _updateLastFeeCollectionTime();
  }
  /*//////////////////////////////////////////////////////////////
                                AUTH
  //////////////////////////////////////////////////////////////*/
  ///@notice Collect and send TRSY fees (from tax fees) to an external address
  ///@param _recipient Address to send TRSY fees to
  ///@param _amount Amount of TRSY to send
  function collectFees(address _recipient, uint256 _amount) external onlyOwner {
    _checkZeroAddress(_recipient);
    _checkZeroValue(_amount);
    balanceOf[address(this)] -= _amount;
    balanceOf[_recipient] += _amount;
    emit FeesCollected(_recipient, _amount);
  }
  ///@notice Collect management fee by inflating TRSY and minting to Fyde
  ///        is called by the relayer when processingRequests
  function collectManagementFee() external {
    uint256 feePerSecond = uint256(protocolData.managementFee / 31_557_600);
    uint256 timePeriod = block.timestamp - protocolData.lastFeeCollectionTime;
    if (timePeriod == 0) return;
    uint256 feeToMint = feePerSecond * timePeriod * totalSupply / 1e18;
    _updateLastFeeCollectionTime();
    _mint(address(this), feeToMint);
    emit ManagementFeeCollected(feeToMint);
  }
  /*//////////////////////////////////////////////////////////////
                  RELAYER & KEEPER FUNCTIONS
  //////////////////////////////////////////////////////////////*/
  ///@notice Update protocol AUM, called by keeper
  ///@param _aum New AUM
  ///@dev Can at most be updated by maxDeviationThreshold and only once per block
  function updateProtocolAUM(uint256 _aum) external {
    if (msg.sender != RELAYER && msg.sender != owner) revert Unauthorized();
    if (block.number == protocolData.lastAUMUpdateBlock) revert CoolDownPeriodActive();
    protocolData.lastAUMUpdateBlock = uint48(block.number);
    (, uint256 limitedAum) = _AumIsInRange(_aum);
    _updateProtocolAUM(limitedAum);
  }
  /*//////////////////////////////////////////////////////////////
                  PROCESSING DEPOSIT ACTIONS
  //////////////////////////////////////////////////////////////*/
  ///@notice Process deposit action, called by relayer
  ///@param _protocolAUM AUM given by keeper
  ///@param _req RequestData struct
  ///@return totalUsdDeposit USD value of the deposit
  function processDeposit(uint256 _protocolAUM, RequestData calldata _req)
    external
    onlyRelayer
    returns (uint256)
  {
    // Check if asset is supported
    _checkIsSupported(_req.assetIn);
    _checkIsNotQuarantined(_req.assetIn);
    // is keeper AUM in range
    (bool isInRange,) = _AumIsInRange(_protocolAUM);
    if (!isInRange) revert AumNotInRange();
    (
      ProcessParam[] memory processParam,
      uint256 sharesToMint,
      uint256 taxInTRSY,
      uint256 totalUsdDeposit
    ) = getProcessParamDeposit(_req, _protocolAUM);
    // Slippage checker
    if (_req.slippageChecker > sharesToMint) revert SlippageExceed();
    // Transfer assets to Fyde
    for (uint256 i; i < _req.assetIn.length; i++) {
      IERC20(_req.assetIn[i]).safeTransferFrom(_req.requestor, address(this), _req.amountIn[i]);
    }
    if (_req.keepGovRights) _govDeposit(_req, processParam);
    else _standardDeposit(_req, sharesToMint);
    // Mint tax and keep in contract
    if (taxInTRSY > 0) _mint(address(this), taxInTRSY);
    _updateProtocolAUM(_protocolAUM + totalUsdDeposit);
    uint256 trsyPrice = (1e18 * (_protocolAUM + totalUsdDeposit)) / totalSupply;
    emit Deposit(_req.id, trsyPrice, totalUsdDeposit, sharesToMint);
    return totalUsdDeposit;
  }
  function _standardDeposit(RequestData calldata _req, uint256 _sharesToMint) internal {
    // Accounting
    _increaseAssetTotalAmount(_req.assetIn, _req.amountIn);
    // Minting shares
    _mint(_req.requestor, _sharesToMint);
  }
  function _govDeposit(RequestData calldata _req, ProcessParam[] memory _processParam) internal {
    uint256[] memory sharesAfterTax = new uint256[](_req.assetIn.length);
    uint256[] memory amountInAfterTax = new uint256[](_req.assetIn.length);
    // Same average tax rate is applied to each asset
    uint256 taxMultiplicator;
    uint256 totalTrsy;
    for (uint256 i; i < _req.assetIn.length; i++) {
      taxMultiplicator = 1e18 * _processParam[i].sharesAfterTax / (_processParam[i].sharesBeforeTax);
      amountInAfterTax[i] = _req.amountIn[i] * taxMultiplicator / 1e18;
      sharesAfterTax[i] = _processParam[i].sharesAfterTax;
      totalTrsy += sharesAfterTax[i];
    }
    // Mint stTRSY and transfer token into proxy
    address proxy = GOVERNANCE_MODULE.govDeposit(
      _req.requestor, _req.assetIn, amountInAfterTax, sharesAfterTax, totalTrsy
    );
    for (uint256 i; i < _req.assetIn.length; i++) {
      IERC20(_req.assetIn[i]).safeTransfer(proxy, amountInAfterTax[i]);
    }
    // Accounting
    _increaseAssetTotalAmount(_req.assetIn, _req.amountIn);
    _increaseAssetProxyAmount(_req.assetIn, amountInAfterTax);
    // Mint
    _mint(address(GOVERNANCE_MODULE), totalTrsy);
  }
  /*//////////////////////////////////////////////////////////////
                  PROCESSING WITHDRAW ACTIONS
  //////////////////////////////////////////////////////////////*/
  ///@notice Process withdraw action, called by relayer
  ///@param _protocolAUM AUM given by keeper
  ///@param _req RequestData struct
  ///@return totalUsdWithdraw USD value of the withdraw
  function processWithdraw(uint256 _protocolAUM, RequestData calldata _req)
    external
    onlyRelayer
    returns (uint256)
  {
    // Check if asset is supported
    _checkIsSupported(_req.assetOut);
    _checkIsNotQuarantined(_req.assetOut);
    // is keeper AUM in range
    (bool isInRange,) = _AumIsInRange(_protocolAUM);
    if (!isInRange) revert AumNotInRange();
    uint256 totalUsdWithdraw;
    uint256 totalSharesToBurn;
    (totalUsdWithdraw, totalSharesToBurn) =
      _req.keepGovRights ? _govWithdraw(_protocolAUM, _req) : _standardWithdraw(_protocolAUM, _req);
    // Accounting
    _decreaseAssetTotalAmount(_req.assetOut, _req.amountOut);
    _updateProtocolAUM(_protocolAUM - totalUsdWithdraw);
    // Calculate for offchain purpose
    uint256 trsyPrice =
      totalSupply != 0 ? (1e18 * (_protocolAUM - totalUsdWithdraw)) / totalSupply : 0;
    emit Withdraw(_req.id, trsyPrice, totalUsdWithdraw, totalSharesToBurn);
    return totalUsdWithdraw;
  }
  function _govWithdraw(uint256 _protocolAUM, RequestData calldata _req)
    internal
    returns (uint256, uint256)
  {
    uint256 usdVal = getQuote(_req.assetOut[0], _req.amountOut[0]);
    if (usdVal == 0) revert AssetPriceNotAvailable();
    uint256 trsyToBurn = _convertToShares(usdVal, _protocolAUM);
    if (_req.slippageChecker < trsyToBurn) revert SlippageExceed();
    _burn(address(GOVERNANCE_MODULE), trsyToBurn);
    _decreaseAssetProxyAmount(_req.assetOut, _req.amountOut);
    GOVERNANCE_MODULE.govWithdraw(_req.requestor, _req.assetOut[0], _req.amountOut[0], trsyToBurn);
    IERC20(_req.assetOut[0]).safeTransfer(_req.requestor, _req.amountOut[0]);
    return (usdVal, trsyToBurn);
  }
  function _standardWithdraw(uint256 _protocolAUM, RequestData calldata _req)
    internal
    returns (uint256, uint256)
  {
    // check if requested token are available
    for (uint256 i = 0; i < _req.assetOut.length; i++) {
      if (standardAssetAccounting(_req.assetOut[i]) < _req.amountOut[i]) {
        revert FydeBalanceInsufficient();
      }
    }
    (, uint256 totalSharesToBurn,, uint256 taxInTRSY, uint256 totalUsdWithdraw) =
      getProcessParamWithdraw(_req, _protocolAUM);
    if (totalSharesToBurn > _req.slippageChecker) revert SlippageExceed();
    if (balanceOf[_req.requestor] < totalSharesToBurn) revert InsufficientTRSYBalance();
    _burn(_req.requestor, totalSharesToBurn);
    // Give tax to this contract
    if (taxInTRSY > 0) _mint(address(this), taxInTRSY);
    for (uint256 i = 0; i < _req.assetOut.length; i++) {
      // Send asset to recipient
      IERC20(_req.assetOut[i]).safeTransfer(_req.requestor, _req.amountOut[i]);
    }
    return (totalUsdWithdraw, totalSharesToBurn);
  }
  /*//////////////////////////////////////////////////////////////
                              SWAP
  //////////////////////////////////////////////////////////////*/
  function processSwap(uint256 _protocolAUM, RequestData calldata _req)
    external
    onlyRelayer
    returns (int256)
  {
    // Check if asset is supported
    _checkIsSupported(_req.assetIn);
    _checkIsSupported(_req.assetOut);
    _checkIsNotQuarantined(_req.assetIn);
    _checkIsNotQuarantined(_req.assetOut);
    _checkIfSwapAllowed(_req.assetIn);
    _checkIfSwapAllowed(_req.assetOut);
    // is keeper AUM in range
    (bool isInRange,) = _AumIsInRange(_protocolAUM);
    if (!isInRange) revert AumNotInRange();
    (uint256 amountOut, int256 deltaAUM) =
      getSwapAmountOut(_req.assetIn[0], _req.amountIn[0], _req.assetOut[0], _protocolAUM);
    if (amountOut == 0) revert SwapAmountNotAvailable();
    if (amountOut < _req.slippageChecker) revert SlippageExceed();
    // Check enough asset in protocol
    if (standardAssetAccounting(_req.assetOut[0]) < amountOut) revert FydeBalanceInsufficient();
    // Update AUM
    uint256 aum;
    // If the swapper pays net tax, we mint the corresponding TRSY to fyde. This way TRSY price
    // stays constant
    if (deltaAUM > 0) {
      aum = _protocolAUM + uint256(deltaAUM);
      _mint(address(this), _convertToShares(uint256(deltaAUM), _protocolAUM));
      // If incentives are higher tan taxes, we burn TRSY from fyde, to keep TRSY price constant
      // as backup if not enough TRSY in Fyde, we don't burn, i.e. TRSY price goes down and
      // incentives are
      // paid by pool
      // this way by frequently cashing out TRSY from fyde we can manually decide how much tax to
      // keep for ourselves
      // or leave in Fyde for incentives
    } else if (deltaAUM < 0) {
      aum = _protocolAUM - uint256(-deltaAUM);
      uint256 trsyToBurn = _convertToShares(uint256(-deltaAUM), _protocolAUM);
      trsyToBurn = balanceOf[address(this)] >= trsyToBurn ? trsyToBurn : balanceOf[address(this)];
      if (trsyToBurn != 0) _burn(address(this), trsyToBurn);
    } else {
      aum = _protocolAUM;
    }
    _updateProtocolAUM(aum);
    // Log accounting
    _increaseAssetTotalAmount(_req.assetIn[0], _req.amountIn[0]);
    _decreaseAssetTotalAmount(_req.assetOut[0], amountOut);
    // Transfer asset
    IERC20(_req.assetIn[0]).safeTransferFrom(_req.requestor, address(this), _req.amountIn[0]);
    IERC20(_req.assetOut[0]).safeTransfer(_req.requestor, amountOut);
    emit Swap(_req.id, _req.assetOut[0], amountOut);
    return deltaAUM;
  }
  /*///////////////////////////////////////////////////////////////
                              GETTERS
  //////////////////////////////////////////////////////////////*/
  ///@notice Give a quote for a speficic asset deposit
  ///@param _asset asset address to quote
  ///@param _amount amount of asset to deposit
  ///@return USD value of the specified deposit (return 18 decimals, 1USD = 1e18)
  ///@dev    If price is inconsistent or not available, returns 0 from oracle module -> needs proper
  ///        handling
  function getQuote(address _asset, uint256 _amount) public view override returns (uint256) {
    AssetInfo memory _assetInfo = assetInfo[_asset];
    uint256 price = oracleModule.getPriceInUSD(_asset, _assetInfo);
    return (_amount * price) / (10 ** _assetInfo.assetDecimals);
  }
  ///@notice Get the USD value of an asset in the protocol
  ///@param _asset asset address
  ///@return USD value of the asset
  ///@dev    If price is inconsistent or not available, returns 0 -> needs proper handling
  function getAssetAUM(address _asset) public view returns (uint256) {
    return getQuote(_asset, totalAssetAccounting[_asset]);
  }
  ///@notice Compute the USD AUM for the protocol
  ///@dev Should NOT be call within a contract (GAS EXPENSIVE), called off-chain by keeper
  function computeProtocolAUM() public view returns (uint256) {
    address asset;
    uint256 aum;
    uint256 assetAUM;
    address[] memory nAsset = assetsList;
    uint256 length = nAsset.length;
    for (uint256 i = 0; i < length; ++i) {
      asset = nAsset[i];
      if (totalAssetAccounting[asset] == 0) continue;
      assetAUM = getAssetAUM(asset);
      if (assetAUM == 0) return protocolData.aum;
      aum += assetAUM;
    }
    return aum;
  }
  /*//////////////////////////////////////////////////////////////
                        PROCESS PARAM
  //////////////////////////////////////////////////////////////*/
  ///@notice Return the process param for a deposit
  ///@param _req RequestData struct
  ///@param _protocolAUM AUM given by keeper
  ///@return processParam array of ProcessParam struct
  ///@return sharesToMint amount of shares to mint
  ///@return taxInTRSY amount of tax in TRSY
  ///@return totalUsdDeposit USD value of the depositn
  function getProcessParamDeposit(RequestData memory _req, uint256 _protocolAUM)
    public
    view
    returns (
      ProcessParam[] memory processParam,
      uint256 sharesToMint,
      uint256 taxInTRSY,
      uint256 totalUsdDeposit
    )
  {
    processParam = new ProcessParam[](_req.assetIn.length);
    // Build data struct and compute value of deposit
    for (uint256 i; i < _req.assetIn.length; i++) {
      uint256 usdVal = getQuote(_req.assetIn[i], _req.amountIn[i]);
      if (usdVal == 0) revert AssetPriceNotAvailable();
      processParam[i] = ProcessParam({
        targetConc: assetInfo[_req.assetIn[i]].targetConcentration,
        currentConc: _getAssetConcentration(_req.assetIn[i], _protocolAUM),
        usdValue: usdVal,
        sharesBeforeTax: _convertToShares(usdVal, _protocolAUM),
        taxableAmount: 0,
        taxInUSD: 0,
        sharesAfterTax: 0
      });
      totalUsdDeposit += usdVal;
    }
    for (uint256 i; i < processParam.length; i++) {
      // Get the TaxInUSD
      processParam[i] =
        _getDepositTax(processParam[i], _protocolAUM, totalUsdDeposit, protocolData.taxFactor);
      // Apply tax to the deposit
      processParam[i].sharesAfterTax =
        _convertToShares(processParam[i].usdValue - processParam[i].taxInUSD, _protocolAUM);
      sharesToMint += processParam[i].sharesAfterTax;
      taxInTRSY += processParam[i].sharesBeforeTax - processParam[i].sharesAfterTax;
    }
    return (processParam, sharesToMint, taxInTRSY, totalUsdDeposit);
  }
  ///@notice Return the process param for a withdraw
  ///@param _req RequestData struct
  ///@param _protocolAUM AUM given by keeper
  ///@return processParam array of ProcessParam struct
  ///@return totalSharesToBurn amount of shares to burn
  ///@return sharesToBurnBeforeTax amount of shares to burn before tax
  ///@return taxInTRSY amount of tax in TRSY
  ///@return totalUsdWithdraw USD value of the withdraw
  function getProcessParamWithdraw(RequestData calldata _req, uint256 _protocolAUM)
    public
    view
    returns (
      ProcessParam[] memory processParam,
      uint256 totalSharesToBurn,
      uint256 sharesToBurnBeforeTax,
      uint256 taxInTRSY,
      uint256 totalUsdWithdraw
    )
  {
    processParam = new ProcessParam[](_req.assetOut.length);
    // Build data struct and compute value of deposit
    for (uint256 i; i < _req.assetOut.length; i++) {
      uint256 usdVal = getQuote(_req.assetOut[i], _req.amountOut[i]);
      if (usdVal == 0) revert AssetPriceNotAvailable();
      processParam[i] = ProcessParam({
        targetConc: assetInfo[_req.assetOut[i]].targetConcentration,
        currentConc: _getAssetConcentration(_req.assetOut[i], _protocolAUM),
        usdValue: usdVal,
        sharesBeforeTax: 0,
        taxableAmount: 0,
        taxInUSD: 0,
        sharesAfterTax: 0
      });
      totalUsdWithdraw += usdVal;
    }
    for (uint256 i; i < processParam.length; i++) {
      // Get the TaxInUSD
      processParam[i] =
        _getWithdrawTax(processParam[i], _protocolAUM, totalUsdWithdraw, protocolData.taxFactor);
      taxInTRSY += _convertToShares(processParam[i].taxInUSD, _protocolAUM);
    }
    sharesToBurnBeforeTax = _convertToShares(totalUsdWithdraw, _protocolAUM);
    totalSharesToBurn = sharesToBurnBeforeTax + taxInTRSY;
  }
  ///@notice Return the amountOut for a swap accounting for tax and incentive
  ///@param _assetIn asset address to swap
  ///@param _amountIn amount of asset to swap
  ///@param _assetOut asset address to receive
  ///@param _protocolAUM AUM given by keeper
  function getSwapAmountOut(
    address _assetIn,
    uint256 _amountIn,
    address _assetOut,
    uint256 _protocolAUM
  ) public view returns (uint256, int256) {
    // Scope to avoid stack too deep
    {
      uint256 usdValIn = getQuote(_assetIn, _amountIn);
      uint256 assetOutPrice = getQuote(_assetOut, 10 ** assetInfo[_assetOut].assetDecimals);
      if (usdValIn == 0 || assetOutPrice == 0) return (0, int256(0));
    }
    ProcessParam memory processParamIn = ProcessParam({
      targetConc: assetInfo[_assetIn].targetConcentration,
      currentConc: _getAssetConcentration(_assetIn, _protocolAUM),
      usdValue: getQuote(_assetIn, _amountIn),
      sharesBeforeTax: 0,
      taxableAmount: 0,
      taxInUSD: 0,
      sharesAfterTax: 0
    });
    ProcessParam memory processParamOut = ProcessParam({
      targetConc: assetInfo[_assetOut].targetConcentration,
      currentConc: _getAssetConcentration(_assetOut, _protocolAUM),
      usdValue: 0,
      sharesBeforeTax: 0,
      taxableAmount: 0,
      taxInUSD: 0,
      sharesAfterTax: 0
    });
    uint256 usdAmountOut = _getSwapRate(
      processParamIn,
      processParamOut,
      _protocolAUM,
      protocolData.taxFactor,
      assetInfo[_assetIn].incentiveFactor,
      assetInfo[_assetOut].incentiveFactor
    );
    return (
      1e18 * usdAmountOut / getQuote(_assetOut, 1e18),
      int256(processParamIn.usdValue) - int256(usdAmountOut)
    );
  }
  /*//////////////////////////////////////////////////////////////
                            INTERNAL
  //////////////////////////////////////////////////////////////*/
  ///@notice Return asset concentration with keeper AUM
  ///@param _asset asset address
  ///@param _protocolAUM AUM given by keeper
  ///@return current concentration for an asset
  ///@dev    If price is inconsistent or not available, returns 0 -> needs proper handling
  function _getAssetConcentration(address _asset, uint256 _protocolAUM)
    internal
    view
    returns (uint256)
  {
    // To avoid division by 0
    if (_protocolAUM == 0 && protocolData.aum == 0) return 0;
    return (1e20 * getAssetAUM(_asset)) / _protocolAUM;
  }
  ///@notice Perform the comparison between AUM registry and one given by Keeper, return limited AUM
  /// if out of bounds
  function _AumIsInRange(uint256 _keeperAUM) internal view returns (bool, uint256) {
    uint16 maxAumDeviationAllowed = protocolData.maxAumDeviationAllowed;
    uint256 currAum = protocolData.aum;
    uint256 lowerBound = PercentageMath.percentSub(currAum, maxAumDeviationAllowed);
    uint256 upperBound = PercentageMath.percentAdd(currAum, maxAumDeviationAllowed);
    if (_keeperAUM < lowerBound) return (false, lowerBound);
    if (_keeperAUM > upperBound) return (false, upperBound);
    return (true, _keeperAUM);
  }
  function _checkIsSupported(address[] memory _assets) internal view {
    address notSupportedAsset = isAnyNotSupported(_assets);
    if (notSupportedAsset != address(0x0)) revert AssetNotSupported(notSupportedAsset);
  }
  function _checkIsNotQuarantined(address[] memory _assets) internal view {
    address quarantinedAsset = IRelayer(RELAYER).isAnyQuarantined(_assets);
    if (quarantinedAsset != address(0x0)) revert AssetIsQuarantined(quarantinedAsset);
  }
  function _checkIfSwapAllowed(address[] memory _assets) internal view {
    address notAllowedAsset = isSwapAllowed(_assets);
    if (notAllowedAsset != address(0x0)) revert SwapDisabled(notAllowedAsset);
  }
  /*//////////////////////////////////////////////////////////////
                            MODIFIERS
  //////////////////////////////////////////////////////////////*/
  modifier onlyRelayer() {
    if (msg.sender != RELAYER) revert Unauthorized();
    _;
  }
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
import {ERC20} from "solmate/tokens/ERC20.sol";
///@title TRSY contract
///@notice Handle the logic for minting and burning TRSY shares
abstract contract TRSY is ERC20 {
  /*//////////////////////////////////////////////////////////////
                                 CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/
  constructor() ERC20("TRSY", "TRSY", 18) {}
  /*//////////////////////////////////////////////////////////////
                                 INTERNAL
    //////////////////////////////////////////////////////////////*/
  ///@notice Convert the value of deposit into share of the protocol
  ///@param _usdValue usd value of the deposit
  ///@param _usdAUM AUM of the protocol in USD (given by keeper)
  ///@return TSRY share for an USD deposit
  function _convertToShares(uint256 _usdValue, uint256 _usdAUM) internal view returns (uint256) {
    uint256 supply = totalSupply;
    return supply == 0 ? _usdValue : (_usdValue * supply) / _usdAUM;
  }
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
import {AssetInfo} from "./Structs.sol";
import {AddressRegistry} from "./AddressRegistry.sol";
import {ProtocolState} from "./ProtocolState.sol";
import {Ownable} from "../utils/Ownable.sol";
import {BaseChecker} from "../utils/BaseChecker.sol";
import {IERC20Metadata} from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IUniswapV3PoolImmutables} from
  "lib/v3-core/contracts/interfaces/pool/IUniswapV3PoolImmutables.sol";
import {IRelayer} from "src/interfaces/IRelayer.sol";
///@title AssetRegistry contract
///@notice Handle logic and state for logging informations regarding the assets
abstract contract AssetRegistry is Ownable, BaseChecker, AddressRegistry, ProtocolState {
  /*//////////////////////////////////////////////////////////////
                                 STORAGE
    //////////////////////////////////////////////////////////////*/
  ///@notice Asset list;
  address[] public assetsList;
  ///@notice last block incentiveFactor was updated, safety measure
  uint128 public lastIncentiveUpdateBlock;
  int128 public incentiveCap;
  ///@notice Map asset address to struct containing info
  mapping(address => AssetInfo) public assetInfo;
  /*//////////////////////////////////////////////////////////////
                                 ERROR
    //////////////////////////////////////////////////////////////*/
  error PoolNotValid();
  error AssetSupported(address asset);
  error NotNormalized();
  error NotZero();
  error CoolDownPeriodActive();
  /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/
  event AssetAdded(address _asset);
  event IncentiveFactorUpdated(address indexed asset, int72 incentiveFactor);
  event TargetConcentrationsUpdated();
  event UniswapPoolUpdated(address indexed asset, address uniswapPool);
  event AssetRemoved(address indexed asset);
  /*//////////////////////////////////////////////////////////////
                                 ADD ASSETS
    //////////////////////////////////////////////////////////////*/
  ///@notice Add assets in batch
  ///@param _assets Array of assets to add
  ///@param _uniswapPools Array of address of uniswap Pool
  ///@dev   only uniswap pool and incentive factor are relevant in assetsInfo, the rest is retrieved
  /// on-chain
  function addAssets(address[] calldata _assets, address[] calldata _uniswapPools)
    external
    onlyOwner
  {
    if (_assets.length != _uniswapPools.length) revert InconsistentLengths();
    for (uint256 i; i < _assets.length; ++i) {
      _addAsset(_assets[i], _uniswapPools[i]);
    }
  }
  function _addAsset(address _asset, address _uniswapPool) private {
    if (assetInfo[_asset].isSupported) revert AssetSupported(_asset);
    assetInfo[_asset].isSupported = true;
    assetInfo[_asset].assetDecimals = IERC20Metadata(_asset).decimals();
    setUniswapPool(_asset, _uniswapPool);
    assetsList.push(_asset);
    emit AssetAdded(_asset);
  }
  ///@notice Removes asset from the protocol whitelist
  ///@param _assetIdx index of the asset in the assets array
  ///@dev only possible if there are no tokens of the asset held by the protocol anymore
  function removeAsset(uint256 _assetIdx) external onlyOwner {
    address asset = assetsList[_assetIdx];
    if (totalAssetAccounting[asset] != 0) revert NotZero();
    if (assetInfo[asset].targetConcentration != 0) revert NotZero();
    delete assetInfo[asset];
    assetsList[_assetIdx] = assetsList[assetsList.length - 1];
    assetsList.pop();
    emit AssetRemoved(asset);
  }
  /*//////////////////////////////////////////////////////////////
                                 SETTER
    //////////////////////////////////////////////////////////////*/
  ///@notice Set target concentration for all asset
  ///@param _targetConcentrations Target concentration (1e18 -> 1%)
  ///@dev 1e18 = 1%
  ///@dev targetConcentrations must have same length as assetsList -> can only update all conc at
  // once to enforce normalization
  function setTargetConcentrations(uint72[] calldata _targetConcentrations) external onlyOwner {
    if (_targetConcentrations.length != assetsList.length) revert InconsistentLengths();
    uint72 sum;
    for (uint256 i; i < _targetConcentrations.length; i++) {
      sum += _targetConcentrations[i];
    }
    if (sum > (1e20 + 1e10) || sum < (1e20 - 1e10)) revert NotNormalized();
    for (uint256 i; i < _targetConcentrations.length; i++) {
      assetInfo[assetsList[i]].targetConcentration = _targetConcentrations[i];
    }
    emit TargetConcentrationsUpdated();
  }
  ///@notice Set target concentration for an asset
  ///@param _asset Asset address
  ///@param _incentiveFactor IncentiveFactor (1e18 -> 1%)
  ///@dev 1e18 = 1%, max incentiveCap
  ///@dev Can only be called every 5 blocks, safety measure in case of compromised IncentiveManager
  function setIncentiveFactor(address _asset, int72 _incentiveFactor) external onlyIncentiveManager {
    if (int128(_incentiveFactor) > incentiveCap) revert ValueOutOfBounds();
    if (block.number < uint256(lastIncentiveUpdateBlock) + 5) revert CoolDownPeriodActive();
    lastIncentiveUpdateBlock = uint128(block.number);
    assetInfo[_asset].incentiveFactor = _incentiveFactor;
    emit IncentiveFactorUpdated(_asset, _incentiveFactor);
  }
  ///@notice Set maximum incentive factor
  ///@param _incentiveCap maximum incentive factor
  ///@dev Capped at 1e20 == 100%
  function setIncentiveCap(int128 _incentiveCap) external onlyOwner {
    if (_incentiveCap > 1e20) revert ValueOutOfBounds();
    incentiveCap = _incentiveCap;
  }
  ///@notice Set uniswap pool for an asset
  ///@param _asset Asset address
  ///@param _uniswapPool Uniswap pool address
  function setUniswapPool(address _asset, address _uniswapPool) public onlyOwner {
    if (_uniswapPool == address(0x0)) {
      assetInfo[_asset].uniswapPool = _uniswapPool;
    } else {
      address token0 = IUniswapV3PoolImmutables(_uniswapPool).token0();
      address token1 = IUniswapV3PoolImmutables(_uniswapPool).token1();
      address quoteToken;
      if (token0 == _asset) quoteToken = token1;
      else if (token1 == _asset) quoteToken = token0;
      else revert PoolNotValid();
      assetInfo[_asset].uniswapPool = _uniswapPool;
      assetInfo[_asset].uniswapQuoteToken = quoteToken;
      assetInfo[_asset].quoteTokenDecimals = IERC20Metadata(quoteToken).decimals();
    }
    emit UniswapPoolUpdated(_asset, _uniswapPool);
  }
  /*//////////////////////////////////////////////////////////////
                                GETTER
    //////////////////////////////////////////////////////////////*/
  ///@notice Get isSupported for an asset
  ///@param _assets asset addresses
  ///@return address of first not supported asset or address(0x0) if all supported
  function isAnyNotSupported(address[] memory _assets) public view returns (address) {
    for (uint256 i; i < _assets.length; i++) {
      if (!assetInfo[_assets[i]].isSupported) return _assets[i];
    }
    return address(0x0);
  }
  ///@notice Get isSwapAllowed for an asset array
  ///@param _assets asset addresses
  ///@return address of first not supported asset or address(0x0) if all supported
  function isSwapAllowed(address[] memory _assets) public view returns (address) {
    for (uint256 i; i < _assets.length; i++) {
      if (assetInfo[_assets[i]].incentiveFactor == -100e18) return _assets[i];
    }
    return address(0x0);
  }
  ///@notice Get number of asset decimals
  ///@param _asset Asset address
  ///@return number of decimals
  function getAssetDecimals(address _asset) external view returns (uint8) {
    return assetInfo[_asset].assetDecimals;
  }
  ///@notice Get number of assets in protocol
  function getAssetsListLength() public view returns (uint256) {
    return assetsList.length;
  }
  ///@dev caller has to be whitelisted manager on relayer
  modifier onlyIncentiveManager() {
    if (!IRelayer(RELAYER).isIncentiveManager(msg.sender)) revert Unauthorized();
    _;
  }
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
// Utils
import {Ownable} from "../utils/Ownable.sol";
// Interfaces
import {IOracle} from "../interfaces/IOracle.sol";
import {IGovernanceModule} from "../interfaces/IGovernanceModule.sol";
///@title AddressRegistry contract
///@notice Handle state and logic for external authorized call (mainly keeper) and the oracle module
abstract contract AddressRegistry is Ownable {
  /*//////////////////////////////////////////////////////////////
                            STORAGE
  //////////////////////////////////////////////////////////////*/
  ///@notice Address of the oracle module
  IOracle public oracleModule;
  ///@notice Governance Registry contract address interface
  IGovernanceModule public immutable GOVERNANCE_MODULE;
  address public RELAYER;
  /*//////////////////////////////////////////////////////////////
                            EVENTS
  //////////////////////////////////////////////////////////////*/
  event OracleModuleUpdated(address indexed oracleModule);
  /*//////////////////////////////////////////////////////////////
                        CONSTRUCTOR
  //////////////////////////////////////////////////////////////*/
  constructor(address _govModule, address _relayer) {
    GOVERNANCE_MODULE = IGovernanceModule(_govModule);
    RELAYER = _relayer;
  }
  /*//////////////////////////////////////////////////////////////
                            ACCESS
  //////////////////////////////////////////////////////////////*/
  function setRelayer(address _relayer) external onlyOwner {
    RELAYER = _relayer;
  }
  ///@notice Set the oracle address
  function setOracleModule(address _oracle) public onlyOwner {
    oracleModule = IOracle(_oracle);
    emit OracleModuleUpdated(_oracle);
  }
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
import {ProtocolData} from "./Structs.sol";
import {Ownable} from "../utils/Ownable.sol";
///@title ProtocolState contract
///@notice Protocol data storage
abstract contract ProtocolState is Ownable {
  /*//////////////////////////////////////////////////////////////
                                 STORAGE
    //////////////////////////////////////////////////////////////*/
  ///@notice Protocol data
  ProtocolData public protocolData;
  ///@notice Number of token in the protocol
  mapping(address => uint256) public totalAssetAccounting;
  ///@notice Number of token in the proxy
  mapping(address => uint256) public proxyAssetAccounting;
  /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/
  error ValueOutOfBounds();
  /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/
  event ProtocolAumUpdated(uint256);
  event MaxAumDeviationAllowedUpdated(uint16);
  event TaxFactorUpdated(uint72);
  event ManagementFeeUpdated(uint72);
  /*//////////////////////////////////////////////////////////////
                                 GETTER
    //////////////////////////////////////////////////////////////*/
  ///@notice Get number of token in the standard pool
  ///@param _asset asset address
  ///@return number of token in standard pool
  function standardAssetAccounting(address _asset) public view returns (uint256) {
    return totalAssetAccounting[_asset] - proxyAssetAccounting[_asset];
  }
  ///@notice Get protocolAUM in USD
  ///@return protocol AUM
  function getProtocolAUM() external view returns (uint256) {
    return protocolData.aum;
  }
  /*//////////////////////////////////////////////////////////////
                                 SETTER
    //////////////////////////////////////////////////////////////*/
  ///@notice Change the AUM's comparison deviation threshold
  ///@param threshold new threshold
  ///@dev 200 = 2 % of deviation
  function updateMaxAumDeviationAllowed(uint16 threshold) public onlyOwner {
    // We bound the threshold to 0.1 % to 5%
    if (threshold < 10 || threshold > 500) revert ValueOutOfBounds();
    protocolData.maxAumDeviationAllowed = threshold;
    emit MaxAumDeviationAllowedUpdated(threshold);
  }
  ///@notice set the tax factor
  ///@param _taxFactor new tax factor
  ///@dev 100% = 100e18
  function updateTaxFactor(uint72 _taxFactor) public onlyOwner {
    if (_taxFactor > 100e18) revert ValueOutOfBounds();
    protocolData.taxFactor = _taxFactor;
    emit TaxFactorUpdated(_taxFactor);
  }
  ///@notice change annual management fee
  ///@param _annualFee new annual fee
  ///@dev 100% = 1e18
  function updateManagementFee(uint72 _annualFee) public onlyOwner {
    // We bound the fee to 0 % to 5%
    if (_annualFee > 5e16) revert ValueOutOfBounds();
    protocolData.managementFee = _annualFee;
    emit ManagementFeeUpdated(_annualFee);
  }
  ///@notice Update last fee collection time to current timestamp
  function _updateLastFeeCollectionTime() internal {
    protocolData.lastFeeCollectionTime = uint48(block.timestamp);
  }
  ///@notice Update the protocol AUM
  function _updateProtocolAUM(uint256 _aum) internal {
    protocolData.aum = _aum;
    emit ProtocolAumUpdated(_aum);
  }
  function _increaseAssetTotalAmount(address[] memory _assets, uint256[] memory _amounts) internal {
    for (uint256 i; i < _assets.length; i++) {
      _increaseAssetTotalAmount(_assets[i], _amounts[i]);
    }
  }
  function _increaseAssetTotalAmount(address _asset, uint256 _amount) internal {
    totalAssetAccounting[_asset] += _amount;
  }
  function _increaseAssetProxyAmount(address[] memory _assets, uint256[] memory _amounts) internal {
    for (uint256 i; i < _assets.length; i++) {
      proxyAssetAccounting[_assets[i]] += _amounts[i];
    }
  }
  function _decreaseAssetTotalAmount(address[] memory _assets, uint256[] memory _amounts) internal {
    for (uint256 i; i < _assets.length; i++) {
      _decreaseAssetTotalAmount(_assets[i], _amounts[i]);
    }
  }
  function _decreaseAssetTotalAmount(address _asset, uint256 _amount) internal {
    totalAssetAccounting[_asset] -= _amount;
  }
  function _decreaseAssetProxyAmount(address[] memory _assets, uint256[] memory _amounts) internal {
    for (uint256 i; i < _assets.length; i++) {
      proxyAssetAccounting[_assets[i]] -= _amounts[i];
    }
  }
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
import {ProcessParam} from "./Structs.sol";
import {MathUtil} from "../utils/MathUtil.sol";
import "synthetix-v3/utils/core-contracts/contracts/utils/SafeCast.sol";
///@title Tax contract
///@notice Handle tax logic, for either a deposit or withdraw compute if the action is unabalacing
/// the protocol
///        if this is the case, the protocol will compute a tax applied on deposit or withdraw by
/// reducing the number of shares to mint
///        or by increasing the number of shares to burn. This tax is then minted to fyde contract.
/// The main logic is that for each action, we compute a taxable amount which is the amount that
/// unbalance the protocol for a given deposit or withdraw,
/// then we apply the tax on this taxable amount.
/// For the swap the logic is the same, we compute the tax and incentive for assetIn and that give
/// the value of assetOut,
/// for this value we compute the tax and incentive for assetOut.
/// SwapRate can be greater for assetOut in case there is no tax and some incentives, the same if no
/// tax no incentive, or lower if there is tax and no incentive.
abstract contract Tax {
  using SafeCastU256 for uint256;
  using SafeCastI256 for int256;
  /*//////////////////////////////////////////////////////////////
                                 MAIN
    //////////////////////////////////////////////////////////////*/
  function _getDepositTax(
    ProcessParam memory processParam,
    uint256 protocolAUM,
    uint256 totalUsdDeposit,
    uint256 taxFactor
  ) internal pure returns (ProcessParam memory) {
    if (processParam.targetConc == 0) {
      processParam.taxInUSD = processParam.usdValue;
      return processParam;
    }
    if (taxFactor == 0) return processParam;
    processParam = _computeDepositTaxableAmount(processParam, protocolAUM, totalUsdDeposit);
    if (processParam.taxableAmount == 0) return processParam;
    processParam = _computeDepositTaxInUSD(processParam, protocolAUM, totalUsdDeposit, taxFactor);
    return processParam;
  }
  function _getWithdrawTax(
    ProcessParam memory processParam,
    uint256 protocolAUM,
    uint256 totalUsdWithdraw,
    uint256 taxFactor
  ) internal pure returns (ProcessParam memory) {
    if (taxFactor == 0) return processParam;
    processParam = _computeWithdrawTaxableAmount(processParam, protocolAUM, totalUsdWithdraw);
    if (processParam.taxableAmount == 0) return processParam;
    processParam = _computeWithdrawTaxInUSD(processParam, protocolAUM, totalUsdWithdraw, taxFactor);
    return processParam;
  }
  function _getSwapRate(
    ProcessParam memory processParamIn,
    ProcessParam memory processParamOut,
    uint256 protocolAUM,
    uint256 taxFactor,
    int256 incentiveFactorIn,
    int256 incentiveFactorOut
  ) internal pure returns (uint256) {
    // Compute tax on deposit
    processParamIn = _getDepositTax(processParamIn, protocolAUM, 0, taxFactor);
    int256 valIn = incentiveFactorIn
      * int256(processParamIn.usdValue - processParamIn.taxableAmount) / int256(1e20);
    // usdValue adjusted with potential tax and incentive
    uint256 withdrawValOut = valIn >= 0
      ? processParamIn.usdValue - processParamIn.taxInUSD + valIn.toUint()
      : processParamIn.usdValue - processParamIn.taxInUSD - (-1 * valIn).toUint();
    processParamOut.usdValue = withdrawValOut;
    processParamOut = _getWithdrawTax(processParamOut, protocolAUM, 0, taxFactor);
    // usdValueOut adjusted with potential tax and incentive
    int256 valOut =
      incentiveFactorOut * int256(withdrawValOut - processParamOut.taxableAmount) / 1e20;
    uint256 usdValOut = valOut >= 0
      ? withdrawValOut - processParamOut.taxInUSD + valOut.toUint()
      : withdrawValOut - processParamOut.taxInUSD - (-1 * valOut).toUint();
    return usdValOut;
  }
  /*//////////////////////////////////////////////////////////////
                                 DEPOSIT
    //////////////////////////////////////////////////////////////*/
  function _computeDepositTaxableAmount(
    ProcessParam memory processParam,
    uint256 protocolAUM,
    uint256 totalUsdDeposit
  ) internal pure returns (ProcessParam memory) {
    int256 deltaConc = protocolAUM.toInt()
      * (processParam.currentConc.toInt() - processParam.targetConc.toInt()) / 1e20;
    int256 targetDeposit = totalUsdDeposit != 0
      ? processParam.targetConc.toInt() * totalUsdDeposit.toInt() / 1e20
      : int256(0);
    int256 tax = processParam.usdValue.toInt() + deltaConc - targetDeposit;
    processParam.taxableAmount =
      MathUtil.min(processParam.usdValue.toInt(), MathUtil.max(tax, int256(0))).toUint();
    return processParam;
  }
  function _computeDepositTaxInUSD(
    ProcessParam memory processParam,
    uint256 protocolAUM,
    uint256 totalUsdDeposit,
    uint256 taxFactor
  ) internal pure returns (ProcessParam memory) {
    uint256 numerator = (protocolAUM * processParam.currentConc / 1e20) + processParam.usdValue;
    uint256 denominator = (protocolAUM + totalUsdDeposit) * processParam.targetConc / 1e20;
    uint256 eq = (1e18 * numerator / denominator) - 1e18;
    uint256 tmpRes = MathUtil.min(eq, 1e18);
    uint256 taxPerc = taxFactor * tmpRes / 1e20; // 1e20 for applying expressing tax as a percentage
    processParam.taxInUSD = processParam.taxableAmount * taxPerc / 1e18;
    return processParam;
  }
  /*//////////////////////////////////////////////////////////////
                                 WITHDRAW
    //////////////////////////////////////////////////////////////*/
  function _computeWithdrawTaxableAmount(
    ProcessParam memory processParam,
    uint256 protocolAUM,
    uint256 totalUsdWithdraw
  ) internal pure returns (ProcessParam memory) {
    int256 deltaConc = protocolAUM.toInt()
      * (processParam.currentConc.toInt() - processParam.targetConc.toInt()) / 1e20;
    int256 targetDeposit = processParam.targetConc.toInt() * totalUsdWithdraw.toInt() / 1e20;
    int256 tax = processParam.usdValue.toInt() - deltaConc - targetDeposit;
    processParam.taxableAmount =
      MathUtil.min(processParam.usdValue.toInt(), MathUtil.max(tax, int256(0))).toUint();
    return processParam;
  }
  function _computeWithdrawTaxInUSD(
    ProcessParam memory processParam,
    uint256 protocolAUM,
    uint256 totalUsdWithdraw,
    uint256 taxFactor
  ) internal pure returns (ProcessParam memory) {
    int256 numerator =
      protocolAUM.toInt() * processParam.currentConc.toInt() / 1e20 - processParam.usdValue.toInt();
    int256 denominator =
      processParam.targetConc.toInt() * (protocolAUM.toInt() - totalUsdWithdraw.toInt()) / 1e20;
    int256 tmpRes = 1e18 - (1e18 * numerator / denominator);
    uint256 tmpRes2 = MathUtil.min(tmpRes.toUint(), 1e18);
    uint256 taxPerc = taxFactor * tmpRes2 / 1e20; // 1e20 for applying expressing tax as a
      // percentage
    processParam.taxInUSD = processParam.taxableAmount * taxPerc / 1e18;
    return processParam;
  }
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
/// Import from Core /////
import {AddressRegistry} from "./AddressRegistry.sol";
import {AssetRegistry} from "./AssetRegistry.sol";
import {ProtocolState} from "./ProtocolState.sol";
import {TRSY} from "./TRSY.sol";
/// Structs /////
import {RebalanceParam} from "./Structs.sol";
// Interface
import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
///@title GovernanceAccess contract
///@notice Exposes functions for the governance module
abstract contract GovernanceAccess is TRSY, AddressRegistry, ProtocolState, AssetRegistry {
  using SafeERC20 for IERC20;
  ///@notice Called by the governance module in order to transfer asset when proxy is funded
  ///@param _asset asset address
  ///@param _recipient user address
  ///@param _amount number of token
  function transferAsset(address _asset, address _recipient, uint256 _amount)
    external
    onlyGovernance
  {
    IERC20(_asset).safeTransfer(_recipient, _amount);
  }
  ///@notice Called by the governance module in order to update proxy Accounting after rebalancing
  ///@param _asset asset address
  ///@param _amount number of token
  function updateAssetProxyAmount(address _asset, uint256 _amount) external onlyGovernance {
    proxyAssetAccounting[_asset] = _amount;
  }
  ///@notice Called by the governance module in order get all variables for rebalancing in one call
  /// (save gas)
  ///@param _asset asset address
  ///@return RebalanceParam struct with rebalance parameters
  function getRebalanceParams(address _asset) external view returns (RebalanceParam memory) {
    return RebalanceParam(
      _asset,
      totalAssetAccounting[_asset],
      proxyAssetAccounting[_asset],
      getQuote(_asset, 1e18),
      0,
      1e18 * protocolData.aum / totalSupply
    );
  }
  function getQuote(address _asset, uint256 _amount) public view virtual returns (uint256);
  modifier onlyGovernance() {
    if (msg.sender != address(GOVERNANCE_MODULE)) revert Unauthorized();
    _;
  }
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
struct AssetInfo {
  uint72 targetConcentration;
  address uniswapPool;
  int72 incentiveFactor;
  uint8 assetDecimals;
  uint8 quoteTokenDecimals;
  address uniswapQuoteToken;
  bool isSupported;
}
struct ProtocolData {
  ///@notice Protocol AUM in USD
  uint256 aum;
  ///@notice multiplicator for the tax equation, 100% = 100e18
  uint72 taxFactor;
  ///@notice Max deviation allowed between AUM from keeper and registry
  uint16 maxAumDeviationAllowed; // Default val 200 == 2 %
  ///@notice block number where AUM was last updated
  uint48 lastAUMUpdateBlock;
  ///@notice annual fee on AUM, in % per year 100% = 100e18
  uint72 managementFee;
  ///@notice last block.timestamp when fee was collected
  uint48 lastFeeCollectionTime;
}
struct UserRequest {
  address asset;
  uint256 amount;
}
struct RequestData {
  uint32 id;
  address requestor;
  address[] assetIn;
  uint256[] amountIn;
  address[] assetOut;
  uint256[] amountOut;
  bool keepGovRights;
  uint256 slippageChecker;
}
struct RequestQ {
  uint64 start;
  uint64 end;
  mapping(uint64 => RequestData) requestData;
}
struct ProcessParam {
  uint256 targetConc;
  uint256 currentConc;
  uint256 usdValue;
  uint256 taxableAmount;
  uint256 taxInUSD;
  uint256 sharesBeforeTax;
  uint256 sharesAfterTax;
}
struct RebalanceParam {
  address asset;
  uint256 assetTotalAmount;
  uint256 assetProxyAmount;
  uint256 assetPrice;
  uint256 sTrsyTotalSupply;
  uint256 trsyPrice;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
///@title Ownable contract
/// @notice Simple 2step owner authorization combining solmate and OZ implementation
abstract contract Ownable {
  /*//////////////////////////////////////////////////////////////
                             STORAGE
    //////////////////////////////////////////////////////////////*/
  ///@notice Address of the owner
  address public owner;
  ///@notice Address of the pending owner
  address public pendingOwner;
  /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/
  event OwnershipTransferred(address indexed user, address indexed newOner);
  event OwnershipTransferStarted(address indexed user, address indexed newOwner);
  event OwnershipTransferCanceled(address indexed pendingOwner);
  /*//////////////////////////////////////////////////////////////
                                 ERROR
    //////////////////////////////////////////////////////////////*/
  error Unauthorized();
  /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/
  constructor(address _owner) {
    owner = _owner;
    emit OwnershipTransferred(address(0), _owner);
  }
  /*//////////////////////////////////////////////////////////////
                             OWNERSHIP LOGIC
    //////////////////////////////////////////////////////////////*/
  ///@notice Transfer ownership to a new address
  ///@param newOwner address of the new owner
  ///@dev newOwner have to acceptOwnership
  function transferOwnership(address newOwner) external onlyOwner {
    pendingOwner = newOwner;
    emit OwnershipTransferStarted(msg.sender, pendingOwner);
  }
  ///@notice NewOwner accept the ownership, it transfer the ownership to newOwner
  function acceptOwnership() external {
    if (msg.sender != pendingOwner) revert Unauthorized();
    address oldOwner = owner;
    owner = pendingOwner;
    delete pendingOwner;
    emit OwnershipTransferred(oldOwner, owner);
  }
  ///@notice Cancel the ownership transfer
  function cancelTransferOwnership() external onlyOwner {
    emit OwnershipTransferCanceled(pendingOwner);
    delete pendingOwner;
  }
  modifier onlyOwner() {
    if (msg.sender != owner) revert Unauthorized();
    _;
  }
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
library PercentageMath {
  ///\tCONSTANTS ///
  uint256 internal constant PERCENTAGE_FACTOR = 1e4; // 100.00%
  uint256 internal constant HALF_PERCENTAGE_FACTOR = 0.5e4; // 50.00%
  uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
  uint256 internal constant MAX_UINT256_MINUS_HALF_PERCENTAGE = 2 ** 256 - 1 - 0.5e4;
  /// INTERNAL ///
  ///@notice Check if value are within the range
  function _isInRange(uint256 valA, uint256 valB, uint256 deviationThreshold)
    internal
    pure
    returns (bool)
  {
    uint256 lowerBound = percentSub(valA, deviationThreshold);
    uint256 upperBound = percentAdd(valA, deviationThreshold);
    if (valB < lowerBound || valB > upperBound) return false;
    else return true;
  }
  /// @notice Executes a percentage addition (x * (1 + p)), rounded up.
  /// @param x The value to which to add the percentage.
  /// @param percentage The percentage of the value to add.
  /// @return y The result of the addition.
  function percentAdd(uint256 x, uint256 percentage) internal pure returns (uint256 y) {
    // Must revert if
    // PERCENTAGE_FACTOR + percentage > type(uint256).max
    //     or x * (PERCENTAGE_FACTOR + percentage) + HALF_PERCENTAGE_FACTOR > type(uint256).max
    // <=> percentage > type(uint256).max - PERCENTAGE_FACTOR
    //     or x > (type(uint256).max - HALF_PERCENTAGE_FACTOR) / (PERCENTAGE_FACTOR + percentage)
    // Note: PERCENTAGE_FACTOR + percentage >= PERCENTAGE_FACTOR > 0
    assembly {
      y := add(PERCENTAGE_FACTOR, percentage) // Temporary assignment to save gas.
      if or(
        gt(percentage, sub(MAX_UINT256, PERCENTAGE_FACTOR)),
        gt(x, div(MAX_UINT256_MINUS_HALF_PERCENTAGE, y))
      ) { revert(0, 0) }
      y := div(add(mul(x, y), HALF_PERCENTAGE_FACTOR), PERCENTAGE_FACTOR)
    }
  }
  /// @notice Executes a percentage subtraction (x * (1 - p)), rounded up.
  /// @param x The value to which to subtract the percentage.
  /// @param percentage The percentage of the value to subtract.
  /// @return y The result of the subtraction.
  function percentSub(uint256 x, uint256 percentage) internal pure returns (uint256 y) {
    // Must revert if
    // percentage > PERCENTAGE_FACTOR
    //     or x * (PERCENTAGE_FACTOR - percentage) + HALF_PERCENTAGE_FACTOR > type(uint256).max
    // <=> percentage > PERCENTAGE_FACTOR
    //     or ((PERCENTAGE_FACTOR - percentage) > 0 and x > (type(uint256).max -
    // HALF_PERCENTAGE_FACTOR) / (PERCENTAGE_FACTOR - percentage))
    assembly {
      y := sub(PERCENTAGE_FACTOR, percentage) // Temporary assignment to save gas.
      if or(
        gt(percentage, PERCENTAGE_FACTOR), mul(y, gt(x, div(MAX_UINT256_MINUS_HALF_PERCENTAGE, y)))
      ) { revert(0, 0) }
      y := div(add(mul(x, y), HALF_PERCENTAGE_FACTOR), PERCENTAGE_FACTOR)
    }
  }
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;
    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }
    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }
    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }
    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }
    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }
    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }
    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }
    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.
        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }
    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.
        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    }
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface 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 amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);
    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);
    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);
    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);
    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);
    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
//SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
import {AssetInfo} from "../core/Structs.sol";
interface IOracle {
  function getPriceInUSD(address, AssetInfo calldata) external view returns (uint256);
  function getGweiPrice() external view returns (uint256);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
import {RequestData, UserRequest} from "../core/Structs.sol";
interface IRelayer {
  function getNumPendingRequest() external view returns (uint256);
  function getRequest(uint64 idx) external view returns (RequestData memory);
  function requestGovernanceWithdraw(
    UserRequest memory _userRequest,
    address _user,
    uint256 _maxTRSYToPay
  ) external payable;
  function requestWithdraw(UserRequest[] memory _userRequest, uint256 _maxTRSYToPay)
    external
    payable;
  function requestDeposit(
    UserRequest[] memory _userRequest,
    bool _keepGovRights,
    uint256 _minTRSYExpected
  ) external payable;
  function requestSwap(
    address _assetIn,
    uint256 _amountIn,
    address _assetOut,
    uint256 _minAmountOut
  ) external payable;
  function processRequests(uint256 _protocolAUM) external;
  function isQuarantined(address _asset) external view returns (bool);
  function isIncentiveManager(address _incentiveManager) external view returns (bool);
  function MAX_ASSET_TO_REQUEST() external view returns (uint8);
  function actionToGasUsage(bytes32 _actionHash) external view returns (uint256);
  function isUser(address _asset) external view returns (bool);
  function isAnyQuarantined(address[] memory _assets) external view returns (address);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/
    event Transfer(address indexed from, address indexed to, uint256 amount);
    event Approval(address indexed owner, address indexed spender, uint256 amount);
    /*//////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/
    string public name;
    string public symbol;
    uint8 public immutable decimals;
    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;
    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/
    uint256 internal immutable INITIAL_CHAIN_ID;
    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
    mapping(address => uint256) public nonces;
    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/
    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;
        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }
    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/
    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }
    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;
        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }
        emit Transfer(msg.sender, to, amount);
        return true;
    }
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
        balanceOf[from] -= amount;
        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }
        emit Transfer(from, to, amount);
        return true;
    }
    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\\x19\\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );
            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
            allowance[recoveredAddress][spender] = value;
        }
        emit Approval(owner, spender, value);
    }
    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }
    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }
    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/
    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;
        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }
        emit Transfer(address(0), to, amount);
    }
    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;
        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }
        emit Transfer(from, address(0), amount);
    }
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
abstract contract BaseChecker {
  error ZeroParameter();
  error InconsistentLengths();
  function _checkZeroValue(uint256 val) internal pure {
    if (val == 0) revert ZeroParameter();
  }
  function _checkZeroAddress(address addr) internal pure {
    if (addr == address(0x0)) revert ZeroParameter();
  }
  function _checkForConsistentLength(address[] memory arr1, uint256[] memory arr2) internal pure {
    if (arr1.length != arr2.length) revert InconsistentLengths();
  }
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);
    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);
    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Pool state that never changes
/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values
interface IUniswapV3PoolImmutables {
    /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface
    /// @return The contract address
    function factory() external view returns (address);
    /// @notice The first of the two tokens of the pool, sorted by address
    /// @return The token contract address
    function token0() external view returns (address);
    /// @notice The second of the two tokens of the pool, sorted by address
    /// @return The token contract address
    function token1() external view returns (address);
    /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6
    /// @return The fee
    function fee() external view returns (uint24);
    /// @notice The pool tick spacing
    /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive
    /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ...
    /// This value is an int24 to avoid casting even though it is always positive.
    /// @return The tick spacing
    function tickSpacing() external view returns (int24);
    /// @notice The maximum amount of position liquidity that can use any tick in the range
    /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and
    /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool
    /// @return The max amount of liquidity per tick
    function maxLiquidityPerTick() external view returns (uint128);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
interface IGovernanceModule {
  function fyde() external view returns (address);
  function proxyImplementation() external view returns (address);
  function proxyBalance(address proxy, address asset) external view returns (uint256);
  function strsyBalance(address _user, address _govToken) external view returns (uint256 balance);
  function assetToStrsy(address _asset) external view returns (address);
  function userToProxy(address _user) external view returns (address);
  function proxyToUser(address _proxy) external view returns (address);
  function isOnGovernanceWhitelist(address _asset) external view returns (bool);
  function getAllGovUsers() external view returns (address[] memory);
  function isAnyNotOnGovWhitelist(address[] calldata _assets) external view returns (address);
  function getUserGTAllowance(uint256 _TRSYAmount, address _token) external view returns (uint256);
  function govDeposit(
    address _depositor,
    address[] calldata _govToken,
    uint256[] calldata _amount,
    uint256[] calldata _amountTRSY,
    uint256 _totalTRSY
  ) external returns (address proxy);
  function govWithdraw(
    address _user,
    address _asset,
    uint256 _amountToWithdraw,
    uint256 _trsyToBurn
  ) external;
  function onStrsyTransfer(address sender, address _recipient) external;
  function unstakeGov(uint256 _amount, address _asset) external;
  function rebalanceProxy(address _proxy, address _asset, address[] memory _usersToRebalance)
    external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/math/Math.sol)
pragma solidity 0.8.19;
/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library MathUtil {
  /**
   * @dev Returns the largest of two numbers.
   */
  function max(int256 a, int256 b) internal pure returns (int256) {
    return a >= b ? a : b;
  }
  /**
   * @dev Returns the smallest of two numbers.
   */
  function min(int256 a, int256 b) internal pure returns (int256) {
    return a < b ? a : b;
  }
  /**
   * @dev Returns the smallest of two numbers.
   */
  function min(uint256 a, uint256 b) internal pure returns (uint256) {
    return a < b ? a : b;
  }
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
/**
 * Utilities that convert numeric types avoiding silent overflows.
 */
import "./SafeCast/SafeCastU32.sol";
import "./SafeCast/SafeCastI32.sol";
import "./SafeCast/SafeCastI24.sol";
import "./SafeCast/SafeCastU56.sol";
import "./SafeCast/SafeCastI56.sol";
import "./SafeCast/SafeCastU64.sol";
import "./SafeCast/SafeCastI128.sol";
import "./SafeCast/SafeCastI256.sol";
import "./SafeCast/SafeCastU128.sol";
import "./SafeCast/SafeCastU160.sol";
import "./SafeCast/SafeCastU256.sol";
import "./SafeCast/SafeCastAddress.sol";
import "./SafeCast/SafeCastBytes32.sol";
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;
    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);
    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.
        return account.code.length > 0;
    }
    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");
        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }
    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }
    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }
    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }
    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }
    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }
    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }
    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }
    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }
    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }
    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }
    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
/**
 * @title See SafeCast.sol.
 */
library SafeCastU32 {
    error OverflowUint32ToInt32();
    function toInt(uint32 x) internal pure returns (int32) {
        // -------------------------------o=========>----------------------
        // ----------------------<========o========>x----------------------
        if (x > uint32(type(int32).max)) {
            revert OverflowUint32ToInt32();
        }
        return int32(x);
    }
    function to256(uint32 x) internal pure returns (uint256) {
        return uint256(x);
    }
    function to56(uint32 x) internal pure returns (uint56) {
        return uint56(x);
    }
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
/**
 * @title See SafeCast.sol.
 */
library SafeCastI32 {
    error OverflowInt32ToUint32();
    function toUint(int32 x) internal pure returns (uint32) {
        // ----------------------<========o========>----------------------
        // ----------------------xxxxxxxxxo=========>----------------------
        if (x < 0) {
            revert OverflowInt32ToUint32();
        }
        return uint32(x);
    }
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
/**
 * @title See SafeCast.sol.
 */
library SafeCastI24 {
    function to256(int24 x) internal pure returns (int256) {
        return int256(x);
    }
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
/**
 * @title See SafeCast.sol.
 */
library SafeCastU56 {
    error OverflowUint56ToInt56();
    function toInt(uint56 x) internal pure returns (int56) {
        // -------------------------------o=========>----------------------
        // ----------------------<========o========>x----------------------
        if (x > uint56(type(int56).max)) {
            revert OverflowUint56ToInt56();
        }
        return int56(x);
    }
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
/**
 * @title See SafeCast.sol.
 */
library SafeCastI56 {
    error OverflowInt56ToInt24();
    function to24(int56 x) internal pure returns (int24) {
        // ----------------------<========o========>-----------------------
        // ----------------------xxx<=====o=====>xxx-----------------------
        if (x < int(type(int24).min) || x > int(type(int24).max)) {
            revert OverflowInt56ToInt24();
        }
        return int24(x);
    }
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
/**
 * @title See SafeCast.sol.
 */
library SafeCastU64 {
    error OverflowUint64ToInt64();
    function toInt(uint64 x) internal pure returns (int64) {
        // -------------------------------o=========>----------------------
        // ----------------------<========o========>x----------------------
        if (x > uint64(type(int64).max)) {
            revert OverflowUint64ToInt64();
        }
        return int64(x);
    }
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
/**
 * @title See SafeCast.sol.
 */
library SafeCastI128 {
    error OverflowInt128ToUint128();
    error OverflowInt128ToInt32();
    function toUint(int128 x) internal pure returns (uint128) {
        // ----------------<==============o==============>-----------------
        // ----------------xxxxxxxxxxxxxxxo===============>----------------
        if (x < 0) {
            revert OverflowInt128ToUint128();
        }
        return uint128(x);
    }
    function to256(int128 x) internal pure returns (int256) {
        return int256(x);
    }
    function to32(int128 x) internal pure returns (int32) {
        // ----------------<==============o==============>-----------------
        // ----------------xxxxxxxxxxxx<==o==>xxxxxxxxxxxx-----------------
        if (x < int(type(int32).min) || x > int(type(int32).max)) {
            revert OverflowInt128ToInt32();
        }
        return int32(x);
    }
    function zero() internal pure returns (int128) {
        return int128(0);
    }
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
/**
 * @title See SafeCast.sol.
 */
library SafeCastI256 {
    error OverflowInt256ToUint256();
    error OverflowInt256ToInt128();
    error OverflowInt256ToInt24();
    function to128(int256 x) internal pure returns (int128) {
        // ----<==========================o===========================>----
        // ----xxxxxxxxxxxx<==============o==============>xxxxxxxxxxxxx----
        if (x < int256(type(int128).min) || x > int256(type(int128).max)) {
            revert OverflowInt256ToInt128();
        }
        return int128(x);
    }
    function to24(int256 x) internal pure returns (int24) {
        // ----<==========================o===========================>----
        // ----xxxxxxxxxxxxxxxxxxxx<======o=======>xxxxxxxxxxxxxxxxxxxx----
        if (x < int256(type(int24).min) || x > int256(type(int24).max)) {
            revert OverflowInt256ToInt24();
        }
        return int24(x);
    }
    function toUint(int256 x) internal pure returns (uint256) {
        // ----<==========================o===========================>----
        // ----xxxxxxxxxxxxxxxxxxxxxxxxxxxo===============================>
        if (x < 0) {
            revert OverflowInt256ToUint256();
        }
        return uint256(x);
    }
    function zero() internal pure returns (int256) {
        return int256(0);
    }
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
/**
 * @title See SafeCast.sol.
 */
library SafeCastU128 {
    error OverflowUint128ToInt128();
    function to256(uint128 x) internal pure returns (uint256) {
        return uint256(x);
    }
    function toInt(uint128 x) internal pure returns (int128) {
        // -------------------------------o===============>----------------
        // ----------------<==============o==============>x----------------
        if (x > uint128(type(int128).max)) {
            revert OverflowUint128ToInt128();
        }
        return int128(x);
    }
    function toBytes32(uint128 x) internal pure returns (bytes32) {
        return bytes32(uint256(x));
    }
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
/**
 * @title See SafeCast.sol.
 */
library SafeCastU160 {
    function to256(uint160 x) internal pure returns (uint256) {
        return uint256(x);
    }
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
/**
 * @title See SafeCast.sol.
 */
library SafeCastU256 {
    error OverflowUint256ToUint128();
    error OverflowUint256ToInt256();
    error OverflowUint256ToUint64();
    error OverflowUint256ToUint32();
    error OverflowUint256ToUint160();
    function to128(uint256 x) internal pure returns (uint128) {
        // -------------------------------o===============================>
        // -------------------------------o===============>xxxxxxxxxxxxxxxx
        if (x > type(uint128).max) {
            revert OverflowUint256ToUint128();
        }
        return uint128(x);
    }
    function to64(uint256 x) internal pure returns (uint64) {
        // -------------------------------o===============================>
        // -------------------------------o======>xxxxxxxxxxxxxxxxxxxxxxxxx
        if (x > type(uint64).max) {
            revert OverflowUint256ToUint64();
        }
        return uint64(x);
    }
    function to32(uint256 x) internal pure returns (uint32) {
        // -------------------------------o===============================>
        // -------------------------------o===>xxxxxxxxxxxxxxxxxxxxxxxxxxxx
        if (x > type(uint32).max) {
            revert OverflowUint256ToUint32();
        }
        return uint32(x);
    }
    function to160(uint256 x) internal pure returns (uint160) {
        // -------------------------------o===============================>
        // -------------------------------o==================>xxxxxxxxxxxxx
        if (x > type(uint160).max) {
            revert OverflowUint256ToUint160();
        }
        return uint160(x);
    }
    function toBytes32(uint256 x) internal pure returns (bytes32) {
        return bytes32(x);
    }
    function toInt(uint256 x) internal pure returns (int256) {
        // -------------------------------o===============================>
        // ----<==========================o===========================>xxxx
        if (x > uint256(type(int256).max)) {
            revert OverflowUint256ToInt256();
        }
        return int256(x);
    }
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
/**
 * @title See SafeCast.sol.
 */
library SafeCastAddress {
    function toBytes32(address x) internal pure returns (bytes32) {
        return bytes32(uint256(uint160(x)));
    }
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
/**
 * @title See SafeCast.sol.
 */
library SafeCastBytes32 {
    function toAddress(bytes32 x) internal pure returns (address) {
        return address(uint160(uint256(x)));
    }
    function toUint(bytes32 x) internal pure returns (uint) {
        return uint(x);
    }
}