creation_sourcecode: // SPDX-License-Identifier: AGPL-3.0
  pragma solidity 0.8.21;
  
  
  interface IDebtManager {
      function vault() external view returns (address);
  
      function aprOracle() external view returns (address);
  
      function siloToStrategy(address _silo) external view returns (address);
  
      function utilizationTargets(address _strategy) external view returns (uint256);
  
      function requestLiquidity(uint256 _amount, address _silo, uint256 _slippage) external;
  }
  
  // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
  /**
   * @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);
  }
  
  // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.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);
  }
  
  interface IERC4626 is IERC20, IERC20Metadata {
      event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
  
      event Withdraw(
          address indexed sender,
          address indexed receiver,
          address indexed owner,
          uint256 assets,
          uint256 shares
      );
  
      /**
       * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
       *
       * - MUST be an ERC-20 token contract.
       * - MUST NOT revert.
       */
      function asset() external view returns (address assetTokenAddress);
  
      /**
       * @dev Returns the total amount of the underlying asset that is “managed” by Vault.
       *
       * - SHOULD include any compounding that occurs from yield.
       * - MUST be inclusive of any fees that are charged against assets in the Vault.
       * - MUST NOT revert.
       */
      function totalAssets() external view returns (uint256 totalManagedAssets);
  
      /**
       * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
       * scenario where all the conditions are met.
       *
       * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
       * - MUST NOT show any variations depending on the caller.
       * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
       * - MUST NOT revert.
       *
       * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
       * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
       * from.
       */
      function convertToShares(uint256 assets) external view returns (uint256 shares);
  
      /**
       * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
       * scenario where all the conditions are met.
       *
       * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
       * - MUST NOT show any variations depending on the caller.
       * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
       * - MUST NOT revert.
       *
       * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
       * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
       * from.
       */
      function convertToAssets(uint256 shares) external view returns (uint256 assets);
  
      /**
       * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
       * through a deposit call.
       *
       * - MUST return a limited value if receiver is subject to some deposit limit.
       * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
       * - MUST NOT revert.
       */
      function maxDeposit(address receiver) external view returns (uint256 maxAssets);
  
      /**
       * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
       * current on-chain conditions.
       *
       * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
       *   call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
       *   in the same transaction.
       * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
       *   deposit would be accepted, regardless if the user has enough tokens approved, etc.
       * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
       * - MUST NOT revert.
       *
       * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
       * share price or some other type of condition, meaning the depositor will lose assets by depositing.
       */
      function previewDeposit(uint256 assets) external view returns (uint256 shares);
  
      /**
       * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
       *
       * - MUST emit the Deposit event.
       * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
       *   deposit execution, and are accounted for during deposit.
       * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
       *   approving enough underlying tokens to the Vault contract, etc).
       *
       * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
       */
      function deposit(uint256 assets, address receiver) external returns (uint256 shares);
  
      /**
       * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
       * - MUST return a limited value if receiver is subject to some mint limit.
       * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
       * - MUST NOT revert.
       */
      function maxMint(address receiver) external view returns (uint256 maxShares);
  
      /**
       * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
       * current on-chain conditions.
       *
       * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
       *   in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
       *   same transaction.
       * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
       *   would be accepted, regardless if the user has enough tokens approved, etc.
       * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
       * - MUST NOT revert.
       *
       * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
       * share price or some other type of condition, meaning the depositor will lose assets by minting.
       */
      function previewMint(uint256 shares) external view returns (uint256 assets);
  
      /**
       * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
       *
       * - MUST emit the Deposit event.
       * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
       *   execution, and are accounted for during mint.
       * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
       *   approving enough underlying tokens to the Vault contract, etc).
       *
       * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
       */
      function mint(uint256 shares, address receiver) external returns (uint256 assets);
  
      /**
       * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
       * Vault, through a withdraw call.
       *
       * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
       * - MUST NOT revert.
       */
      function maxWithdraw(address owner) external view returns (uint256 maxAssets);
  
      /**
       * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
       * given current on-chain conditions.
       *
       * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
       *   call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
       *   called
       *   in the same transaction.
       * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
       *   the withdrawal would be accepted, regardless if the user has enough shares, etc.
       * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
       * - MUST NOT revert.
       *
       * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
       * share price or some other type of condition, meaning the depositor will lose assets by depositing.
       */
      function previewWithdraw(uint256 assets) external view returns (uint256 shares);
  
      /**
       * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
       *
       * - MUST emit the Withdraw event.
       * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
       *   withdraw execution, and are accounted for during withdraw.
       * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
       *   not having enough shares, etc).
       *
       * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
       * Those methods should be performed separately.
       */
      function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
  
      /**
       * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
       * through a redeem call.
       *
       * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
       * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
       * - MUST NOT revert.
       */
      function maxRedeem(address owner) external view returns (uint256 maxShares);
  
      /**
       * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
       * given current on-chain conditions.
       *
       * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
       *   in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
       *   same transaction.
       * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
       *   redemption would be accepted, regardless if the user has enough shares, etc.
       * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
       * - MUST NOT revert.
       *
       * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
       * share price or some other type of condition, meaning the depositor will lose assets by redeeming.
       */
      function previewRedeem(uint256 shares) external view returns (uint256 assets);
  
      /**
       * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
       *
       * - MUST emit the Withdraw event.
       * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
       *   redeem execution, and are accounted for during redeem.
       * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
       *   not having enough shares, etc).
       *
       * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
       * Those methods should be performed separately.
       */
      function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
  }
  
  interface IVault is IERC4626 {
      // STRATEGY EVENTS
      event StrategyChanged(address indexed strategy, uint256 change_type);
      event StrategyReported(
          address indexed strategy,
          uint256 gain,
          uint256 loss,
          uint256 current_debt,
          uint256 protocol_fees,
          uint256 total_fees,
          uint256 total_refunds
      );
      // DEBT MANAGEMENT EVENTS
      event DebtUpdated(
          address indexed strategy,
          uint256 current_debt,
          uint256 new_debt
      );
      // ROLE UPDATES
      event RoleSet(address indexed account, uint256 role);
      event RoleStatusChanged(uint256 role, uint256 status);
      event UpdateRoleManager(address indexed role_manager);
  
      event UpdateAccountant(address indexed accountant);
      event UpdateDefaultQueue(address[] new_default_queue);
      event UpdateUseDefaultQueue(bool use_default_queue);
      event UpdatedMaxDebtForStrategy(
          address indexed sender,
          address indexed strategy,
          uint256 new_debt
      );
      event UpdateDepositLimit(uint256 deposit_limit);
      event UpdateMinimumTotalIdle(uint256 minimum_total_idle);
      event UpdateProfitMaxUnlockTime(uint256 profit_max_unlock_time);
      event DebtPurchased(address indexed strategy, uint256 amount);
      event Shutdown();
  
      struct StrategyParams {
          uint256 activation;
          uint256 last_report;
          uint256 current_debt;
          uint256 max_debt;
      }
  
      function FACTORY() external view returns (uint256);
  
      function strategies(address) external view returns (StrategyParams memory);
  
      function default_queue(uint256) external view returns (address);
  
      function use_default_queue() external view returns (bool);
  
      function total_supply() external view returns (uint256);
  
      function minimum_total_idle() external view returns (uint256);
  
      function deposit_limit() external view returns (uint256);
  
      function deposit_limit_module() external view returns (address);
  
      function withdraw_limit_module() external view returns (address);
  
      function accountant() external view returns (address);
  
      function roles(address) external view returns (uint256);
  
      function open_roles(uint256) external view returns (bool);
  
      function role_manager() external view returns (address);
  
      function future_role_manager() external view returns (address);
  
      function isShutdown() external view returns (bool);
  
      function nonces(address) external view returns (uint256);
  
      function set_accountant(address new_accountant) external;
  
      function set_default_queue(address[] memory new_default_queue) external;
  
      function set_use_default_queue(bool) external;
  
      function set_deposit_limit(uint256 deposit_limit) external;
  
      function set_deposit_limit_module(
          address new_deposit_limit_module
      ) external;
  
      function set_withdraw_limit_module(
          address new_withdraw_limit_module
      ) external;
  
      function set_minimum_total_idle(uint256 minimum_total_idle) external;
  
      function setProfitMaxUnlockTime(
          uint256 new_profit_max_unlock_time
      ) external;
  
      function set_role(address account, uint256 role) external;
  
      function add_role(address account, uint256 role) external;
  
      function remove_role(address account, uint256 role) external;
  
      function set_open_role(uint256 role) external;
  
      function close_open_role(uint256 role) external;
  
      function transfer_role_manager(address role_manager) external;
  
      function accept_role_manager() external;
  
      function unlockedShares() external view returns (uint256);
  
      function pricePerShare() external view returns (uint256);
  
      function get_default_queue() external view returns (address[] memory);
  
      function process_report(
          address strategy
      ) external returns (uint256, uint256);
  
      function buy_debt(address strategy, uint256 amount) external;
  
      function add_strategy(address new_strategy) external;
  
      function revoke_strategy(address strategy) external;
  
      function force_revoke_strategy(address strategy) external;
  
      function update_max_debt_for_strategy(
          address strategy,
          uint256 new_max_debt
      ) external;
  
      function update_debt(
          address strategy,
          uint256 target_debt
      ) external returns (uint256);
  
      function shutdown_vault() external;
  
      function totalIdle() external view returns (uint256);
  
      function totalDebt() external view returns (uint256);
  
      function apiVersion() external view returns (string memory);
  
      function assess_share_of_unrealised_losses(
          address strategy,
          uint256 assets_needed
      ) external view returns (uint256);
  
      function profitMaxUnlockTime() external view returns (uint256);
  
      function fullProfitUnlockDate() external view returns (uint256);
  
      function profitUnlockingRate() external view returns (uint256);
  
      function lastProfitUpdate() external view returns (uint256);
  
      //// NON-STANDARD ERC-4626 FUNCTIONS \\\\
  
      function withdraw(
          uint256 assets,
          address receiver,
          address owner,
          uint256 max_loss
      ) external returns (uint256);
  
      function withdraw(
          uint256 assets,
          address receiver,
          address owner,
          uint256 max_loss,
          address[] memory strategies
      ) external returns (uint256);
  
      function redeem(
          uint256 shares,
          address receiver,
          address owner,
          uint256 max_loss
      ) external returns (uint256);
  
      function redeem(
          uint256 shares,
          address receiver,
          address owner,
          uint256 max_loss,
          address[] memory strategies
      ) external returns (uint256);
  
      function maxWithdraw(
          address owner,
          uint256 max_loss
      ) external view returns (uint256);
  
      function maxWithdraw(
          address owner,
          uint256 max_loss,
          address[] memory strategies
      ) external view returns (uint256);
  
      function maxRedeem(
          address owner,
          uint256 max_loss
      ) external view returns (uint256);
  
      function maxRedeem(
          address owner,
          uint256 max_loss,
          address[] memory strategies
      ) external view returns (uint256);
  
      //// NON-STANDARD ERC-20 FUNCTIONS \\\\
  
      function increaseAllowance(
          address spender,
          uint256 amount
      ) external returns (bool);
  
      function decreaseAllowance(
          address spender,
          uint256 amount
      ) external returns (bool);
  
      function DOMAIN_SEPARATOR() external view returns (bytes32);
  
      function permit(
          address owner,
          address spender,
          uint256 amount,
          uint256 deadline,
          uint8 v,
          bytes32 r,
          bytes32 s
      ) external returns (bool);
  }
  
  struct VaultAccount {
      uint128 amount; // Total amount, analogous to market cap
      uint128 shares; // Total shares, analogous to shares outstanding
  }
  
  interface ISturdyPair {
      struct CurrentRateInfo {
          uint32 lastBlock;
          uint32 feeToProtocolRate; // Fee amount 1e5 precision
          uint64 lastTimestamp;
          uint64 ratePerSec;
          uint64 fullUtilizationRate;
      }
  
      function CIRCUIT_BREAKER_ADDRESS() external view returns (address);
  
      function COMPTROLLER_ADDRESS() external view returns (address);
  
      function DEPLOYER_ADDRESS() external view returns (address);
  
      function FRAXLEND_WHITELIST_ADDRESS() external view returns (address);
  
      function timelockAddress() external view returns (address);
  
      function addCollateral(uint256 _collateralAmount, address _borrower) external;
      
      function addInterest(
          bool _returnAccounting
      )
          external
          returns (
              uint256 _interestEarned,
              uint256 _feesAmount,
              uint256 _feesShare,
              CurrentRateInfo memory _currentRateInfo,
              VaultAccount memory _totalAsset,
              VaultAccount memory _totalBorrow
          );
  
      function allowance(address owner, address spender) external view returns (uint256);
  
      function approve(address spender, uint256 amount) external returns (bool);
  
      function approvedBorrowers(address) external view returns (bool);
  
      function approvedLenders(address) external view returns (bool);
  
      function approveBorrowDelegation(address _delegatee, uint256 _amount) external;
  
      function asset() external view returns (address);
  
      function balanceOf(address account) external view returns (uint256);
  
      function borrowAsset(
          uint256 _borrowAmount,
          uint256 _collateralAmount,
          address _receiver
      ) external returns (uint256 _shares);
  
      function borrowAssetOnBehalfOf(
          uint256 _borrowAmount,
          address _onBehalfOf
      ) external returns (uint256 _shares);
  
      function borrowerWhitelistActive() external view returns (bool);
  
      function changeFee(uint32 _newFee) external;
  
      function cleanLiquidationFee() external view returns (uint256);
  
      function collateralContract() external view returns (address);
  
      function currentRateInfo()
          external
          view
          returns (
              uint32 lastBlock,
              uint32 feeToProtocolRate,
              uint64 lastTimestamp,
              uint64 ratePerSec,
              uint64 fullUtilizationRate
          );
  
      function decimals() external view returns (uint8);
  
      function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
  
      function deposit(uint256 _amount, address _receiver) external returns (uint256 _sharesReceived);
  
      function dirtyLiquidationFee() external view returns (uint256);
  
      function exchangeRateInfo() external view returns (address oracle, uint32 maxOracleDeviation, uint184 lastTimestamp, uint256 lowExchangeRate, uint256 highExchangeRate);
  
      function getConstants()
          external
          pure
          returns (
              uint256 _LTV_PRECISION,
              uint256 _LIQ_PRECISION,
              uint256 _UTIL_PREC,
              uint256 _FEE_PRECISION,
              uint256 _EXCHANGE_PRECISION,
              uint256 _DEVIATION_PRECISION,
              uint256 _RATE_PRECISION,
              uint256 _MAX_PROTOCOL_FEE
          );
  
      function getImmutableAddressBool()
          external
          view
          returns (
              address _assetContract,
              address _collateralContract,
              address _oracleMultiply,
              address _oracleDivide,
              address _rateContract,
              address _DEPLOYER_CONTRACT,
              address _COMPTROLLER_ADDRESS,
              address _FRAXLEND_WHITELIST,
              bool _borrowerWhitelistActive,
              bool _lenderWhitelistActive
          );
  
      function getImmutableUint256()
          external
          view
          returns (
              uint256 _oracleNormalization,
              uint256 _maxLTV,
              uint256 _cleanLiquidationFee,
              uint256 _maturityDate,
              uint256 _penaltyRate
          );
  
      function getPairAccounting()
          external
          view
          returns (
              uint128 _totalAssetAmount,
              uint128 _totalAssetShares,
              uint128 _totalBorrowAmount,
              uint128 _totalBorrowShares,
              uint256 _totalCollateral
          );
  
      function getUserSnapshot(
          address _address
      ) external view returns (uint256 _userAssetShares, uint256 _userBorrowShares, uint256 _userCollateralBalance);
  
      function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
  
      function lenderWhitelistActive() external view returns (bool);
  
      function leveragedPosition(
          address _swapperAddress,
          uint256 _borrowAmount,
          uint256 _initialCollateralAmount,
          uint256 _amountCollateralOutMin,
          address[] memory _path
      ) external returns (uint256 _totalCollateralBalance);
  
      function liquidate(
          uint128 _sharesToLiquidate,
          uint256 _deadline,
          address _borrower
      ) external returns (uint256 _collateralForLiquidator);
  
      function maturityDate() external view returns (uint256);
  
      function maxLTV() external view returns (uint256);
  
      function maxOracleDelay() external view returns (uint256);
  
      function name() external view returns (string memory);
  
      function oracleDivide() external view returns (address);
  
      function oracleMultiply() external view returns (address);
  
      function oracleNormalization() external view returns (uint256);
  
      function owner() external view returns (address);
  
      function pause() external;
  
      function paused() external view returns (bool);
  
      function penaltyRate() external view returns (uint256);
  
      function rateContract() external view returns (address);
  
      function redeem(uint256 _shares, address _receiver, address _owner) external returns (uint256 _amountToReturn);
  
      function removeCollateral(uint256 _collateralAmount, address _receiver) external;
  
      function removeCollateralFrom(
          uint256 _collateralAmount,
          address _receiver,
          address _borrower
      ) external;
  
      function setWhitelistedDelegators(address _delegator, bool _enabled) external;
  
      function renounceOwnership() external;
  
      function repayAsset(uint256 _shares, address _borrower) external returns (uint256 _amountToRepay);
  
      function repayAssetWithCollateral(
          address _swapperAddress,
          uint256 _collateralToSwap,
          uint256 _amountAssetOutMin,
          address[] memory _path
      ) external returns (uint256 _amountAssetOut);
  
      function setApprovedBorrowers(address[] memory _borrowers, bool _approval) external;
  
      function setApprovedLenders(address[] memory _lenders, bool _approval) external;
  
      function setMaxOracleDelay(uint256 _newDelay) external;
  
      function setSwapper(address _swapper, bool _approval) external;
  
      function setTimelock(address _newAddress) external;
  
      function swappers(address) external view returns (bool);
  
      function symbol() external view returns (string memory);
  
      function toAssetAmount(
          uint256 _shares,
          bool _roundUp,
          bool _previewInterest
      ) external view returns (uint256);
  
      function toAssetShares(
          uint256 _amount,
          bool _roundUp,
          bool _previewInterest
      ) external view returns (uint256);
  
      function toBorrowAmount(
          uint256 _shares,
          bool _roundUp,
          bool _previewInterest
      ) external view returns (uint256 _amount);
  
      function toBorrowShares(
          uint256 _amount,
          bool _roundUp,
          bool _previewInterest
      ) external view returns (uint256 _shares);
  
      function totalAsset() external view returns (uint128 amount, uint128 shares);
  
      function totalBorrow() external view returns (uint128 amount, uint128 shares);
  
      function totalCollateral() external view returns (uint256);
  
      function totalSupply() external view returns (uint256);
  
      function transfer(address to, uint256 amount) external returns (bool);
  
      function transferFrom(address from, address to, uint256 amount) external returns (bool);
  
      function transferOwnership(address newOwner) external;
  
      function unpause() external;
  
      function updateExchangeRate()
          external
          returns (bool _isBorrowAllowed, uint256 _lowExchangeRate, uint256 _highExchangeRate);
  
      function userBorrowShares(address) external view returns (uint256);
  
      function userCollateralBalance(address) external view returns (uint256);
  
      function version() external pure returns (uint256 _major, uint256 _minor, uint256 _patch);
  
      function withdrawFees(uint128 _shares, address _recipient) external returns (uint256 _amountToTransfer);
  
      function isInterestPaused() external view returns (bool);
  }
  
  // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
  /**
   * @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);
  }
  
  // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
  /**
   * @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);
          }
      }
  }
  
  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));
      }
  }
  
  abstract contract ReentrancyGuard {
      // Booleans are more expensive than uint256 or any type that takes up a full
      // word because each write operation emits an extra SLOAD to first read the
      // slot's contents, replace the bits taken up by the boolean, and then write
      // back. This is the compiler's defense against contract upgrades and
      // pointer aliasing, and it cannot be disabled.
  
      // The values being non-zero value makes deployment a bit more expensive,
      // but in exchange the refund on every call to nonReentrant will be lower in
      // amount. Since refunds are capped to a percentage of the total
      // transaction's gas, it is best to keep them low in cases like this one, to
      // increase the likelihood of the full refund coming into effect.
      uint256 private constant _NOT_ENTERED = 1;
      uint256 private constant _ENTERED = 2;
  
      uint256 private _status;
  
      constructor() {
          _status = _NOT_ENTERED;
      }
  
      /**
       * @dev Prevents a contract from calling itself, directly or indirectly.
       * Calling a `nonReentrant` function from another `nonReentrant`
       * function is not supported. It is possible to prevent this from happening
       * by making the `nonReentrant` function external, and making it call a
       * `private` function that does the actual work.
       */
      modifier nonReentrant() {
          _nonReentrantBefore();
          _;
          _nonReentrantAfter();
      }
  
      function _nonReentrantBefore() private {
          // On the first call to nonReentrant, _status will be _NOT_ENTERED
          require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
  
          // Any calls to nonReentrant after this point will fail
          _status = _ENTERED;
      }
  
      function _nonReentrantAfter() private {
          // By storing the original value once again, a refund is triggered (see
          // https://eips.ethereum.org/EIPS/eip-2200)
          _status = _NOT_ENTERED;
      }
  
      /**
       * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
       * `nonReentrant` function in the call stack.
       */
      function _reentrancyGuardEntered() internal view returns (bool) {
          return _status == _ENTERED;
      }
  }
  
  // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
  /**
   * @dev Provides information about the current execution context, including the
   * sender of the transaction and its data. While these are generally available
   * via msg.sender and msg.data, they should not be accessed in such a direct
   * manner, since when dealing with meta-transactions the account sending and
   * paying for execution may not be the actual sender (as far as an application
   * is concerned).
   *
   * This contract is only required for intermediate, library-like contracts.
   */
  abstract contract Context {
      function _msgSender() internal view virtual returns (address) {
          return msg.sender;
      }
  
      function _msgData() internal view virtual returns (bytes calldata) {
          return msg.data;
      }
  }
  
  abstract contract Ownable is Context {
      address private _owner;
  
      event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
  
      /**
       * @dev Initializes the contract setting the deployer as the initial owner.
       */
      constructor() {
          _transferOwnership(_msgSender());
      }
  
      /**
       * @dev Throws if called by any account other than the owner.
       */
      modifier onlyOwner() {
          _checkOwner();
          _;
      }
  
      /**
       * @dev Returns the address of the current owner.
       */
      function owner() public view virtual returns (address) {
          return _owner;
      }
  
      /**
       * @dev Throws if the sender is not the owner.
       */
      function _checkOwner() internal view virtual {
          require(owner() == _msgSender(), "Ownable: caller is not the owner");
      }
  
      /**
       * @dev Leaves the contract without owner. It will not be possible to call
       * `onlyOwner` functions. Can only be called by the current owner.
       *
       * NOTE: Renouncing ownership will leave the contract without an owner,
       * thereby disabling any functionality that is only available to the owner.
       */
      function renounceOwnership() public virtual onlyOwner {
          _transferOwnership(address(0));
      }
  
      /**
       * @dev Transfers ownership of the contract to a new account (`newOwner`).
       * Can only be called by the current owner.
       */
      function transferOwnership(address newOwner) public virtual onlyOwner {
          require(newOwner != address(0), "Ownable: new owner is the zero address");
          _transferOwnership(newOwner);
      }
  
      /**
       * @dev Transfers ownership of the contract to a new account (`newOwner`).
       * Internal function without access restriction.
       */
      function _transferOwnership(address newOwner) internal virtual {
          address oldOwner = _owner;
          _owner = newOwner;
          emit OwnershipTransferred(oldOwner, newOwner);
      }
  }
  
  contract SiloGateway is ReentrancyGuard, Ownable {
      using SafeERC20 for IERC20;
  
      uint256 private constant UTIL_PREC = 1e5;
  
      // DebtManager address
      IDebtManager public immutable manager;
      // Vault address
      IVault public immutable vault;
      // Vault's asset which is lending or borrowing
      address public immutable asset;
      // The slippage percent value in case of requestLiquidity()
      uint256 public slippage;
      // JIT caller -> bool
      mapping(address => bool) public whitelistedCaller;
  
      event SetSlippage(uint256 slippage);
      event SetWhitelistedCaller(address caller, bool enabled);
  
      error AG_INVALID_CONFIGURATION();
      error AG_TOTAL_ASSET_IS_ZERO();
  
      constructor(IDebtManager _manager) {
          manager = _manager;
          vault = IVault(_manager.vault());
          asset = vault.asset();
      }
  
      /**
       * @dev Set the slippage percent value
       * - Caller is Admin
       * @param _slippage The slippage percent value, at most 0.01%
       */
      function setSlippage(uint256 _slippage) external payable onlyOwner {
          if (_slippage >= 10) revert AG_INVALID_CONFIGURATION();
  
          slippage = _slippage;
  
          emit SetSlippage(_slippage);
      }
  
      /**
       * @dev Set the whitelisted caller.
       * - Caller is Admin
       * @param _caller The JIT caller address.
       * @param _enabled True if whitelisted caller, else false.
       */
      function setWhitelistedCaller(
          address _caller,
          bool _enabled
      ) external payable onlyOwner {
          whitelistedCaller[_caller] = _enabled;
  
          emit SetWhitelistedCaller(_caller, _enabled);
      }
  
      /**
       * @dev Borrow asset from `_silo`
       *      If there is not enough liquidity in silo, it would try to make enough liquidity via vault.
       * - Caller is anyone but if `msg.sender` != `_borrower`, only whitelisted caller.
       * @param _silo The silo address.
       * @param _borrowAmount The borrowing amount.
       * @param _collateralAmount The collateral amount, if not zero, perform deposit collateral first.
       * @param _collateralAsset The collateral asset address.
       * @param _borrower The borrower address.
       * @param _receiver The receiver address of borrowing asset.
       */
      function borrowAsset(
          address _silo,
          uint256 _borrowAmount,
          uint256 _collateralAmount,
          address _collateralAsset,
          address _borrower,
          address _receiver
      ) external nonReentrant {
          if (_borrower != msg.sender) {
              // Only whitelisted contract can use the JIT feature instead of borrower.
              if (!whitelistedCaller[msg.sender] || !Address.isContract(msg.sender)) {
                  revert AG_INVALID_CONFIGURATION();
              }
          }
  
          address strategy = manager.siloToStrategy(_silo);
          if (_collateralAsset != ISturdyPair(_silo).collateralContract()) revert AG_INVALID_CONFIGURATION();
          if (asset != ISturdyPair(_silo).asset()) revert AG_INVALID_CONFIGURATION();
          if (strategy == address(0)) revert AG_INVALID_CONFIGURATION();
          if (_borrowAmount == 0) revert AG_INVALID_CONFIGURATION();
          if (_receiver == address(0)) revert AG_INVALID_CONFIGURATION();
  
          (uint256 totalAsset, ) = ISturdyPair(_silo).totalAsset();
          if (totalAsset == 0) revert AG_TOTAL_ASSET_IS_ZERO();
  
          (uint256 totalBorrow, ) = ISturdyPair(_silo).totalBorrow();
          uint256 numerator = UTIL_PREC * (totalBorrow + _borrowAmount);
          uint256 utilizationRate = numerator / totalAsset;
          uint256 utilizationTarget = manager.utilizationTargets(strategy);
          // 0 means 100% utilization
          if (utilizationTarget == 0) {
              utilizationTarget = UTIL_PREC;
          }
  
          // Just in-time liquidity
          if (utilizationRate > utilizationTarget) {
              uint256 requiredAmount = (numerator / utilizationTarget) -
                  totalAsset;
              manager.requestLiquidity(requiredAmount, _silo, slippage);
          }
  
          /// ToDo: Based on sturdy-silo
          {
              if (_collateralAmount > 0) {
                  IERC20(_collateralAsset).safeTransferFrom(
                      _borrower,
                      address(this),
                      _collateralAmount
                  );
                  IERC20(_collateralAsset).safeApprove(_silo, _collateralAmount);
                  ISturdyPair(_silo).addCollateral(_collateralAmount, _borrower);
              }
  
              if (_borrowAmount > 0) {
                  ISturdyPair(_silo).borrowAssetOnBehalfOf(
                      _borrowAmount,
                      _borrower
                  );
                  IERC20(asset).safeTransfer(_receiver, _borrowAmount);
              }
          }
      }
  }