creation_sourcecode: // SPDX-License-Identifier: AGPL-3.0
  pragma solidity 0.8.21;
  
  
  // 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);
  }
  
  interface IAprOracle {
      function getExpectedApr(
          address _strategy,
          int256 _debtChange
      ) external view returns (uint256);
  
      function getUtilizationInfo(
          address _strategy
      ) external view returns (uint256, uint256);
  
      function oracles(address _strategy) external view returns (address);
  }
  
  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);
      }
  }
  
  library Math {
      enum Rounding {
          Down, // Toward negative infinity
          Up, // Toward infinity
          Zero // Toward zero
      }
  
      /**
       * @dev Returns the largest of two numbers.
       */
      function max(uint256 a, uint256 b) internal pure returns (uint256) {
          return a > b ? a : b;
      }
  
      /**
       * @dev Returns the smallest of two numbers.
       */
      function min(uint256 a, uint256 b) internal pure returns (uint256) {
          return a < b ? a : b;
      }
  
      /**
       * @dev Returns the average of two numbers. The result is rounded towards
       * zero.
       */
      function average(uint256 a, uint256 b) internal pure returns (uint256) {
          // (a + b) / 2 can overflow.
          return (a & b) + (a ^ b) / 2;
      }
  
      /**
       * @dev Returns the ceiling of the division of two numbers.
       *
       * This differs from standard division with `/` in that it rounds up instead
       * of rounding down.
       */
      function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
          // (a + b - 1) / b can overflow on addition, so we distribute.
          return a == 0 ? 0 : (a - 1) / b + 1;
      }
  
      /**
       * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
       * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
       * with further edits by Uniswap Labs also under MIT license.
       */
      function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
          unchecked {
              // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
              // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
              // variables such that product = prod1 * 2^256 + prod0.
              uint256 prod0; // Least significant 256 bits of the product
              uint256 prod1; // Most significant 256 bits of the product
              assembly {
                  let mm := mulmod(x, y, not(0))
                  prod0 := mul(x, y)
                  prod1 := sub(sub(mm, prod0), lt(mm, prod0))
              }
  
              // Handle non-overflow cases, 256 by 256 division.
              if (prod1 == 0) {
                  // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                  // The surrounding unchecked block does not change this fact.
                  // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                  return prod0 / denominator;
              }
  
              // Make sure the result is less than 2^256. Also prevents denominator == 0.
              require(denominator > prod1, "Math: mulDiv overflow");
  
              ///////////////////////////////////////////////
              // 512 by 256 division.
              ///////////////////////////////////////////////
  
              // Make division exact by subtracting the remainder from [prod1 prod0].
              uint256 remainder;
              assembly {
                  // Compute remainder using mulmod.
                  remainder := mulmod(x, y, denominator)
  
                  // Subtract 256 bit number from 512 bit number.
                  prod1 := sub(prod1, gt(remainder, prod0))
                  prod0 := sub(prod0, remainder)
              }
  
              // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
              // See https://cs.stackexchange.com/q/138556/92363.
  
              // Does not overflow because the denominator cannot be zero at this stage in the function.
              uint256 twos = denominator & (~denominator + 1);
              assembly {
                  // Divide denominator by twos.
                  denominator := div(denominator, twos)
  
                  // Divide [prod1 prod0] by twos.
                  prod0 := div(prod0, twos)
  
                  // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                  twos := add(div(sub(0, twos), twos), 1)
              }
  
              // Shift in bits from prod1 into prod0.
              prod0 |= prod1 * twos;
  
              // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
              // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
              // four bits. That is, denominator * inv = 1 mod 2^4.
              uint256 inverse = (3 * denominator) ^ 2;
  
              // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
              // in modular arithmetic, doubling the correct bits in each step.
              inverse *= 2 - denominator * inverse; // inverse mod 2^8
              inverse *= 2 - denominator * inverse; // inverse mod 2^16
              inverse *= 2 - denominator * inverse; // inverse mod 2^32
              inverse *= 2 - denominator * inverse; // inverse mod 2^64
              inverse *= 2 - denominator * inverse; // inverse mod 2^128
              inverse *= 2 - denominator * inverse; // inverse mod 2^256
  
              // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
              // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
              // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
              // is no longer required.
              result = prod0 * inverse;
              return result;
          }
      }
  
      /**
       * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
       */
      function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
          uint256 result = mulDiv(x, y, denominator);
          if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
              result += 1;
          }
          return result;
      }
  
      /**
       * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
       *
       * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
       */
      function sqrt(uint256 a) internal pure returns (uint256) {
          if (a == 0) {
              return 0;
          }
  
          // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
          //
          // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
          // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
          //
          // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
          // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
          // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
          //
          // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
          uint256 result = 1 << (log2(a) >> 1);
  
          // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
          // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
          // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
          // into the expected uint128 result.
          unchecked {
              result = (result + a / result) >> 1;
              result = (result + a / result) >> 1;
              result = (result + a / result) >> 1;
              result = (result + a / result) >> 1;
              result = (result + a / result) >> 1;
              result = (result + a / result) >> 1;
              result = (result + a / result) >> 1;
              return min(result, a / result);
          }
      }
  
      /**
       * @notice Calculates sqrt(a), following the selected rounding direction.
       */
      function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
          unchecked {
              uint256 result = sqrt(a);
              return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
          }
      }
  
      /**
       * @dev Return the log in base 2, rounded down, of a positive value.
       * Returns 0 if given 0.
       */
      function log2(uint256 value) internal pure returns (uint256) {
          uint256 result = 0;
          unchecked {
              if (value >> 128 > 0) {
                  value >>= 128;
                  result += 128;
              }
              if (value >> 64 > 0) {
                  value >>= 64;
                  result += 64;
              }
              if (value >> 32 > 0) {
                  value >>= 32;
                  result += 32;
              }
              if (value >> 16 > 0) {
                  value >>= 16;
                  result += 16;
              }
              if (value >> 8 > 0) {
                  value >>= 8;
                  result += 8;
              }
              if (value >> 4 > 0) {
                  value >>= 4;
                  result += 4;
              }
              if (value >> 2 > 0) {
                  value >>= 2;
                  result += 2;
              }
              if (value >> 1 > 0) {
                  result += 1;
              }
          }
          return result;
      }
  
      /**
       * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
       * Returns 0 if given 0.
       */
      function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
          unchecked {
              uint256 result = log2(value);
              return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
          }
      }
  
      /**
       * @dev Return the log in base 10, rounded down, of a positive value.
       * Returns 0 if given 0.
       */
      function log10(uint256 value) internal pure returns (uint256) {
          uint256 result = 0;
          unchecked {
              if (value >= 10 ** 64) {
                  value /= 10 ** 64;
                  result += 64;
              }
              if (value >= 10 ** 32) {
                  value /= 10 ** 32;
                  result += 32;
              }
              if (value >= 10 ** 16) {
                  value /= 10 ** 16;
                  result += 16;
              }
              if (value >= 10 ** 8) {
                  value /= 10 ** 8;
                  result += 8;
              }
              if (value >= 10 ** 4) {
                  value /= 10 ** 4;
                  result += 4;
              }
              if (value >= 10 ** 2) {
                  value /= 10 ** 2;
                  result += 2;
              }
              if (value >= 10 ** 1) {
                  result += 1;
              }
          }
          return result;
      }
  
      /**
       * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
       * Returns 0 if given 0.
       */
      function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
          unchecked {
              uint256 result = log10(value);
              return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
          }
      }
  
      /**
       * @dev Return the log in base 256, rounded down, of a positive value.
       * Returns 0 if given 0.
       *
       * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
       */
      function log256(uint256 value) internal pure returns (uint256) {
          uint256 result = 0;
          unchecked {
              if (value >> 128 > 0) {
                  value >>= 128;
                  result += 16;
              }
              if (value >> 64 > 0) {
                  value >>= 64;
                  result += 8;
              }
              if (value >> 32 > 0) {
                  value >>= 32;
                  result += 4;
              }
              if (value >> 16 > 0) {
                  value >>= 16;
                  result += 2;
              }
              if (value >> 8 > 0) {
                  result += 1;
              }
          }
          return result;
      }
  
      /**
       * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
       * Returns 0 if given 0.
       */
      function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
          unchecked {
              uint256 result = log256(value);
              return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
          }
      }
  }
  
  contract DebtManager is ReentrancyGuard, Ownable {
      struct StrategyAllocation {
          address strategy;
          uint256 debt;
      }
  
      event AddStrategy(address strategy);
      event RemoveStrategy(address strategy);
      event SetZKVerifier(address zkVerifier);
      event SetOracle(address oracle);
      event SetSiloToStrategy(address indexed silo, address indexed strategy);
      event SetWhitelistedGateway(address indexed gateway, bool enabled);
      event SetUtilizationTargetOfStrategy(address indexed strategy, uint256 target);
      event SetGlobalUtilizationTarget(uint256 target);
      event SetManualAllocator(address allocator);
  
      error AG_INVALID_CONFIGURATION();
      error AG_ORACLE_NOT_SET();
      error AG_CALLER_NOT_ADMIN();
      error AG_NOT_AVAILABLE_STRATEGY();
      error AG_NOT_MANUAL_ALLOCATOR();
      error AG_NOT_ZK_VERIFIER();
      error AG_INVALID_STRATEGY();
      error AG_SUPPLY_LIMIT();
      error AG_INSUFFICIENT_ASSETS();
      error AG_HIGHER_DEBT();
  
      uint256 private constant UTIL_PREC = 1e5;
      
      IVault public immutable vault;
  
      address private _zkVerifier;
      address[] private _strategies;
  
      IAprOracle public oracle;
      // silo -> strategy
      mapping(address => address) public siloToStrategy;
      // gateway -> bool
      mapping(address => bool) public whitelistedGateway;
      // strategy -> utilization target, 0 means no target
      mapping(address => uint256) public utilizationTargets;
      // strategy -> bool, if the strategy is added ? true : false
      mapping(address => bool) public strategyAvails;
      // global utilization target, 0 means no target
      uint256 public globalTarget;
      // manual allocator
      address public manualAllocator;
  
      constructor(IVault _vault, IAprOracle _oracle) {
          vault = _vault;
          oracle = _oracle;
      }
  
      /**
       * @dev Add strategy to list.
       * - Caller is Admin
       * @param _strategy The strategy to manage debt.
       */
      function addStrategy(address _strategy) external payable onlyOwner {
          if (vault.strategies(_strategy).activation == 0) revert AG_INVALID_CONFIGURATION();
          if (oracle.oracles(_strategy) == address(0)) revert AG_ORACLE_NOT_SET();
  
          uint256 strategyCount = _strategies.length;
          for (uint256 i; i < strategyCount; ++i) {
              if (_strategies[i] == _strategy) return;
          }
  
          _strategies.push(_strategy);
          strategyAvails[_strategy] = true;
  
          emit AddStrategy(_strategy);
      }
  
      /**
       * @dev Remove strategy from list.
       * - Caller is Anyone
       * @param _strategy The strategy to manage debt.
       */
      function removeStrategy(address _strategy) external {
          if (vault.strategies(_strategy).activation != 0) {
              if (msg.sender != owner()) revert AG_CALLER_NOT_ADMIN();
          }
  
          uint256 strategyCount = _strategies.length;
          for (uint256 i; i < strategyCount; ++i) {
              if (_strategies[i] == _strategy) {
                  // if not last element
                  if (i != strategyCount - 1) {
                      _strategies[i] = _strategies[strategyCount - 1];
                  }
                  
                  _strategies.pop();
                  delete utilizationTargets[_strategy];
                  delete strategyAvails[_strategy];
  
                  emit RemoveStrategy(_strategy);
  
                  return;
              }
          }
      }
  
      /**
       * @dev Set the manual allocator address
       * - Caller is Admin
       * @param _manualAllocator The allocator address.
       */
      function setManualAllocator(
          address _manualAllocator
      ) external payable onlyOwner {
          manualAllocator = _manualAllocator;
  
          emit SetManualAllocator(_manualAllocator);
      }
  
      /**
       * @dev Set the apr oracle contract address.
       * - Caller is Admin
       * @param _oracle The oracle contract address.
       */
      function setOracle(
          IAprOracle _oracle
      ) external payable onlyOwner {
          oracle = _oracle;
  
          emit SetOracle(address(_oracle));
      }
  
      /**
       * @dev Set the strategy's external silo address.
       * - Caller is Admin
       * @param _silo The external silo address.
       * @param _strategy The strategy address to manage debt.
       */
      function setSiloToStrategy(
          address _silo,
          address _strategy
      ) external payable onlyOwner {
          siloToStrategy[_silo] = _strategy;
  
          emit SetSiloToStrategy(_silo, _strategy);
      }
  
      /**
       * @dev Set the whitelisted gateway.
       * - Caller is Admin
       * @param _gateway The Silo Gateway address.
       * @param _enabled True if whitelisted gateway, else false.
       */
      function setWhitelistedGateway(
          address _gateway,
          bool _enabled
      ) external payable onlyOwner {
          whitelistedGateway[_gateway] = _enabled;
  
          emit SetWhitelistedGateway(_gateway, _enabled);
      }
  
      /**
       * @dev Set the strategy's utilization target value.
       * - Caller is Admin
       * @param _strategy The strategy address to manage debt.
       * @param _target The utilization target of strategy.
       */
      function setUtilizationTargetOfStrategy(
          address _strategy,
          uint256 _target
      ) external payable onlyOwner {
          if (_target >= UTIL_PREC) revert AG_INVALID_CONFIGURATION();
          if (!strategyAvails[_strategy]) revert AG_NOT_AVAILABLE_STRATEGY();
  
          utilizationTargets[_strategy] = _target;
  
          emit SetUtilizationTargetOfStrategy(_strategy, _target);
      }
  
      /**
       * @dev Set the global utilization target value.
       * - Caller is Admin
       * @param _target The global utilization target value.
       */
      function setGlobalTarget(
          uint256 _target
      ) external payable onlyOwner {
          if (_target >= UTIL_PREC) revert AG_INVALID_CONFIGURATION();
  
          globalTarget = _target;
  
          emit SetGlobalUtilizationTarget(_target);
      }
  
      /**
       * @dev Set the zero knowledge verifier address.
       * - Caller is Admin
       * @param _verifier The zero knowledge verifier address.
       */
      function setZKVerifier(address _verifier) external payable onlyOwner {
          _zkVerifier = _verifier;
  
          emit SetZKVerifier(_verifier);
      }
  
      /**
       * @dev Manual update the allocations.
       *      The `_newPositions` list should be in order of decreasing debt and increasing debt.
       * - Caller is Admin
       * @param _newPositions The list of position info
       */
      function manualAllocation(
          StrategyAllocation[] memory _newPositions
      ) external payable {
          if (msg.sender != manualAllocator) revert AG_NOT_MANUAL_ALLOCATOR();
  
          _manualAllocation(_newPositions);
      }
  
      /**
       * @dev Manual update the allocations from zk verifier.
       *      ZK verifier guarantee newAPR > curAPR and it is optimal allocations as well.
       *      The `_newPositions` list should be in order of decreasing debt and increasing debt.
       * - Caller is ZKVerifier
       * @param _newPositions The list of position info
       */
      function zkAllocation(
          StrategyAllocation[] memory _newPositions
      ) external payable {
          if (msg.sender != _zkVerifier) revert AG_NOT_ZK_VERIFIER();
          if (_strategies.length != _newPositions.length) revert AG_INVALID_CONFIGURATION();
  
          _manualAllocation(_newPositions);
      }
  
      /**
       * @dev Process the just in time liquidity.
       *      If the sturdy silos have not enough liquidity in case of borrowing,
       *      silos would request liquidity by reducing debts from other strategies.
       * - Caller is Silo Gateways
       * @param _amount The required liquidity amount.
       * @param _silo The silo address.
       - @param _slippage The _slippage percent value.
       */
      function requestLiquidity(uint256 _amount, address _silo, uint256 _slippage) external payable nonReentrant {
          // only whitelisted gateways can request liquidity in case of borrow.
          address requestingStrategy = siloToStrategy[_silo];
  
          if (requestingStrategy == address(0)) revert AG_INVALID_STRATEGY();
          if (!whitelistedGateway[msg.sender]) revert AG_INVALID_CONFIGURATION();
  
          // update state of requesting strategy and check the supply cap
          IVault.StrategyParams memory requestingStrategyData = vault
              .strategies(requestingStrategy);
          
          uint256 strategyNewDebt = requestingStrategyData.current_debt + _amount;
          if (strategyNewDebt >= requestingStrategyData.max_debt - 1) revert AG_SUPPLY_LIMIT();
  
          uint256 minIdle = vault.minimum_total_idle();
  
          // global utilization target check.
          {
              uint256 maxSupply = vault.totalAssets() - minIdle;
              uint256 globalUtilizationTarget = globalTarget;
              if (globalUtilizationTarget != 0) {
                  maxSupply = maxSupply * globalUtilizationTarget / UTIL_PREC;
              }
              if (strategyNewDebt >= maxSupply) revert AG_SUPPLY_LIMIT();
          }
  
          address[] memory strategies = _strategies;
          uint256 totalIdle = vault.totalIdle();
          uint256 requiredAmount = _amount + minIdle;
          uint256 allowedSlippage = _amount * _slippage / UTIL_PREC;
          uint256 strategyCount = strategies.length;
  
          if (requiredAmount > totalIdle) {
              unchecked {
                  requiredAmount -= totalIdle;
              }
  
              (
                  uint256[] memory availableAmounts,
                  IVault.StrategyParams[] memory strategyDatas
               ) = _getAvailableAmountsAndDatas(strategies, requestingStrategy);
  
              // withdraw from other strategies to fill the required amount using selection sort algorithm
              for (uint256 i; i < strategyCount; ++i) {
                  // find best candidate which has max available amount
                  uint256 maxIndex = i;
                  for (uint256 j = i + 1; j < strategyCount; ++j) {
                      if (availableAmounts[j] <= availableAmounts[maxIndex]) continue;
                      
                      maxIndex = j;
                  }
  
                  // swap the position of best candidate
                  if (i != maxIndex) {
                      (strategies[i], strategies[maxIndex]) = (strategies[maxIndex], strategies[i]);
                      (availableAmounts[i], availableAmounts[maxIndex]) = (availableAmounts[maxIndex], availableAmounts[i]);
                      (strategyDatas[i], strategyDatas[maxIndex]) = (strategyDatas[maxIndex], strategyDatas[i]);
                  }
  
                  // get withdraw amount
                  uint256 withdrawAmount = availableAmounts[i];
                  if (withdrawAmount > requiredAmount) {
                      withdrawAmount = requiredAmount;
                  }
  
                  if (withdrawAmount == 0) continue;
  
                  uint256 newDebt;
                  if (strategyDatas[i].current_debt > withdrawAmount) {
                      unchecked {
                          newDebt = strategyDatas[i].current_debt - withdrawAmount;
                      }
                  }
  
                  if (vault.assess_share_of_unrealised_losses(strategies[i], strategyDatas[i].current_debt - newDebt) != 0) {
                      continue;
                  }
  
                  totalIdle = vault.totalIdle();
                  vault.update_debt(strategies[i], newDebt);
                  unchecked {
                      withdrawAmount = vault.totalIdle() - totalIdle;
                  }
  
                  if (withdrawAmount < requiredAmount) {
                      unchecked {
                          requiredAmount -= withdrawAmount;
                      }
                  } else {
                      requiredAmount = 0;
                      break;
                  }
  
                  if (requiredAmount < allowedSlippage) break;
              }
  
              if (requiredAmount >= allowedSlippage) revert AG_INSUFFICIENT_ASSETS();
          }
  
          // update debt of msg.sender to fill the missing liquidity
          vault.update_debt(
              requestingStrategy,
              strategyNewDebt
          );
      }
  
      /**
       * @dev Get the full array of strategies.
       * @return the full array of strategies.
       */
      function getStrategies() external view returns (address[] memory) {
          return _strategies;
      }
  
      /**
       * @dev Get the verifier address.
       * @return the verifier address.
       */
      function getZKVerifier() external view returns (address) {
          return _zkVerifier;
      }
  
      function _manualAllocation(
          StrategyAllocation[] memory _newPositions
      ) internal {
          unchecked {
              uint256 strategyLength = _newPositions.length;
  
              for (uint256 i; i < strategyLength; ++i) {
                  StrategyAllocation memory position = _newPositions[i];
                  if (!strategyAvails[position.strategy]) revert AG_NOT_AVAILABLE_STRATEGY();
  
                  IVault.StrategyParams memory strategyData = vault.strategies(
                      position.strategy
                  );
  
                  if (strategyData.activation == 0) revert AG_NOT_AVAILABLE_STRATEGY();
  
                  if (strategyData.current_debt == position.debt) continue;
  
                  if (position.debt > strategyData.max_debt) revert AG_HIGHER_DEBT();
  
                  // deposit/increase not possible because minimum total idle reached
                  if (position.debt > strategyData.current_debt && 
                      vault.totalIdle() <= vault.minimum_total_idle()) continue;
  
                  if (
                      strategyData.current_debt > position.debt && 
                      vault.assess_share_of_unrealised_losses(position.strategy, strategyData.current_debt - position.debt) != 0
                  ) {
                      vault.process_report(position.strategy);
                  }
  
                  // update debt.
                  vault.update_debt(position.strategy, position.debt);
              }
          }
      }
  
      function _getAvailableAmountsAndDatas(
          address[] memory _availableStrategies, 
          address _requestingStrategy
      ) internal returns (uint256[] memory, IVault.StrategyParams[] memory) {
          IAprOracle _oracle = oracle;
          uint256 strategyCount = _availableStrategies.length;
          uint256[] memory amounts = new uint256[](strategyCount);
          IVault.StrategyParams[] memory strategyDatas = new IVault.StrategyParams[](strategyCount);
  
          for (uint256 i; i < strategyCount; ++i) {
              address strategy = _availableStrategies[i];
              if (strategy == _requestingStrategy) continue;
  
              strategyDatas[i] = vault.strategies(strategy);
              if (strategyDatas[i].current_debt == 0) continue;
  
              uint256 strategyUtilizationTarget = utilizationTargets[strategy];
              
              // 0 means no target.
              if (strategyUtilizationTarget == 0) strategyUtilizationTarget = UTIL_PREC;
  
              (uint256 borrows, uint256 supply) = _oracle.getUtilizationInfo(strategy);
              
              // if current utilization value is over the target, can't withdraw.
              if (borrows * UTIL_PREC / supply > strategyUtilizationTarget) continue;
  
              // get withdrawable amount from strategy
              amounts[i] = Math.min(
                  supply - borrows * UTIL_PREC / strategyUtilizationTarget,
                  strategyDatas[i].current_debt
              );
          }
  
          return (amounts, strategyDatas);
      }
  }