ETH Price: $2,585.63 (+2.78%)

Transaction Decoder

Block:
22854032 at Jul-05-2025 03:52:47 PM +UTC
Transaction Fee:
0.000080117320007564 ETH $0.21
Gas Used:
165,956 Gas / 0.482762419 Gwei

Emitted Events:

421 Dai.Approval( src=Yearn V3 Vault, guy=Yearn V3 Vault, wad=158589222450856285335720 )
422 Dai.Transfer( src=Yearn V3 Vault, dst=Yearn V3 Vault, wad=158589222450856285335720 )
423 Yearn V3 Vault.Transfer( sender=0x0000000000000000000000000000000000000000, receiver=Yearn V3 Vault, value=146948993625354533480896 )
424 Yearn V3 Vault.Deposit( sender=Yearn V3 Vault, owner=Yearn V3 Vault, assets=158589222450856285335720, shares=146948993625354533480896 )
425 Dai.Approval( src=Yearn V3 Vault, guy=Yearn V3 Vault, wad=0 )
426 Yearn V3 Vault.DebtUpdated( strategy=Yearn V3 Vault, current_debt=541687097906620639878985, new_debt=700276320357476925214705 )

Account State Difference:

  Address   Before After State Difference Code
0x028eC733...20195336c
0x1e9eB053...5356b8671
0x28313239...29eE17961
0.435098644584227917 Eth
Nonce: 5995
0.435018527264220353 Eth
Nonce: 5996
0.000080117320007564
0x6B175474...495271d0F
0x92545bCE...0bd2fC22e
(BuilderNet)
29.350153161064911488 Eth29.350153161065077444 Eth0.000000000000165956

Execution Trace

yHaaSRelayer.forwardCall( debtAllocatorAddress=0x1e9eB053228B1156831759401dE0E115356b8671, data=0xDA5F328600000000000000000000000092545BCE636E6EE91D88D2D017182CD0BD2FC22E000000000000000000000000028EC7330FF87667B6DFB0D94B954C820195336C00000000000000000000000000000000000000000000944A0E2E462AC2948FF1 ) => ( success=True )
  • DebtAllocator.update_debt( _vault=0x92545bCE636E6eE91D88D2D017182cD0bd2fC22e, _strategy=0x028eC7330ff87667b6dfb0D94b954c820195336c, _targetDebt=700276320357476925214705 )
    • DebtAllocator.update_debt( _vault=0x92545bCE636E6eE91D88D2D017182cD0bd2fC22e, _strategy=0x028eC7330ff87667b6dfb0D94b954c820195336c, _targetDebt=700276320357476925214705 )
      • V3.update_debt( strategy=0x028eC7330ff87667b6dfb0D94b954c820195336c, target_debt=700276320357476925214705, max_loss=1 ) => ( 700276320357476925214705 )
        • V3.update_debt( strategy=0x028eC7330ff87667b6dfb0D94b954c820195336c, target_debt=700276320357476925214705, max_loss=1 ) => ( 700276320357476925214705 )
          • V3.maxDeposit( receiver=0x92545bCE636E6eE91D88D2D017182cD0bd2fC22e ) => ( 31563392365560071763253043 )
            • V3.maxDeposit( receiver=0x92545bCE636E6eE91D88D2D017182cD0bd2fC22e ) => ( 31563392365560071763253043 )
            • Dai.approve( usr=0x028eC7330ff87667b6dfb0D94b954c820195336c, wad=158589222450856285335720 ) => ( True )
            • Dai.balanceOf( 0x92545bCE636E6eE91D88D2D017182cD0bd2fC22e ) => ( 158589222450856285335721 )
            • V3.deposit( assets=158589222450856285335720, receiver=0x92545bCE636E6eE91D88D2D017182cD0bd2fC22e ) => ( 146948993625354533480896 )
              • V3.deposit( assets=158589222450856285335720, receiver=0x92545bCE636E6eE91D88D2D017182cD0bd2fC22e ) => ( 146948993625354533480896 )
              • Dai.balanceOf( 0x92545bCE636E6eE91D88D2D017182cD0bd2fC22e ) => ( 1 )
              • Dai.approve( usr=0x028eC7330ff87667b6dfb0D94b954c820195336c, wad=0 ) => ( True )
                File 1 of 7: yHaaSRelayer
                // SPDX-License-Identifier: MIT
                pragma solidity ^0.8.18;
                
                contract yHaaSRelayer {
                    address public owner;
                    address public governance;
                
                    mapping(address => bool) public keepers;
                
                    constructor() {
                        owner = msg.sender;
                        governance = msg.sender;
                    }
                
                    function harvestStrategy(address _strategyAddress) public onlyKeepers returns (uint256 profit, uint256 loss) {
                        (profit, loss) = StrategyAPI(_strategyAddress).report();
                    }
                
                    function tendStrategy(address _strategyAddress) public onlyKeepers {
                        StrategyAPI(_strategyAddress).tend();
                    }
                
                    function processReport(address _vaultAddress, address _strategyAddress) public onlyKeepers returns (uint256 gain, uint256 loss) {
                        (gain, loss) = VaultAPI(_vaultAddress).process_report(_strategyAddress);
                    }
                
                    function forwardCall(address debtAllocatorAddress, bytes memory data) public onlyKeepers returns (bool success) {
                        (success, ) = debtAllocatorAddress.call(data);
                    }
                
                    function setKeeper(address _address, bool _allowed) external virtual onlyAuthorized {
                        keepers[_address] = _allowed;
                    }
                
                    /**
                    @notice Changes the `owner` address.
                    @param _owner The new address to assign as `owner`.
                    */
                    function setOwner(address _owner) external onlyAuthorized {
                        require(_owner != address(0));
                        owner = _owner;
                    }
                
                    /**
                    @notice Changes the `governance` address.
                    @param _governance The new address to assign as `governance`.
                    */
                    function setGovernance(address _governance) external onlyGovernance {
                        require(_governance != address(0));
                        governance = _governance;
                    }
                
                    modifier onlyKeepers() {
                        require(msg.sender == owner || keepers[msg.sender] == true || msg.sender == governance, "!keeper yHaaSProxy");
                        _;
                    }
                
                    modifier onlyAuthorized() {
                        require(msg.sender == owner || msg.sender == governance, "!authorized");
                        _;
                    }
                
                    modifier onlyGovernance() {
                        require(msg.sender == governance, "!governance");
                        _;
                    }
                } 
                
                interface StrategyAPI {
                    function tend() external;
                    function report() external returns (uint256 _profit, uint256 _loss);
                }
                
                interface VaultAPI {
                    function process_report(address) external returns (uint256 _gain, uint256 _loss);
                }

                File 2 of 7: Dai
                // hevm: flattened sources of /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/dai.sol
                pragma solidity =0.5.12;
                
                ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/lib.sol
                // This program is free software: you can redistribute it and/or modify
                // it under the terms of the GNU General Public License as published by
                // the Free Software Foundation, either version 3 of the License, or
                // (at your option) any later version.
                
                // This program is distributed in the hope that it will be useful,
                // but WITHOUT ANY WARRANTY; without even the implied warranty of
                // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                // GNU General Public License for more details.
                
                // You should have received a copy of the GNU General Public License
                // along with this program.  If not, see <http://www.gnu.org/licenses/>.
                
                /* pragma solidity 0.5.12; */
                
                contract LibNote {
                    event LogNote(
                        bytes4   indexed  sig,
                        address  indexed  usr,
                        bytes32  indexed  arg1,
                        bytes32  indexed  arg2,
                        bytes             data
                    ) anonymous;
                
                    modifier note {
                        _;
                        assembly {
                            // log an 'anonymous' event with a constant 6 words of calldata
                            // and four indexed topics: selector, caller, arg1 and arg2
                            let mark := msize                         // end of memory ensures zero
                            mstore(0x40, add(mark, 288))              // update free memory pointer
                            mstore(mark, 0x20)                        // bytes type data offset
                            mstore(add(mark, 0x20), 224)              // bytes size (padded)
                            calldatacopy(add(mark, 0x40), 0, 224)     // bytes payload
                            log4(mark, 288,                           // calldata
                                 shl(224, shr(224, calldataload(0))), // msg.sig
                                 caller,                              // msg.sender
                                 calldataload(4),                     // arg1
                                 calldataload(36)                     // arg2
                                )
                        }
                    }
                }
                
                ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/dai.sol
                // Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico
                
                // This program is free software: you can redistribute it and/or modify
                // it under the terms of the GNU Affero General Public License as published by
                // the Free Software Foundation, either version 3 of the License, or
                // (at your option) any later version.
                //
                // This program is distributed in the hope that it will be useful,
                // but WITHOUT ANY WARRANTY; without even the implied warranty of
                // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                // GNU Affero General Public License for more details.
                //
                // You should have received a copy of the GNU Affero General Public License
                // along with this program.  If not, see <https://www.gnu.org/licenses/>.
                
                /* pragma solidity 0.5.12; */
                
                /* import "./lib.sol"; */
                
                contract Dai is LibNote {
                    // --- Auth ---
                    mapping (address => uint) public wards;
                    function rely(address guy) external note auth { wards[guy] = 1; }
                    function deny(address guy) external note auth { wards[guy] = 0; }
                    modifier auth {
                        require(wards[msg.sender] == 1, "Dai/not-authorized");
                        _;
                    }
                
                    // --- ERC20 Data ---
                    string  public constant name     = "Dai Stablecoin";
                    string  public constant symbol   = "DAI";
                    string  public constant version  = "1";
                    uint8   public constant decimals = 18;
                    uint256 public totalSupply;
                
                    mapping (address => uint)                      public balanceOf;
                    mapping (address => mapping (address => uint)) public allowance;
                    mapping (address => uint)                      public nonces;
                
                    event Approval(address indexed src, address indexed guy, uint wad);
                    event Transfer(address indexed src, address indexed dst, uint wad);
                
                    // --- Math ---
                    function add(uint x, uint y) internal pure returns (uint z) {
                        require((z = x + y) >= x);
                    }
                    function sub(uint x, uint y) internal pure returns (uint z) {
                        require((z = x - y) <= x);
                    }
                
                    // --- EIP712 niceties ---
                    bytes32 public DOMAIN_SEPARATOR;
                    // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)");
                    bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb;
                
                    constructor(uint256 chainId_) public {
                        wards[msg.sender] = 1;
                        DOMAIN_SEPARATOR = keccak256(abi.encode(
                            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                            keccak256(bytes(name)),
                            keccak256(bytes(version)),
                            chainId_,
                            address(this)
                        ));
                    }
                
                    // --- Token ---
                    function transfer(address dst, uint wad) external returns (bool) {
                        return transferFrom(msg.sender, dst, wad);
                    }
                    function transferFrom(address src, address dst, uint wad)
                        public returns (bool)
                    {
                        require(balanceOf[src] >= wad, "Dai/insufficient-balance");
                        if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
                            require(allowance[src][msg.sender] >= wad, "Dai/insufficient-allowance");
                            allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad);
                        }
                        balanceOf[src] = sub(balanceOf[src], wad);
                        balanceOf[dst] = add(balanceOf[dst], wad);
                        emit Transfer(src, dst, wad);
                        return true;
                    }
                    function mint(address usr, uint wad) external auth {
                        balanceOf[usr] = add(balanceOf[usr], wad);
                        totalSupply    = add(totalSupply, wad);
                        emit Transfer(address(0), usr, wad);
                    }
                    function burn(address usr, uint wad) external {
                        require(balanceOf[usr] >= wad, "Dai/insufficient-balance");
                        if (usr != msg.sender && allowance[usr][msg.sender] != uint(-1)) {
                            require(allowance[usr][msg.sender] >= wad, "Dai/insufficient-allowance");
                            allowance[usr][msg.sender] = sub(allowance[usr][msg.sender], wad);
                        }
                        balanceOf[usr] = sub(balanceOf[usr], wad);
                        totalSupply    = sub(totalSupply, wad);
                        emit Transfer(usr, address(0), wad);
                    }
                    function approve(address usr, uint wad) external returns (bool) {
                        allowance[msg.sender][usr] = wad;
                        emit Approval(msg.sender, usr, wad);
                        return true;
                    }
                
                    // --- Alias ---
                    function push(address usr, uint wad) external {
                        transferFrom(msg.sender, usr, wad);
                    }
                    function pull(address usr, uint wad) external {
                        transferFrom(usr, msg.sender, wad);
                    }
                    function move(address src, address dst, uint wad) external {
                        transferFrom(src, dst, wad);
                    }
                
                    // --- Approve by signature ---
                    function permit(address holder, address spender, uint256 nonce, uint256 expiry,
                                    bool allowed, uint8 v, bytes32 r, bytes32 s) external
                    {
                        bytes32 digest =
                            keccak256(abi.encodePacked(
                                "\x19\x01",
                                DOMAIN_SEPARATOR,
                                keccak256(abi.encode(PERMIT_TYPEHASH,
                                                     holder,
                                                     spender,
                                                     nonce,
                                                     expiry,
                                                     allowed))
                        ));
                
                        require(holder != address(0), "Dai/invalid-address-0");
                        require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit");
                        require(expiry == 0 || now <= expiry, "Dai/permit-expired");
                        require(nonce == nonces[holder]++, "Dai/invalid-nonce");
                        uint wad = allowed ? uint(-1) : 0;
                        allowance[holder][spender] = wad;
                        emit Approval(holder, spender, wad);
                    }
                }

                File 3 of 7: Yearn V3 Vault
                # @version 0.3.7
                
                """
                @title Yearn V3 Vault
                @license GNU AGPLv3
                @author yearn.finance
                @notice
                    The Yearn VaultV3 is designed as a non-opinionated system to distribute funds of 
                    depositors for a specific `asset` into different opportunities (aka Strategies)
                    and manage accounting in a robust way.
                
                    Depositors receive shares (aka vaults tokens) proportional to their deposit amount. 
                    Vault tokens are yield-bearing and can be redeemed at any time to get back deposit 
                    plus any yield generated.
                
                    Addresses that are given different permissioned roles by the `role_manager` 
                    are then able to allocate funds as they best see fit to different strategies 
                    and adjust the strategies and allocations as needed, as well as reporting realized
                    profits or losses.
                
                    Strategies are any ERC-4626 compliant contracts that use the same underlying `asset` 
                    as the vault. The vault provides no assurances as to the safety of any strategy
                    and it is the responsibility of those that hold the corresponding roles to choose
                    and fund strategies that best fit their desired specifications.
                
                    Those holding vault tokens are able to redeem the tokens for the corresponding
                    amount of underlying asset based on any reported profits or losses since their
                    initial deposit.
                
                    The vault is built to be customized by the management to be able to fit their
                    specific desired needs. Including the customization of strategies, accountants, 
                    ownership etc.
                """
                
                # INTERFACES #
                
                from vyper.interfaces import ERC20
                from vyper.interfaces import ERC20Detailed
                
                interface IStrategy:
                    def asset() -> address: view
                    def balanceOf(owner: address) -> uint256: view
                    def convertToAssets(shares: uint256) -> uint256: view
                    def convertToShares(assets: uint256) -> uint256: view
                    def previewWithdraw(assets: uint256) -> uint256: view
                    def maxDeposit(receiver: address) -> uint256: view
                    def deposit(assets: uint256, receiver: address) -> uint256: nonpayable
                    def maxRedeem(owner: address) -> uint256: view
                    def redeem(shares: uint256, receiver: address, owner: address) -> uint256: nonpayable
                    
                interface IAccountant:
                    def report(strategy: address, gain: uint256, loss: uint256) -> (uint256, uint256): nonpayable
                
                interface IDepositLimitModule:
                    def available_deposit_limit(receiver: address) -> uint256: view
                    
                interface IWithdrawLimitModule:
                    def available_withdraw_limit(owner: address, max_loss: uint256, strategies: DynArray[address, MAX_QUEUE]) -> uint256: view
                
                interface IFactory:
                    def protocol_fee_config() -> (uint16, address): view
                
                # EVENTS #
                # ERC4626 EVENTS
                event Deposit:
                    sender: indexed(address)
                    owner: indexed(address)
                    assets: uint256
                    shares: uint256
                
                event Withdraw:
                    sender: indexed(address)
                    receiver: indexed(address)
                    owner: indexed(address)
                    assets: uint256
                    shares: uint256
                
                # ERC20 EVENTS
                event Transfer:
                    sender: indexed(address)
                    receiver: indexed(address)
                    value: uint256
                
                event Approval:
                    owner: indexed(address)
                    spender: indexed(address)
                    value: uint256
                
                # STRATEGY EVENTS
                event StrategyChanged:
                    strategy: indexed(address)
                    change_type: indexed(StrategyChangeType)
                    
                event StrategyReported:
                    strategy: indexed(address)
                    gain: uint256
                    loss: uint256
                    current_debt: uint256
                    protocol_fees: uint256
                    total_fees: uint256
                    total_refunds: uint256
                
                # DEBT MANAGEMENT EVENTS
                event DebtUpdated:
                    strategy: indexed(address)
                    current_debt: uint256
                    new_debt: uint256
                
                # ROLE UPDATES
                event RoleSet:
                    account: indexed(address)
                    role: indexed(Roles)
                
                # STORAGE MANAGEMENT EVENTS
                event UpdateRoleManager:
                    role_manager: indexed(address)
                
                event UpdateAccountant:
                    accountant: indexed(address)
                
                event UpdateDepositLimitModule:
                    deposit_limit_module: indexed(address)
                
                event UpdateWithdrawLimitModule:
                    withdraw_limit_module: indexed(address)
                
                event UpdateDefaultQueue:
                    new_default_queue: DynArray[address, MAX_QUEUE]
                
                event UpdateUseDefaultQueue:
                    use_default_queue: bool
                
                event UpdatedMaxDebtForStrategy:
                    sender: indexed(address)
                    strategy: indexed(address)
                    new_debt: uint256
                
                event UpdateDepositLimit:
                    deposit_limit: uint256
                
                event UpdateMinimumTotalIdle:
                    minimum_total_idle: uint256
                
                event UpdateProfitMaxUnlockTime:
                    profit_max_unlock_time: uint256
                
                event DebtPurchased:
                    strategy: indexed(address)
                    amount: uint256
                
                event Shutdown:
                    pass
                
                # STRUCTS #
                struct StrategyParams:
                    # Timestamp when the strategy was added.
                    activation: uint256 
                    # Timestamp of the strategies last report.
                    last_report: uint256
                    # The current assets the strategy holds.
                    current_debt: uint256
                    # The max assets the strategy can hold. 
                    max_debt: uint256
                
                # CONSTANTS #
                # The max length the withdrawal queue can be.
                MAX_QUEUE: constant(uint256) = 10
                # 100% in Basis Points.
                MAX_BPS: constant(uint256) = 10_000
                # Extended for profit locking calculations.
                MAX_BPS_EXTENDED: constant(uint256) = 1_000_000_000_000
                # The version of this vault.
                API_VERSION: constant(String[28]) = "3.0.2"
                
                # ENUMS #
                # Each permissioned function has its own Role.
                # Roles can be combined in any combination or all kept separate.
                # Follows python Enum patterns so the first Enum == 1 and doubles each time.
                enum Roles:
                    ADD_STRATEGY_MANAGER # Can add strategies to the vault.
                    REVOKE_STRATEGY_MANAGER # Can remove strategies from the vault.
                    FORCE_REVOKE_MANAGER # Can force remove a strategy causing a loss.
                    ACCOUNTANT_MANAGER # Can set the accountant that assess fees.
                    QUEUE_MANAGER # Can set the default withdrawal queue.
                    REPORTING_MANAGER # Calls report for strategies.
                    DEBT_MANAGER # Adds and removes debt from strategies.
                    MAX_DEBT_MANAGER # Can set the max debt for a strategy.
                    DEPOSIT_LIMIT_MANAGER # Sets deposit limit and module for the vault.
                    WITHDRAW_LIMIT_MANAGER # Sets the withdraw limit module.
                    MINIMUM_IDLE_MANAGER # Sets the minimum total idle the vault should keep.
                    PROFIT_UNLOCK_MANAGER # Sets the profit_max_unlock_time.
                    DEBT_PURCHASER # Can purchase bad debt from the vault.
                    EMERGENCY_MANAGER # Can shutdown vault in an emergency.
                
                enum StrategyChangeType:
                    ADDED
                    REVOKED
                
                enum Rounding:
                    ROUND_DOWN
                    ROUND_UP
                
                # STORAGE #
                # Underlying token used by the vault.
                asset: public(address)
                # Based off the `asset` decimals.
                decimals: public(uint8)
                # Deployer contract used to retrieve the protocol fee config.
                factory: address
                
                # HashMap that records all the strategies that are allowed to receive assets from the vault.
                strategies: public(HashMap[address, StrategyParams])
                # The current default withdrawal queue.
                default_queue: public(DynArray[address, MAX_QUEUE])
                # Should the vault use the default_queue regardless whats passed in.
                use_default_queue: public(bool)
                
                ### ACCOUNTING ###
                # ERC20 - amount of shares per account
                balance_of: HashMap[address, uint256]
                # ERC20 - owner -> (spender -> amount)
                allowance: public(HashMap[address, HashMap[address, uint256]])
                # Total amount of shares that are currently minted including those locked.
                total_supply: uint256
                # Total amount of assets that has been deposited in strategies.
                total_debt: uint256
                # Current assets held in the vault contract. Replacing balanceOf(this) to avoid price_per_share manipulation.
                total_idle: uint256
                # Minimum amount of assets that should be kept in the vault contract to allow for fast, cheap redeems.
                minimum_total_idle: public(uint256)
                # Maximum amount of tokens that the vault can accept. If totalAssets > deposit_limit, deposits will revert.
                deposit_limit: public(uint256)
                
                ### PERIPHERY ###
                # Contract that charges fees and can give refunds.
                accountant: public(address)
                # Contract to control the deposit limit.
                deposit_limit_module: public(address)
                # Contract to control the withdraw limit.
                withdraw_limit_module: public(address)
                
                ### ROLES ###
                # HashMap mapping addresses to their roles
                roles: public(HashMap[address, Roles])
                # Address that can add and remove roles to addresses.
                role_manager: public(address)
                # Temporary variable to store the address of the next role_manager until the role is accepted.
                future_role_manager: public(address)
                
                # ERC20 - name of the vaults token.
                name: public(String[64])
                # ERC20 - symbol of the vaults token.
                symbol: public(String[32])
                
                # State of the vault - if set to true, only withdrawals will be available. It can't be reverted.
                shutdown: bool
                # The amount of time profits will unlock over.
                profit_max_unlock_time: uint256
                # The timestamp of when the current unlocking period ends.
                full_profit_unlock_date: uint256
                # The per second rate at which profit will unlock.
                profit_unlocking_rate: uint256
                # Last timestamp of the most recent profitable report.
                last_profit_update: uint256
                
                # `nonces` track `permit` approvals with signature.
                nonces: public(HashMap[address, uint256])
                DOMAIN_TYPE_HASH: constant(bytes32) = keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')
                PERMIT_TYPE_HASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
                
                # Constructor
                @external
                def __init__():
                    # Set `asset` so it cannot be re-initialized.
                    self.asset = self
                    
                @external
                def initialize(
                    asset: address, 
                    name: String[64], 
                    symbol: String[32], 
                    role_manager: address, 
                    profit_max_unlock_time: uint256
                ):
                    """
                    @notice
                        Initialize a new vault. Sets the asset, name, symbol, and role manager.
                    @param asset
                        The address of the asset that the vault will accept.
                    @param name
                        The name of the vault token.
                    @param symbol
                        The symbol of the vault token.
                    @param role_manager 
                        The address that can add and remove roles to addresses
                    @param profit_max_unlock_time
                        The amount of time that the profit will be locked for
                    """
                    assert self.asset == empty(address), "initialized"
                    assert asset != empty(address), "ZERO ADDRESS"
                    assert role_manager != empty(address), "ZERO ADDRESS"
                
                    self.asset = asset
                    # Get the decimals for the vault to use.
                    self.decimals = ERC20Detailed(asset).decimals()
                    
                    # Set the factory as the deployer address.
                    self.factory = msg.sender
                
                    # Must be less than one year for report cycles
                    assert profit_max_unlock_time <= 31_556_952 # dev: profit unlock time too long
                    self.profit_max_unlock_time = profit_max_unlock_time
                
                    self.name = name
                    self.symbol = symbol
                    self.role_manager = role_manager
                
                ## SHARE MANAGEMENT ##
                ## ERC20 ##
                @internal
                def _spend_allowance(owner: address, spender: address, amount: uint256):
                    # Unlimited approval does nothing (saves an SSTORE)
                    current_allowance: uint256 = self.allowance[owner][spender]
                    if (current_allowance < max_value(uint256)):
                        assert current_allowance >= amount, "insufficient allowance"
                        self._approve(owner, spender, unsafe_sub(current_allowance, amount))
                
                @internal
                def _transfer(sender: address, receiver: address, amount: uint256):
                    sender_balance: uint256 = self.balance_of[sender]
                    assert sender_balance >= amount, "insufficient funds"
                    self.balance_of[sender] = unsafe_sub(sender_balance, amount)
                    self.balance_of[receiver] = unsafe_add(self.balance_of[receiver], amount)
                    log Transfer(sender, receiver, amount)
                
                @internal
                def _transfer_from(sender: address, receiver: address, amount: uint256) -> bool:
                    self._spend_allowance(sender, msg.sender, amount)
                    self._transfer(sender, receiver, amount)
                    return True
                
                @internal
                def _approve(owner: address, spender: address, amount: uint256) -> bool:
                    self.allowance[owner][spender] = amount
                    log Approval(owner, spender, amount)
                    return True
                
                @internal
                def _permit(
                    owner: address, 
                    spender: address, 
                    amount: uint256, 
                    deadline: uint256, 
                    v: uint8, 
                    r: bytes32, 
                    s: bytes32
                ) -> bool:
                    assert owner != empty(address), "invalid owner"
                    assert deadline >= block.timestamp, "permit expired"
                    nonce: uint256 = self.nonces[owner]
                    digest: bytes32 = keccak256(
                        concat(
                            b'\x19\x01',
                            self.domain_separator(),
                            keccak256(
                                concat(
                                    PERMIT_TYPE_HASH,
                                    convert(owner, bytes32),
                                    convert(spender, bytes32),
                                    convert(amount, bytes32),
                                    convert(nonce, bytes32),
                                    convert(deadline, bytes32),
                                )
                            )
                        )
                    )
                    assert ecrecover(
                        digest, v, r, s
                    ) == owner, "invalid signature"
                
                    self.allowance[owner][spender] = amount
                    self.nonces[owner] = nonce + 1
                    log Approval(owner, spender, amount)
                    return True
                
                @internal
                def _burn_shares(shares: uint256, owner: address):
                    self.balance_of[owner] -= shares
                    self.total_supply = unsafe_sub(self.total_supply, shares)
                    log Transfer(owner, empty(address), shares)
                
                @view
                @internal
                def _unlocked_shares() -> uint256:
                    """
                    Returns the amount of shares that have been unlocked.
                    To avoid sudden price_per_share spikes, profits can be processed 
                    through an unlocking period. The mechanism involves shares to be 
                    minted to the vault which are unlocked gradually over time. Shares 
                    that have been locked are gradually unlocked over profit_max_unlock_time.
                    """
                    _full_profit_unlock_date: uint256 = self.full_profit_unlock_date
                    unlocked_shares: uint256 = 0
                    if _full_profit_unlock_date > block.timestamp:
                        # If we have not fully unlocked, we need to calculate how much has been.
                        unlocked_shares = self.profit_unlocking_rate * (block.timestamp - self.last_profit_update) / MAX_BPS_EXTENDED
                
                    elif _full_profit_unlock_date != 0:
                        # All shares have been unlocked
                        unlocked_shares = self.balance_of[self]
                
                    return unlocked_shares
                
                
                @view
                @internal
                def _total_supply() -> uint256:
                    # Need to account for the shares issued to the vault that have unlocked.
                    return self.total_supply - self._unlocked_shares()
                
                @view
                @internal
                def _total_assets() -> uint256:
                    """
                    Total amount of assets that are in the vault and in the strategies. 
                    """
                    return self.total_idle + self.total_debt
                
                @view
                @internal
                def _convert_to_assets(shares: uint256, rounding: Rounding) -> uint256:
                    """ 
                    assets = shares * (total_assets / total_supply) --- (== price_per_share * shares)
                    """
                    if shares == max_value(uint256) or shares == 0:
                        return shares
                
                    total_supply: uint256 = self._total_supply()
                    # if total_supply is 0, price_per_share is 1
                    if total_supply == 0: 
                        return shares
                
                    numerator: uint256 = shares * self._total_assets()
                    amount: uint256 = numerator / total_supply
                    if rounding == Rounding.ROUND_UP and numerator % total_supply != 0:
                        amount += 1
                
                    return amount
                
                @view
                @internal
                def _convert_to_shares(assets: uint256, rounding: Rounding) -> uint256:
                    """
                    shares = amount * (total_supply / total_assets) --- (== amount / price_per_share)
                    """
                    if assets == max_value(uint256) or assets == 0:
                        return assets
                
                    total_supply: uint256 = self._total_supply()
                    total_assets: uint256 = self._total_assets()
                
                    if total_assets == 0:
                        # if total_assets and total_supply is 0, price_per_share is 1
                        if total_supply == 0:
                            return assets
                        else:
                            # Else if total_supply > 0 price_per_share is 0
                            return 0
                
                    numerator: uint256 = assets * total_supply
                    shares: uint256 = numerator / total_assets
                    if rounding == Rounding.ROUND_UP and numerator % total_assets != 0:
                        shares += 1
                
                    return shares
                
                @internal
                def _erc20_safe_approve(token: address, spender: address, amount: uint256):
                    # Used only to approve tokens that are not the type managed by this Vault.
                    # Used to handle non-compliant tokens like USDT
                    assert ERC20(token).approve(spender, amount, default_return_value=True), "approval failed"
                
                @internal
                def _erc20_safe_transfer_from(token: address, sender: address, receiver: address, amount: uint256):
                    # Used only to transfer tokens that are not the type managed by this Vault.
                    # Used to handle non-compliant tokens like USDT
                    assert ERC20(token).transferFrom(sender, receiver, amount, default_return_value=True), "transfer failed"
                
                @internal
                def _erc20_safe_transfer(token: address, receiver: address, amount: uint256):
                    # Used only to send tokens that are not the type managed by this Vault.
                    # Used to handle non-compliant tokens like USDT
                    assert ERC20(token).transfer(receiver, amount, default_return_value=True), "transfer failed"
                
                @internal
                def _issue_shares(shares: uint256, recipient: address):
                    self.balance_of[recipient] = unsafe_add(self.balance_of[recipient], shares)
                    self.total_supply += shares
                
                    log Transfer(empty(address), recipient, shares)
                
                @internal
                def _issue_shares_for_amount(amount: uint256, recipient: address) -> uint256:
                    """
                    Issues shares that are worth 'amount' in the underlying token (asset).
                    WARNING: this takes into account that any new assets have been summed 
                    to total_assets (otherwise pps will go down).
                    """
                    total_supply: uint256 = self._total_supply()
                    total_assets: uint256 = self._total_assets()
                    new_shares: uint256 = 0
                    
                    # If no supply PPS = 1.
                    if total_supply == 0:
                        new_shares = amount
                    elif total_assets > amount:
                        new_shares = amount * total_supply / (total_assets - amount)
                
                    # We don't make the function revert
                    if new_shares == 0:
                       return 0
                
                    self._issue_shares(new_shares, recipient)
                
                    return new_shares
                
                ## ERC4626 ##
                @view
                @internal
                def _max_deposit(receiver: address) -> uint256: 
                    if receiver in [empty(address), self]:
                        return 0
                
                    # If there is a deposit limit module set use that.
                    deposit_limit_module: address = self.deposit_limit_module
                    if deposit_limit_module != empty(address):
                        return IDepositLimitModule(deposit_limit_module).available_deposit_limit(receiver)
                    
                    # Else use the standard flow.
                    _deposit_limit: uint256 = self.deposit_limit
                    if (_deposit_limit == max_value(uint256)):
                        return _deposit_limit
                
                    _total_assets: uint256 = self._total_assets()
                    if (_total_assets >= _deposit_limit):
                        return 0
                
                    return unsafe_sub(_deposit_limit, _total_assets)
                
                @view
                @internal
                def _max_withdraw(
                    owner: address,
                    max_loss: uint256,
                    strategies: DynArray[address, MAX_QUEUE]
                ) -> uint256:
                    """
                    @dev Returns the max amount of `asset` an `owner` can withdraw.
                
                    This will do a full simulation of the withdraw in order to determine
                    how much is currently liquid and if the `max_loss` would allow for the 
                    tx to not revert.
                
                    This will track any expected loss to check if the tx will revert, but
                    not account for it in the amount returned since it is unrealised and 
                    therefore will not be accounted for in the conversion rates.
                
                    i.e. If we have 100 debt and 10 of unrealised loss, the max we can get
                    out is 90, but a user of the vault will need to call withdraw with 100
                    in order to get the full 90 out.
                    """
                
                    # Get the max amount for the owner if fully liquid.
                    max_assets: uint256 = self._convert_to_assets(self.balance_of[owner], Rounding.ROUND_DOWN)
                
                    # If there is a withdraw limit module use that.
                    withdraw_limit_module: address = self.withdraw_limit_module
                    if withdraw_limit_module != empty(address):
                        return min(
                            # Use the min between the returned value and the max.
                            # Means the limit module doesn't need to account for balances or conversions.
                            IWithdrawLimitModule(withdraw_limit_module).available_withdraw_limit(owner, max_loss, strategies),
                            max_assets
                        )
                    
                    # See if we have enough idle to service the withdraw.
                    current_idle: uint256 = self.total_idle
                    if max_assets > current_idle:
                        # Track how much we can pull.
                        have: uint256 = current_idle
                        loss: uint256 = 0
                
                        # Cache the default queue.
                        _strategies: DynArray[address, MAX_QUEUE] = self.default_queue
                
                        # If a custom queue was passed, and we don't force the default queue.
                        if len(strategies) != 0 and not self.use_default_queue:
                            # Use the custom queue.
                            _strategies = strategies
                
                        for strategy in _strategies:
                            # Can't use an invalid strategy.
                            assert self.strategies[strategy].activation != 0, "inactive strategy"
                
                            # Get the maximum amount the vault would withdraw from the strategy.
                            to_withdraw: uint256 = min(
                                # What we still need for the full withdraw.
                                max_assets - have, 
                                # The current debt the strategy has.
                                self.strategies[strategy].current_debt
                            )
                
                            # Get any unrealised loss for the strategy.
                            unrealised_loss: uint256 = self._assess_share_of_unrealised_losses(strategy, to_withdraw)
                
                            # See if any limit is enforced by the strategy.
                            strategy_limit: uint256 = IStrategy(strategy).convertToAssets(
                                IStrategy(strategy).maxRedeem(self)
                            )
                
                            # Adjust accordingly if there is a max withdraw limit.
                            realizable_withdraw: uint256 = to_withdraw - unrealised_loss
                            if strategy_limit < realizable_withdraw:
                                if unrealised_loss != 0:
                                    # lower unrealised loss proportional to the limit.
                                    unrealised_loss = unrealised_loss * strategy_limit / realizable_withdraw
                
                                # Still count the unrealised loss as withdrawable.
                                to_withdraw = strategy_limit + unrealised_loss
                                
                            # If 0 move on to the next strategy.
                            if to_withdraw == 0:
                                continue
                
                            # If there would be a loss with a non-maximum `max_loss` value.
                            if unrealised_loss > 0 and max_loss < MAX_BPS:
                                # Check if the loss is greater than the allowed range.
                                if loss + unrealised_loss > (have + to_withdraw) * max_loss / MAX_BPS:
                                    # If so use the amounts up till now.
                                    break
                
                            # Add to what we can pull.
                            have += to_withdraw
                
                            # If we have all we need break.
                            if have >= max_assets:
                                break
                
                            # Add any unrealised loss to the total
                            loss += unrealised_loss
                
                        # Update the max after going through the queue.
                        # In case we broke early or exhausted the queue.
                        max_assets = have
                
                    return max_assets
                
                @internal
                def _deposit(sender: address, recipient: address, assets: uint256) -> uint256:
                    """
                    Used for `deposit` calls to transfer the amount of `asset` to the vault, 
                    issue the corresponding shares to the `recipient` and update all needed 
                    vault accounting.
                    """
                    assert self.shutdown == False # dev: shutdown
                    assert assets <= self._max_deposit(recipient), "exceed deposit limit"
                 
                    # Transfer the tokens to the vault first.
                    self._erc20_safe_transfer_from(self.asset, msg.sender, self, assets)
                    # Record the change in total assets.
                    self.total_idle += assets
                    
                    # Issue the corresponding shares for assets.
                    shares: uint256 = self._issue_shares_for_amount(assets, recipient)
                
                    assert shares > 0, "cannot mint zero"
                
                    log Deposit(sender, recipient, assets, shares)
                    return shares
                
                @internal
                def _mint(sender: address, recipient: address, shares: uint256) -> uint256:
                    """
                    Used for `mint` calls to issue the corresponding shares to the `recipient`,
                    transfer the amount of `asset` to the vault, and update all needed vault 
                    accounting.
                    """
                    assert self.shutdown == False # dev: shutdown
                    # Get corresponding amount of assets.
                    assets: uint256 = self._convert_to_assets(shares, Rounding.ROUND_UP)
                
                    assert assets > 0, "cannot deposit zero"
                    assert assets <= self._max_deposit(recipient), "exceed deposit limit"
                
                    # Transfer the tokens to the vault first.
                    self._erc20_safe_transfer_from(self.asset, msg.sender, self, assets)
                    # Record the change in total assets.
                    self.total_idle += assets
                    
                    # Issue the corresponding shares for assets.
                    self._issue_shares(shares, recipient)
                
                    log Deposit(sender, recipient, assets, shares)
                    return assets
                
                @view
                @internal
                def _assess_share_of_unrealised_losses(strategy: address, assets_needed: uint256) -> uint256:
                    """
                    Returns the share of losses that a user would take if withdrawing from this strategy
                    This accounts for losses that have been realized at the strategy level but not yet
                    realized at the vault level.
                
                    e.g. if the strategy has unrealised losses for 10% of its current debt and the user 
                    wants to withdraw 1_000 tokens, the losses that they will take is 100 token
                    """
                    # Minimum of how much debt the debt should be worth.
                    strategy_current_debt: uint256 = self.strategies[strategy].current_debt
                    # The actual amount that the debt is currently worth.
                    vault_shares: uint256 = IStrategy(strategy).balanceOf(self)
                    strategy_assets: uint256 = IStrategy(strategy).convertToAssets(vault_shares)
                    
                    # If no losses, return 0
                    if strategy_assets >= strategy_current_debt or strategy_current_debt == 0:
                        return 0
                
                    # Users will withdraw assets_needed divided by loss ratio (strategy_assets / strategy_current_debt - 1).
                    # NOTE: If there are unrealised losses, the user will take his share.
                    numerator: uint256 = assets_needed * strategy_assets
                    users_share_of_loss: uint256 = assets_needed - numerator / strategy_current_debt
                    # Always round up.
                    if numerator % strategy_current_debt != 0:
                        users_share_of_loss += 1
                
                    return users_share_of_loss
                
                @internal
                def _withdraw_from_strategy(strategy: address, assets_to_withdraw: uint256):
                    """
                    This takes the amount denominated in asset and performs a {redeem}
                    with the corresponding amount of shares.
                
                    We use {redeem} to natively take on losses without additional non-4626 standard parameters.
                    """
                    # Need to get shares since we use redeem to be able to take on losses.
                    shares_to_redeem: uint256 = min(
                        # Use previewWithdraw since it should round up.
                        IStrategy(strategy).previewWithdraw(assets_to_withdraw), 
                        # And check against our actual balance.
                        IStrategy(strategy).balanceOf(self)
                    )
                    # Redeem the shares.
                    IStrategy(strategy).redeem(shares_to_redeem, self, self)
                
                @internal
                def _redeem(
                    sender: address, 
                    receiver: address, 
                    owner: address,
                    assets: uint256,
                    shares: uint256, 
                    max_loss: uint256,
                    strategies: DynArray[address, MAX_QUEUE]
                ) -> uint256:
                    """
                    This will attempt to free up the full amount of assets equivalent to
                    `shares` and transfer them to the `receiver`. If the vault does
                    not have enough idle funds it will go through any strategies provided by
                    either the withdrawer or the default_queue to free up enough funds to 
                    service the request.
                
                    The vault will attempt to account for any unrealized losses taken on from
                    strategies since their respective last reports.
                
                    Any losses realized during the withdraw from a strategy will be passed on
                    to the user that is redeeming their vault shares unless it exceeds the given
                    `max_loss`.
                    """
                    assert receiver != empty(address), "ZERO ADDRESS"
                    assert shares > 0, "no shares to redeem"
                    assert assets > 0, "no assets to withdraw"
                    assert max_loss <= MAX_BPS, "max loss"
                    
                    # If there is a withdraw limit module, check the max.
                    withdraw_limit_module: address = self.withdraw_limit_module
                    if withdraw_limit_module != empty(address):
                        assert assets <= IWithdrawLimitModule(withdraw_limit_module).available_withdraw_limit(owner, max_loss, strategies), "exceed withdraw limit"
                
                    assert self.balance_of[owner] >= shares, "insufficient shares to redeem"
                    
                    if sender != owner:
                        self._spend_allowance(owner, sender, shares)
                
                    # The amount of the underlying token to withdraw.
                    requested_assets: uint256 = assets
                
                    # load to memory to save gas
                    current_total_idle: uint256 = self.total_idle
                    _asset: address = self.asset
                
                    # If there are not enough assets in the Vault contract, we try to free
                    # funds from strategies.
                    if requested_assets > current_total_idle:
                
                        # Cache the default queue.
                        _strategies: DynArray[address, MAX_QUEUE] = self.default_queue
                
                        # If a custom queue was passed, and we don't force the default queue.
                        if len(strategies) != 0 and not self.use_default_queue:
                            # Use the custom queue.
                            _strategies = strategies
                
                        # load to memory to save gas
                        current_total_debt: uint256 = self.total_debt
                
                        # Withdraw from strategies only what idle doesn't cover.
                        # `assets_needed` is the total amount we need to fill the request.
                        assets_needed: uint256 = unsafe_sub(requested_assets, current_total_idle)
                        # `assets_to_withdraw` is the amount to request from the current strategy.
                        assets_to_withdraw: uint256 = 0
                
                        # To compare against real withdrawals from strategies
                        previous_balance: uint256 = ERC20(_asset).balanceOf(self)
                
                        for strategy in _strategies:
                            # Make sure we have a valid strategy.
                            assert self.strategies[strategy].activation != 0, "inactive strategy"
                
                            # How much should the strategy have.
                            current_debt: uint256 = self.strategies[strategy].current_debt
                
                            # What is the max amount to withdraw from this strategy.
                            assets_to_withdraw = min(assets_needed, current_debt)
                
                            # Cache max_withdraw now for use if unrealized loss > 0
                            # Use maxRedeem and convert it since we use redeem.
                            max_withdraw: uint256 = IStrategy(strategy).convertToAssets(
                                IStrategy(strategy).maxRedeem(self)
                            )
                
                            # CHECK FOR UNREALISED LOSSES
                            # If unrealised losses > 0, then the user will take the proportional share 
                            # and realize it (required to avoid users withdrawing from lossy strategies).
                            # NOTE: strategies need to manage the fact that realising part of the loss can 
                            # mean the realisation of 100% of the loss!! (i.e. if for withdrawing 10% of the
                            # strategy it needs to unwind the whole position, generated losses might be bigger)
                            unrealised_losses_share: uint256 = self._assess_share_of_unrealised_losses(strategy, assets_to_withdraw)
                            if unrealised_losses_share > 0:
                                # If max withdraw is limiting the amount to pull, we need to adjust the portion of 
                                # the unrealized loss the user should take.
                                if max_withdraw < assets_to_withdraw - unrealised_losses_share:
                                    # How much would we want to withdraw
                                    wanted: uint256 = assets_to_withdraw - unrealised_losses_share
                                    # Get the proportion of unrealised comparing what we want vs. what we can get
                                    unrealised_losses_share = unrealised_losses_share * max_withdraw / wanted
                                    # Adjust assets_to_withdraw so all future calculations work correctly
                                    assets_to_withdraw = max_withdraw + unrealised_losses_share
                                
                                # User now "needs" less assets to be unlocked (as he took some as losses)
                                assets_to_withdraw -= unrealised_losses_share
                                requested_assets -= unrealised_losses_share
                                # NOTE: done here instead of waiting for regular update of these values 
                                # because it's a rare case (so we can save minor amounts of gas)
                                assets_needed -= unrealised_losses_share
                                current_total_debt -= unrealised_losses_share
                
                                # If max withdraw is 0 and unrealised loss is still > 0 then the strategy likely
                                # realized a 100% loss and we will need to realize that loss before moving on.
                                if max_withdraw == 0 and unrealised_losses_share > 0:
                                    # Adjust the strategy debt accordingly.
                                    new_debt: uint256 = current_debt - unrealised_losses_share
                        
                                    # Update strategies storage
                                    self.strategies[strategy].current_debt = new_debt
                                    # Log the debt update
                                    log DebtUpdated(strategy, current_debt, new_debt)
                
                            # Adjust based on the max withdraw of the strategy.
                            assets_to_withdraw = min(assets_to_withdraw, max_withdraw)
                
                            # Can't withdraw 0.
                            if assets_to_withdraw == 0:
                                continue
                            
                            # WITHDRAW FROM STRATEGY
                            self._withdraw_from_strategy(strategy, assets_to_withdraw)
                            post_balance: uint256 = ERC20(_asset).balanceOf(self)
                            
                            # Always check against the real amounts.
                            withdrawn: uint256 = post_balance - previous_balance
                            loss: uint256 = 0
                            # Check if we redeemed too much.
                            if withdrawn > assets_to_withdraw:
                                # Make sure we don't underflow in debt updates.
                                if withdrawn > current_debt:
                                    # Can't withdraw more than our debt.
                                    assets_to_withdraw = current_debt
                                else:
                                    # Add the extra to how much we withdrew.
                                    assets_to_withdraw += (unsafe_sub(withdrawn, assets_to_withdraw))
                
                            # If we have not received what we expected, we consider the difference a loss.
                            elif withdrawn < assets_to_withdraw:
                                loss = unsafe_sub(assets_to_withdraw, withdrawn)
                
                            # NOTE: strategy's debt decreases by the full amount but the total idle increases 
                            # by the actual amount only (as the difference is considered lost).
                            current_total_idle += (assets_to_withdraw - loss)
                            requested_assets -= loss
                            current_total_debt -= assets_to_withdraw
                
                            # Vault will reduce debt because the unrealised loss has been taken by user
                            new_debt: uint256 = current_debt - (assets_to_withdraw + unrealised_losses_share)
                        
                            # Update strategies storage
                            self.strategies[strategy].current_debt = new_debt
                            # Log the debt update
                            log DebtUpdated(strategy, current_debt, new_debt)
                
                            # Break if we have enough total idle to serve initial request.
                            if requested_assets <= current_total_idle:
                                break
                
                            # We update the previous_balance variable here to save gas in next iteration.
                            previous_balance = post_balance
                
                            # Reduce what we still need. Safe to use assets_to_withdraw 
                            # here since it has been checked against requested_assets
                            assets_needed -= assets_to_withdraw
                
                        # If we exhaust the queue and still have insufficient total idle, revert.
                        assert current_total_idle >= requested_assets, "insufficient assets in vault"
                        # Commit memory to storage.
                        self.total_debt = current_total_debt
                
                    # Check if there is a loss and a non-default value was set.
                    if assets > requested_assets and max_loss < MAX_BPS:
                        # Assure the loss is within the allowed range.
                        assert assets - requested_assets <= assets * max_loss / MAX_BPS, "too much loss"
                
                    # First burn the corresponding shares from the redeemer.
                    self._burn_shares(shares, owner)
                    # Commit memory to storage.
                    self.total_idle = current_total_idle - requested_assets
                    # Transfer the requested amount to the receiver.
                    self._erc20_safe_transfer(_asset, receiver, requested_assets)
                
                    log Withdraw(sender, receiver, owner, requested_assets, shares)
                    return requested_assets
                
                ## STRATEGY MANAGEMENT ##
                @internal
                def _add_strategy(new_strategy: address, add_to_queue: bool):
                    assert new_strategy not in [self, empty(address)], "strategy cannot be zero address"
                    assert IStrategy(new_strategy).asset() == self.asset, "invalid asset"
                    assert self.strategies[new_strategy].activation == 0, "strategy already active"
                
                    # Add the new strategy to the mapping.
                    self.strategies[new_strategy] = StrategyParams({
                        activation: block.timestamp,
                        last_report: block.timestamp,
                        current_debt: 0,
                        max_debt: 0
                    })
                
                    # If we are adding to the queue and the default queue has space, add the strategy.
                    if add_to_queue and len(self.default_queue) < MAX_QUEUE:
                        self.default_queue.append(new_strategy)        
                        
                    log StrategyChanged(new_strategy, StrategyChangeType.ADDED)
                
                @internal
                def _revoke_strategy(strategy: address, force: bool=False):
                    assert self.strategies[strategy].activation != 0, "strategy not active"
                
                    # If force revoking a strategy, it will cause a loss.
                    loss: uint256 = 0
                    
                    if self.strategies[strategy].current_debt != 0:
                        assert force, "strategy has debt"
                        # Vault realizes the full loss of outstanding debt.
                        loss = self.strategies[strategy].current_debt
                        # Adjust total vault debt.
                        self.total_debt -= loss
                
                        log StrategyReported(strategy, 0, loss, 0, 0, 0, 0)
                
                    # Set strategy params all back to 0 (WARNING: it can be re-added).
                    self.strategies[strategy] = StrategyParams({
                      activation: 0,
                      last_report: 0,
                      current_debt: 0,
                      max_debt: 0
                    })
                
                    # Remove strategy if it is in the default queue.
                    new_queue: DynArray[address, MAX_QUEUE] = []
                    for _strategy in self.default_queue:
                        # Add all strategies to the new queue besides the one revoked.
                        if _strategy != strategy:
                            new_queue.append(_strategy)
                        
                    # Set the default queue to our updated queue.
                    self.default_queue = new_queue
                
                    log StrategyChanged(strategy, StrategyChangeType.REVOKED)
                
                # DEBT MANAGEMENT #
                @internal
                def _update_debt(strategy: address, target_debt: uint256, max_loss: uint256) -> uint256:
                    """
                    The vault will re-balance the debt vs target debt. Target debt must be
                    smaller or equal to strategy's max_debt. This function will compare the 
                    current debt with the target debt and will take funds or deposit new 
                    funds to the strategy. 
                
                    The strategy can require a maximum amount of funds that it wants to receive
                    to invest. The strategy can also reject freeing funds if they are locked.
                    """
                    # How much we want the strategy to have.
                    new_debt: uint256 = target_debt
                    # How much the strategy currently has.
                    current_debt: uint256 = self.strategies[strategy].current_debt
                
                    # If the vault is shutdown we can only pull funds.
                    if self.shutdown:
                        new_debt = 0
                
                    assert new_debt != current_debt, "new debt equals current debt"
                
                    if current_debt > new_debt:
                        # Reduce debt.
                        assets_to_withdraw: uint256 = unsafe_sub(current_debt, new_debt)
                
                        # Ensure we always have minimum_total_idle when updating debt.
                        minimum_total_idle: uint256 = self.minimum_total_idle
                        total_idle: uint256 = self.total_idle
                        
                        # Respect minimum total idle in vault
                        if total_idle + assets_to_withdraw < minimum_total_idle:
                            assets_to_withdraw = unsafe_sub(minimum_total_idle, total_idle)
                            # Cant withdraw more than the strategy has.
                            if assets_to_withdraw > current_debt:
                                assets_to_withdraw = current_debt
                
                        # Check how much we are able to withdraw.
                        # Use maxRedeem and convert since we use redeem.
                        withdrawable: uint256 = IStrategy(strategy).convertToAssets(
                            IStrategy(strategy).maxRedeem(self)
                        )
                        assert withdrawable != 0, "nothing to withdraw"
                
                        # If insufficient withdrawable, withdraw what we can.
                        if withdrawable < assets_to_withdraw:
                            assets_to_withdraw = withdrawable
                
                        # If there are unrealised losses we don't let the vault reduce its debt until there is a new report
                        unrealised_losses_share: uint256 = self._assess_share_of_unrealised_losses(strategy, assets_to_withdraw)
                        assert unrealised_losses_share == 0, "strategy has unrealised losses"
                        
                        # Cache for repeated use.
                        _asset: address = self.asset
                
                        # Always check the actual amount withdrawn.
                        pre_balance: uint256 = ERC20(_asset).balanceOf(self)
                        self._withdraw_from_strategy(strategy, assets_to_withdraw)
                        post_balance: uint256 = ERC20(_asset).balanceOf(self)
                        
                        # making sure we are changing idle according to the real result no matter what. 
                        # We pull funds with {redeem} so there can be losses or rounding differences.
                        withdrawn: uint256 = min(post_balance - pre_balance, current_debt)
                
                        # If we didn't get the amount we asked for and there is a max loss.
                        if withdrawn < assets_to_withdraw and max_loss < MAX_BPS:
                            # Make sure the loss is within the allowed range.
                            assert assets_to_withdraw - withdrawn <= assets_to_withdraw * max_loss / MAX_BPS, "too much loss"
                
                        # If we got too much make sure not to increase PPS.
                        elif withdrawn > assets_to_withdraw:
                            assets_to_withdraw = withdrawn
                
                        # Update storage.
                        self.total_idle += withdrawn # actual amount we got.
                        # Amount we tried to withdraw in case of losses
                        self.total_debt -= assets_to_withdraw 
                
                        new_debt = current_debt - assets_to_withdraw
                    else: 
                        # We are increasing the strategies debt
                
                        # Revert if target_debt cannot be achieved due to configured max_debt for given strategy
                        assert new_debt <= self.strategies[strategy].max_debt, "target debt higher than max debt"
                
                        # Vault is increasing debt with the strategy by sending more funds.
                        max_deposit: uint256 = IStrategy(strategy).maxDeposit(self)
                        assert max_deposit != 0, "nothing to deposit"
                
                        # Deposit the difference between desired and current.
                        assets_to_deposit: uint256 = new_debt - current_debt
                        if assets_to_deposit > max_deposit:
                            # Deposit as much as possible.
                            assets_to_deposit = max_deposit
                        
                        # Ensure we always have minimum_total_idle when updating debt.
                        minimum_total_idle: uint256 = self.minimum_total_idle
                        total_idle: uint256 = self.total_idle
                
                        assert total_idle > minimum_total_idle, "no funds to deposit"
                        available_idle: uint256 = unsafe_sub(total_idle, minimum_total_idle)
                
                        # If insufficient funds to deposit, transfer only what is free.
                        if assets_to_deposit > available_idle:
                            assets_to_deposit = available_idle
                
                        # Can't Deposit 0.
                        if assets_to_deposit > 0:
                            # Cache for repeated use.
                            _asset: address = self.asset
                
                            # Approve the strategy to pull only what we are giving it.
                            self._erc20_safe_approve(_asset, strategy, assets_to_deposit)
                
                            # Always update based on actual amounts deposited.
                            pre_balance: uint256 = ERC20(_asset).balanceOf(self)
                            IStrategy(strategy).deposit(assets_to_deposit, self)
                            post_balance: uint256 = ERC20(_asset).balanceOf(self)
                
                            # Make sure our approval is always back to 0.
                            self._erc20_safe_approve(_asset, strategy, 0)
                
                            # Making sure we are changing according to the real result no 
                            # matter what. This will spend more gas but makes it more robust.
                            assets_to_deposit = pre_balance - post_balance
                
                            # Update storage.
                            self.total_idle -= assets_to_deposit
                            self.total_debt += assets_to_deposit
                
                        new_debt = current_debt + assets_to_deposit
                
                    # Commit memory to storage.
                    self.strategies[strategy].current_debt = new_debt
                
                    log DebtUpdated(strategy, current_debt, new_debt)
                    return new_debt
                
                ## ACCOUNTING MANAGEMENT ##
                @internal
                def _process_report(strategy: address) -> (uint256, uint256):
                    """
                    Processing a report means comparing the debt that the strategy has taken 
                    with the current amount of funds it is reporting. If the strategy owes 
                    less than it currently has, it means it has had a profit, else (assets < debt) 
                    it has had a loss.
                
                    Different strategies might choose different reporting strategies: pessimistic, 
                    only realised P&L, ... The best way to report depends on the strategy.
                
                    The profit will be distributed following a smooth curve over the vaults 
                    profit_max_unlock_time seconds. Losses will be taken immediately, first from the 
                    profit buffer (avoiding an impact in pps), then will reduce pps.
                
                    Any applicable fees are charged and distributed during the report as well
                    to the specified recipients.
                    """
                    # Make sure we have a valid strategy.
                    assert self.strategies[strategy].activation != 0, "inactive strategy"
                
                    # Vault assesses profits using 4626 compliant interface. 
                    # NOTE: It is important that a strategies `convertToAssets` implementation
                    # cannot be manipulated or else the vault could report incorrect gains/losses.
                    strategy_shares: uint256 = IStrategy(strategy).balanceOf(self)
                    # How much the vaults position is worth.
                    total_assets: uint256 = IStrategy(strategy).convertToAssets(strategy_shares)
                    # How much the vault had deposited to the strategy.
                    current_debt: uint256 = self.strategies[strategy].current_debt
                
                    gain: uint256 = 0
                    loss: uint256 = 0
                
                    ### Asses Gain or Loss ###
                
                    # Compare reported assets vs. the current debt.
                    if total_assets > current_debt:
                        # We have a gain.
                        gain = unsafe_sub(total_assets, current_debt)
                    else:
                        # We have a loss.
                        loss = unsafe_sub(current_debt, total_assets)
                    
                    # Cache `asset` for repeated use.
                    _asset: address = self.asset
                
                    ### Asses Fees and Refunds ###
                
                    # For Accountant fee assessment.
                    total_fees: uint256 = 0
                    total_refunds: uint256 = 0
                    # If accountant is not set, fees and refunds remain unchanged.
                    accountant: address = self.accountant
                    if accountant != empty(address):
                        total_fees, total_refunds = IAccountant(accountant).report(strategy, gain, loss)
                
                        if total_refunds > 0:
                            # Make sure we have enough approval and enough asset to pull.
                            total_refunds = min(total_refunds, min(ERC20(_asset).balanceOf(accountant), ERC20(_asset).allowance(accountant, self)))
                
                    # Total fees to charge in shares.
                    total_fees_shares: uint256 = 0
                    # For Protocol fee assessment.
                    protocol_fee_bps: uint16 = 0
                    protocol_fees_shares: uint256 = 0
                    protocol_fee_recipient: address = empty(address)
                    # `shares_to_burn` is derived from amounts that would reduce the vaults PPS.
                    # NOTE: this needs to be done before any pps changes
                    shares_to_burn: uint256 = 0
                    # Only need to burn shares if there is a loss or fees.
                    if loss + total_fees > 0:
                        # The amount of shares we will want to burn to offset losses and fees.
                        shares_to_burn = self._convert_to_shares(loss + total_fees, Rounding.ROUND_UP)
                
                        # If we have fees then get the proportional amount of shares to issue.
                        if total_fees > 0:
                            # Get the total amount shares to issue for the fees.
                            total_fees_shares = shares_to_burn * total_fees / (loss + total_fees)
                
                            # Get the protocol fee config for this vault.
                            protocol_fee_bps, protocol_fee_recipient = IFactory(self.factory).protocol_fee_config()
                
                            # If there is a protocol fee.
                            if protocol_fee_bps > 0:
                                # Get the percent of fees to go to protocol fees.
                                protocol_fees_shares = total_fees_shares * convert(protocol_fee_bps, uint256) / MAX_BPS
                
                
                    # Shares to lock is any amount that would otherwise increase the vaults PPS.
                    shares_to_lock: uint256 = 0
                    profit_max_unlock_time: uint256 = self.profit_max_unlock_time
                    # Get the amount we will lock to avoid a PPS increase.
                    if gain + total_refunds > 0 and profit_max_unlock_time != 0:
                        shares_to_lock = self._convert_to_shares(gain + total_refunds, Rounding.ROUND_DOWN)
                
                    # The total current supply including locked shares.
                    total_supply: uint256 = self.total_supply
                    # The total shares the vault currently owns. Both locked and unlocked.
                    total_locked_shares: uint256 = self.balance_of[self]
                    # Get the desired end amount of shares after all accounting.
                    ending_supply: uint256 = total_supply + shares_to_lock - shares_to_burn - self._unlocked_shares()
                    
                    # If we will end with more shares than we have now.
                    if ending_supply > total_supply:
                        # Issue the difference.
                        self._issue_shares(unsafe_sub(ending_supply, total_supply), self)
                
                    # Else we need to burn shares.
                    elif total_supply > ending_supply:
                        # Can't burn more than the vault owns.
                        to_burn: uint256 = min(unsafe_sub(total_supply, ending_supply), total_locked_shares)
                        self._burn_shares(to_burn, self)
                
                    # Adjust the amount to lock for this period.
                    if shares_to_lock > shares_to_burn:
                        # Don't lock fees or losses.
                        shares_to_lock = unsafe_sub(shares_to_lock, shares_to_burn)
                    else:
                        shares_to_lock = 0
                
                    # Pull refunds
                    if total_refunds > 0:
                        # Transfer the refunded amount of asset to the vault.
                        self._erc20_safe_transfer_from(_asset, accountant, self, total_refunds)
                        # Update storage to increase total assets.
                        self.total_idle += total_refunds
                
                    # Record any reported gains.
                    if gain > 0:
                        # NOTE: this will increase total_assets
                        current_debt = unsafe_add(current_debt, gain)
                        self.strategies[strategy].current_debt = current_debt
                        self.total_debt += gain
                
                    # Or record any reported loss
                    elif loss > 0:
                        current_debt = unsafe_sub(current_debt, loss)
                        self.strategies[strategy].current_debt = current_debt
                        self.total_debt -= loss
                
                    # Issue shares for fees that were calculated above if applicable.
                    if total_fees_shares > 0:
                        # Accountant fees are (total_fees - protocol_fees).
                        self._issue_shares(total_fees_shares - protocol_fees_shares, accountant)
                
                        # If we also have protocol fees.
                        if protocol_fees_shares > 0:
                            self._issue_shares(protocol_fees_shares, protocol_fee_recipient)
                
                    # Update unlocking rate and time to fully unlocked.
                    total_locked_shares = self.balance_of[self]
                    if total_locked_shares > 0:
                        previously_locked_time: uint256 = 0
                        _full_profit_unlock_date: uint256 = self.full_profit_unlock_date
                        # Check if we need to account for shares still unlocking.
                        if _full_profit_unlock_date > block.timestamp: 
                            # There will only be previously locked shares if time remains.
                            # We calculate this here since it will not occur every time we lock shares.
                            previously_locked_time = (total_locked_shares - shares_to_lock) * (_full_profit_unlock_date - block.timestamp)
                
                        # new_profit_locking_period is a weighted average between the remaining time of the previously locked shares and the profit_max_unlock_time
                        new_profit_locking_period: uint256 = (previously_locked_time + shares_to_lock * profit_max_unlock_time) / total_locked_shares
                        # Calculate how many shares unlock per second.
                        self.profit_unlocking_rate = total_locked_shares * MAX_BPS_EXTENDED / new_profit_locking_period
                        # Calculate how long until the full amount of shares is unlocked.
                        self.full_profit_unlock_date = block.timestamp + new_profit_locking_period
                        # Update the last profitable report timestamp.
                        self.last_profit_update = block.timestamp
                    else:
                        # NOTE: only setting this to the 0 will turn in the desired effect, 
                        # no need to update profit_unlocking_rate
                        self.full_profit_unlock_date = 0
                    
                    # Record the report of profit timestamp.
                    self.strategies[strategy].last_report = block.timestamp
                
                    # We have to recalculate the fees paid for cases with an overall loss or no profit locking
                    if loss + total_fees > gain + total_refunds or profit_max_unlock_time == 0:
                        total_fees = self._convert_to_assets(total_fees_shares, Rounding.ROUND_DOWN)
                
                    log StrategyReported(
                        strategy,
                        gain,
                        loss,
                        current_debt,
                        total_fees * convert(protocol_fee_bps, uint256) / MAX_BPS, # Protocol Fees
                        total_fees,
                        total_refunds
                    )
                
                    return (gain, loss)
                
                # SETTERS #
                @external
                def set_accountant(new_accountant: address):
                    """
                    @notice Set the new accountant address.
                    @param new_accountant The new accountant address.
                    """
                    self._enforce_role(msg.sender, Roles.ACCOUNTANT_MANAGER)
                    self.accountant = new_accountant
                
                    log UpdateAccountant(new_accountant)
                
                @external
                def set_default_queue(new_default_queue: DynArray[address, MAX_QUEUE]):
                    """
                    @notice Set the new default queue array.
                    @dev Will check each strategy to make sure it is active. But will not
                        check that the same strategy is not added twice. maxRedeem and maxWithdraw
                        return values may be inaccurate if a strategy is added twice.
                    @param new_default_queue The new default queue array.
                    """
                    self._enforce_role(msg.sender, Roles.QUEUE_MANAGER)
                
                    # Make sure every strategy in the new queue is active.
                    for strategy in new_default_queue:
                        assert self.strategies[strategy].activation != 0, "!inactive"
                
                    # Save the new queue.
                    self.default_queue = new_default_queue
                
                    log UpdateDefaultQueue(new_default_queue)
                
                @external
                def set_use_default_queue(use_default_queue: bool):
                    """
                    @notice Set a new value for `use_default_queue`.
                    @dev If set `True` the default queue will always be
                        used no matter whats passed in.
                    @param use_default_queue new value.
                    """
                    self._enforce_role(msg.sender, Roles.QUEUE_MANAGER)
                    self.use_default_queue = use_default_queue
                
                    log UpdateUseDefaultQueue(use_default_queue)
                
                @external
                def set_deposit_limit(deposit_limit: uint256, override: bool = False):
                    """
                    @notice Set the new deposit limit.
                    @dev Can not be changed if a deposit_limit_module
                    is set unless the override flag is true or if shutdown.
                    @param deposit_limit The new deposit limit.
                    @param override If a `deposit_limit_module` already set should be overridden.
                    """
                    assert self.shutdown == False # Dev: shutdown
                    self._enforce_role(msg.sender, Roles.DEPOSIT_LIMIT_MANAGER)
                
                    # If we are overriding the deposit limit module.
                    if override:
                        # Make sure it is set to address 0 if not already.
                        if self.deposit_limit_module != empty(address):
                
                            self.deposit_limit_module = empty(address)
                            log UpdateDepositLimitModule(empty(address))
                    else:  
                        # Make sure the deposit_limit_module has been set to address(0).
                        assert self.deposit_limit_module == empty(address), "using module"
                
                    self.deposit_limit = deposit_limit
                
                    log UpdateDepositLimit(deposit_limit)
                
                @external
                def set_deposit_limit_module(deposit_limit_module: address, override: bool = False):
                    """
                    @notice Set a contract to handle the deposit limit.
                    @dev The default `deposit_limit` will need to be set to
                    max uint256 since the module will override it or the override flag
                    must be set to true to set it to max in 1 tx..
                    @param deposit_limit_module Address of the module.
                    @param override If a `deposit_limit` already set should be overridden.
                    """
                    assert self.shutdown == False # Dev: shutdown
                    self._enforce_role(msg.sender, Roles.DEPOSIT_LIMIT_MANAGER)
                
                    # If we are overriding the deposit limit
                    if override:
                        # Make sure it is max uint256 if not already.
                        if self.deposit_limit != max_value(uint256):
                
                            self.deposit_limit = max_value(uint256)
                            log UpdateDepositLimit(max_value(uint256))
                    else:
                        # Make sure the deposit_limit has been set to uint max.
                        assert self.deposit_limit == max_value(uint256), "using deposit limit"
                
                    self.deposit_limit_module = deposit_limit_module
                
                    log UpdateDepositLimitModule(deposit_limit_module)
                
                @external
                def set_withdraw_limit_module(withdraw_limit_module: address):
                    """
                    @notice Set a contract to handle the withdraw limit.
                    @dev This will override the default `max_withdraw`.
                    @param withdraw_limit_module Address of the module.
                    """
                    self._enforce_role(msg.sender, Roles.WITHDRAW_LIMIT_MANAGER)
                
                    self.withdraw_limit_module = withdraw_limit_module
                
                    log UpdateWithdrawLimitModule(withdraw_limit_module)
                
                @external
                def set_minimum_total_idle(minimum_total_idle: uint256):
                    """
                    @notice Set the new minimum total idle.
                    @param minimum_total_idle The new minimum total idle.
                    """
                    self._enforce_role(msg.sender, Roles.MINIMUM_IDLE_MANAGER)
                    self.minimum_total_idle = minimum_total_idle
                
                    log UpdateMinimumTotalIdle(minimum_total_idle)
                
                @external
                def setProfitMaxUnlockTime(new_profit_max_unlock_time: uint256):
                    """
                    @notice Set the new profit max unlock time.
                    @dev The time is denominated in seconds and must be less than 1 year.
                        We only need to update locking period if setting to 0,
                        since the current period will use the old rate and on the next
                        report it will be reset with the new unlocking time.
                    
                        Setting to 0 will cause any currently locked profit to instantly
                        unlock and an immediate increase in the vaults Price Per Share.
                
                    @param new_profit_max_unlock_time The new profit max unlock time.
                    """
                    self._enforce_role(msg.sender, Roles.PROFIT_UNLOCK_MANAGER)
                    # Must be less than one year for report cycles
                    assert new_profit_max_unlock_time <= 31_556_952, "profit unlock time too long"
                
                    # If setting to 0 we need to reset any locked values.
                    if (new_profit_max_unlock_time == 0):
                
                        share_balance: uint256 = self.balance_of[self]
                        if share_balance > 0:
                            # Burn any shares the vault still has.
                            self._burn_shares(share_balance, self)
                
                        # Reset unlocking variables to 0.
                        self.profit_unlocking_rate = 0
                        self.full_profit_unlock_date = 0
                
                    self.profit_max_unlock_time = new_profit_max_unlock_time
                
                    log UpdateProfitMaxUnlockTime(new_profit_max_unlock_time)
                
                # ROLE MANAGEMENT #
                @internal
                def _enforce_role(account: address, role: Roles):
                    # Make sure the sender holds the role.
                    assert role in self.roles[account], "not allowed"
                
                @external
                def set_role(account: address, role: Roles):
                    """
                    @notice Set the roles for an account.
                    @dev This will fully override an accounts current roles
                     so it should include all roles the account should hold.
                    @param account The account to set the role for.
                    @param role The roles the account should hold.
                    """
                    assert msg.sender == self.role_manager
                    self.roles[account] = role
                
                    log RoleSet(account, role)
                
                @external
                def add_role(account: address, role: Roles):
                    """
                    @notice Add a new role to an address.
                    @dev This will add a new role to the account
                     without effecting any of the previously held roles.
                    @param account The account to add a role to.
                    @param role The new role to add to account.
                    """
                    assert msg.sender == self.role_manager
                    self.roles[account] = self.roles[account] | role
                
                    log RoleSet(account, self.roles[account])
                
                @external
                def remove_role(account: address, role: Roles):
                    """
                    @notice Remove a single role from an account.
                    @dev This will leave all other roles for the 
                     account unchanged.
                    @param account The account to remove a Role from.
                    @param role The Role to remove.
                    """
                    assert msg.sender == self.role_manager
                    self.roles[account] = self.roles[account] & ~role
                
                    log RoleSet(account, self.roles[account])
                    
                @external
                def transfer_role_manager(role_manager: address):
                    """
                    @notice Step 1 of 2 in order to transfer the 
                        role manager to a new address. This will set
                        the future_role_manager. Which will then need
                        to be accepted by the new manager.
                    @param role_manager The new role manager address.
                    """
                    assert msg.sender == self.role_manager
                    self.future_role_manager = role_manager
                
                @external
                def accept_role_manager():
                    """
                    @notice Accept the role manager transfer.
                    """
                    assert msg.sender == self.future_role_manager
                    self.role_manager = msg.sender
                    self.future_role_manager = empty(address)
                
                    log UpdateRoleManager(msg.sender)
                
                # VAULT STATUS VIEWS
                
                @view
                @external
                def isShutdown() -> bool:
                    """
                    @notice Get if the vault is shutdown.
                    @return Bool representing the shutdown status
                    """
                    return self.shutdown
                @view
                @external
                def unlockedShares() -> uint256:
                    """
                    @notice Get the amount of shares that have been unlocked.
                    @return The amount of shares that are have been unlocked.
                    """
                    return self._unlocked_shares()
                
                @view
                @external
                def pricePerShare() -> uint256:
                    """
                    @notice Get the price per share (pps) of the vault.
                    @dev This value offers limited precision. Integrations that require 
                        exact precision should use convertToAssets or convertToShares instead.
                    @return The price per share.
                    """
                    return self._convert_to_assets(10 ** convert(self.decimals, uint256), Rounding.ROUND_DOWN)
                
                @view
                @external
                def get_default_queue() -> DynArray[address, MAX_QUEUE]:
                    """
                    @notice Get the full default queue currently set.
                    @return The current default withdrawal queue.
                    """
                    return self.default_queue
                
                ## REPORTING MANAGEMENT ##
                @external
                @nonreentrant("lock")
                def process_report(strategy: address) -> (uint256, uint256):
                    """
                    @notice Process the report of a strategy.
                    @param strategy The strategy to process the report for.
                    @return The gain and loss of the strategy.
                    """
                    self._enforce_role(msg.sender, Roles.REPORTING_MANAGER)
                    return self._process_report(strategy)
                
                @external
                @nonreentrant("lock")
                def buy_debt(strategy: address, amount: uint256):
                    """
                    @notice Used for governance to buy bad debt from the vault.
                    @dev This should only ever be used in an emergency in place
                    of force revoking a strategy in order to not report a loss.
                    It allows the DEBT_PURCHASER role to buy the strategies debt
                    for an equal amount of `asset`. 
                
                    @param strategy The strategy to buy the debt for
                    @param amount The amount of debt to buy from the vault.
                    """
                    self._enforce_role(msg.sender, Roles.DEBT_PURCHASER)
                    assert self.strategies[strategy].activation != 0, "not active"
                    
                    # Cache the current debt.
                    current_debt: uint256 = self.strategies[strategy].current_debt
                    _amount: uint256 = amount
                
                    assert current_debt > 0, "nothing to buy"
                    assert _amount > 0, "nothing to buy with"
                    
                    if _amount > current_debt:
                        _amount = current_debt
                
                    # We get the proportion of the debt that is being bought and
                    # transfer the equivalent shares. We assume this is being used
                    # due to strategy issues so won't rely on its conversion rates.
                    shares: uint256 = IStrategy(strategy).balanceOf(self) * _amount / current_debt
                
                    assert shares > 0, "cannot buy zero"
                
                    self._erc20_safe_transfer_from(self.asset, msg.sender, self, _amount)
                
                    # Lower strategy debt
                    self.strategies[strategy].current_debt -= _amount
                    # lower total debt
                    self.total_debt -= _amount
                    # Increase total idle
                    self.total_idle += _amount
                
                    # log debt change
                    log DebtUpdated(strategy, current_debt, current_debt - _amount)
                
                    # Transfer the strategies shares out.
                    self._erc20_safe_transfer(strategy, msg.sender, shares)
                
                    log DebtPurchased(strategy, _amount)
                
                ## STRATEGY MANAGEMENT ##
                @external
                def add_strategy(new_strategy: address, add_to_queue: bool=True):
                    """
                    @notice Add a new strategy.
                    @param new_strategy The new strategy to add.
                    """
                    self._enforce_role(msg.sender, Roles.ADD_STRATEGY_MANAGER)
                    self._add_strategy(new_strategy, add_to_queue)
                
                @external
                def revoke_strategy(strategy: address):
                    """
                    @notice Revoke a strategy.
                    @param strategy The strategy to revoke.
                    """
                    self._enforce_role(msg.sender, Roles.REVOKE_STRATEGY_MANAGER)
                    self._revoke_strategy(strategy)
                
                @external
                def force_revoke_strategy(strategy: address):
                    """
                    @notice Force revoke a strategy.
                    @dev The vault will remove the strategy and write off any debt left 
                        in it as a loss. This function is a dangerous function as it can force a 
                        strategy to take a loss. All possible assets should be removed from the 
                        strategy first via update_debt. If a strategy is removed erroneously it 
                        can be re-added and the loss will be credited as profit. Fees will apply.
                    @param strategy The strategy to force revoke.
                    """
                    self._enforce_role(msg.sender, Roles.FORCE_REVOKE_MANAGER)
                    self._revoke_strategy(strategy, True)
                
                ## DEBT MANAGEMENT ##
                @external
                def update_max_debt_for_strategy(strategy: address, new_max_debt: uint256):
                    """
                    @notice Update the max debt for a strategy.
                    @param strategy The strategy to update the max debt for.
                    @param new_max_debt The new max debt for the strategy.
                    """
                    self._enforce_role(msg.sender, Roles.MAX_DEBT_MANAGER)
                    assert self.strategies[strategy].activation != 0, "inactive strategy"
                    self.strategies[strategy].max_debt = new_max_debt
                
                    log UpdatedMaxDebtForStrategy(msg.sender, strategy, new_max_debt)
                
                @external
                @nonreentrant("lock")
                def update_debt(
                    strategy: address, 
                    target_debt: uint256, 
                    max_loss: uint256 = MAX_BPS
                ) -> uint256:
                    """
                    @notice Update the debt for a strategy.
                    @param strategy The strategy to update the debt for.
                    @param target_debt The target debt for the strategy.
                    @param max_loss Optional to check realized losses on debt decreases.
                    @return The amount of debt added or removed.
                    """
                    self._enforce_role(msg.sender, Roles.DEBT_MANAGER)
                    return self._update_debt(strategy, target_debt, max_loss)
                
                ## EMERGENCY MANAGEMENT ##
                @external
                def shutdown_vault():
                    """
                    @notice Shutdown the vault.
                    """
                    self._enforce_role(msg.sender, Roles.EMERGENCY_MANAGER)
                    assert self.shutdown == False
                    
                    # Shutdown the vault.
                    self.shutdown = True
                
                    # Set deposit limit to 0.
                    if self.deposit_limit_module != empty(address):
                        self.deposit_limit_module = empty(address)
                
                        log UpdateDepositLimitModule(empty(address))
                
                    self.deposit_limit = 0
                    log UpdateDepositLimit(0)
                
                    self.roles[msg.sender] = self.roles[msg.sender] | Roles.DEBT_MANAGER
                    log Shutdown()
                
                
                ## SHARE MANAGEMENT ##
                ## ERC20 + ERC4626 ##
                @external
                @nonreentrant("lock")
                def deposit(assets: uint256, receiver: address) -> uint256:
                    """
                    @notice Deposit assets into the vault.
                    @param assets The amount of assets to deposit.
                    @param receiver The address to receive the shares.
                    @return The amount of shares minted.
                    """
                    return self._deposit(msg.sender, receiver, assets)
                
                @external
                @nonreentrant("lock")
                def mint(shares: uint256, receiver: address) -> uint256:
                    """
                    @notice Mint shares for the receiver.
                    @param shares The amount of shares to mint.
                    @param receiver The address to receive the shares.
                    @return The amount of assets deposited.
                    """
                    return self._mint(msg.sender, receiver, shares)
                
                @external
                @nonreentrant("lock")
                def withdraw(
                    assets: uint256, 
                    receiver: address, 
                    owner: address, 
                    max_loss: uint256 = 0,
                    strategies: DynArray[address, MAX_QUEUE] = []
                ) -> uint256:
                    """
                    @notice Withdraw an amount of asset to `receiver` burning `owner`s shares.
                    @dev The default behavior is to not allow any loss.
                    @param assets The amount of asset to withdraw.
                    @param receiver The address to receive the assets.
                    @param owner The address who's shares are being burnt.
                    @param max_loss Optional amount of acceptable loss in Basis Points.
                    @param strategies Optional array of strategies to withdraw from.
                    @return The amount of shares actually burnt.
                    """
                    shares: uint256 = self._convert_to_shares(assets, Rounding.ROUND_UP)
                    self._redeem(msg.sender, receiver, owner, assets, shares, max_loss, strategies)
                    return shares
                
                @external
                @nonreentrant("lock")
                def redeem(
                    shares: uint256, 
                    receiver: address, 
                    owner: address, 
                    max_loss: uint256 = MAX_BPS,
                    strategies: DynArray[address, MAX_QUEUE] = []
                ) -> uint256:
                    """
                    @notice Redeems an amount of shares of `owners` shares sending funds to `receiver`.
                    @dev The default behavior is to allow losses to be realized.
                    @param shares The amount of shares to burn.
                    @param receiver The address to receive the assets.
                    @param owner The address who's shares are being burnt.
                    @param max_loss Optional amount of acceptable loss in Basis Points.
                    @param strategies Optional array of strategies to withdraw from.
                    @return The amount of assets actually withdrawn.
                    """
                    assets: uint256 = self._convert_to_assets(shares, Rounding.ROUND_DOWN)
                    # Always return the actual amount of assets withdrawn.
                    return self._redeem(msg.sender, receiver, owner, assets, shares, max_loss, strategies)
                
                
                @external
                def approve(spender: address, amount: uint256) -> bool:
                    """
                    @notice Approve an address to spend the vault's shares.
                    @param spender The address to approve.
                    @param amount The amount of shares to approve.
                    @return True if the approval was successful.
                    """
                    return self._approve(msg.sender, spender, amount)
                
                @external
                def transfer(receiver: address, amount: uint256) -> bool:
                    """
                    @notice Transfer shares to a receiver.
                    @param receiver The address to transfer shares to.
                    @param amount The amount of shares to transfer.
                    @return True if the transfer was successful.
                    """
                    assert receiver not in [self, empty(address)]
                    self._transfer(msg.sender, receiver, amount)
                    return True
                
                @external
                def transferFrom(sender: address, receiver: address, amount: uint256) -> bool:
                    """
                    @notice Transfer shares from a sender to a receiver.
                    @param sender The address to transfer shares from.
                    @param receiver The address to transfer shares to.
                    @param amount The amount of shares to transfer.
                    @return True if the transfer was successful.
                    """
                    assert receiver not in [self, empty(address)]
                    return self._transfer_from(sender, receiver, amount)
                
                ## ERC20+4626 compatibility
                @external
                def permit(
                    owner: address, 
                    spender: address, 
                    amount: uint256, 
                    deadline: uint256, 
                    v: uint8, 
                    r: bytes32, 
                    s: bytes32
                ) -> bool:
                    """
                    @notice Approve an address to spend the vault's shares.
                    @param owner The address to approve.
                    @param spender The address to approve.
                    @param amount The amount of shares to approve.
                    @param deadline The deadline for the permit.
                    @param v The v component of the signature.
                    @param r The r component of the signature.
                    @param s The s component of the signature.
                    @return True if the approval was successful.
                    """
                    return self._permit(owner, spender, amount, deadline, v, r, s)
                
                @view
                @external
                def balanceOf(addr: address) -> uint256:
                    """
                    @notice Get the balance of a user.
                    @param addr The address to get the balance of.
                    @return The balance of the user.
                    """
                    if(addr == self):
                        # If the address is the vault, account for locked shares.
                        return self.balance_of[addr] - self._unlocked_shares()
                
                    return self.balance_of[addr]
                
                @view
                @external
                def totalSupply() -> uint256:
                    """
                    @notice Get the total supply of shares.
                    @return The total supply of shares.
                    """
                    return self._total_supply()
                
                @view
                @external
                def totalAssets() -> uint256:
                    """
                    @notice Get the total assets held by the vault.
                    @return The total assets held by the vault.
                    """
                    return self._total_assets()
                
                @view
                @external
                def totalIdle() -> uint256:
                    """
                    @notice Get the amount of loose `asset` the vault holds.
                    @return The current total idle.
                    """
                    return self.total_idle
                
                @view
                @external
                def totalDebt() -> uint256:
                    """
                    @notice Get the the total amount of funds invested
                    across all strategies.
                    @return The current total debt.
                    """
                    return self.total_debt
                
                @view
                @external
                def convertToShares(assets: uint256) -> uint256:
                    """
                    @notice Convert an amount of assets to shares.
                    @param assets The amount of assets to convert.
                    @return The amount of shares.
                    """
                    return self._convert_to_shares(assets, Rounding.ROUND_DOWN)
                
                @view
                @external
                def previewDeposit(assets: uint256) -> uint256:
                    """
                    @notice Preview the amount of shares that would be minted for a deposit.
                    @param assets The amount of assets to deposit.
                    @return The amount of shares that would be minted.
                    """
                    return self._convert_to_shares(assets, Rounding.ROUND_DOWN)
                
                @view
                @external
                def previewMint(shares: uint256) -> uint256:
                    """
                    @notice Preview the amount of assets that would be deposited for a mint.
                    @param shares The amount of shares to mint.
                    @return The amount of assets that would be deposited.
                    """
                    return self._convert_to_assets(shares, Rounding.ROUND_UP)
                
                @view
                @external
                def convertToAssets(shares: uint256) -> uint256:
                    """
                    @notice Convert an amount of shares to assets.
                    @param shares The amount of shares to convert.
                    @return The amount of assets.
                    """
                    return self._convert_to_assets(shares, Rounding.ROUND_DOWN)
                
                @view
                @external
                def maxDeposit(receiver: address) -> uint256:
                    """
                    @notice Get the maximum amount of assets that can be deposited.
                    @param receiver The address that will receive the shares.
                    @return The maximum amount of assets that can be deposited.
                    """
                    return self._max_deposit(receiver)
                
                @view
                @external
                def maxMint(receiver: address) -> uint256:
                    """
                    @notice Get the maximum amount of shares that can be minted.
                    @param receiver The address that will receive the shares.
                    @return The maximum amount of shares that can be minted.
                    """
                    max_deposit: uint256 = self._max_deposit(receiver)
                    return self._convert_to_shares(max_deposit, Rounding.ROUND_DOWN)
                
                @view
                @external
                def maxWithdraw(
                    owner: address,
                    max_loss: uint256 = 0,
                    strategies: DynArray[address, MAX_QUEUE] = []
                ) -> uint256:
                    """
                    @notice Get the maximum amount of assets that can be withdrawn.
                    @dev Complies to normal 4626 interface and takes custom params.
                    NOTE: Passing in a incorrectly ordered queue may result in
                     incorrect returns values.
                    @param owner The address that owns the shares.
                    @param max_loss Custom max_loss if any.
                    @param strategies Custom strategies queue if any.
                    @return The maximum amount of assets that can be withdrawn.
                    """
                    return self._max_withdraw(owner, max_loss, strategies)
                
                @view
                @external
                def maxRedeem(
                    owner: address,
                    max_loss: uint256 = MAX_BPS,
                    strategies: DynArray[address, MAX_QUEUE] = []
                ) -> uint256:
                    """
                    @notice Get the maximum amount of shares that can be redeemed.
                    @dev Complies to normal 4626 interface and takes custom params.
                    NOTE: Passing in a incorrectly ordered queue may result in
                     incorrect returns values.
                    @param owner The address that owns the shares.
                    @param max_loss Custom max_loss if any.
                    @param strategies Custom strategies queue if any.
                    @return The maximum amount of shares that can be redeemed.
                    """
                    return min(
                        # Min of the shares equivalent of max_withdraw or the full balance
                        self._convert_to_shares(self._max_withdraw(owner, max_loss, strategies), Rounding.ROUND_DOWN),
                        self.balance_of[owner]
                    )
                
                @view
                @external
                def previewWithdraw(assets: uint256) -> uint256:
                    """
                    @notice Preview the amount of shares that would be redeemed for a withdraw.
                    @param assets The amount of assets to withdraw.
                    @return The amount of shares that would be redeemed.
                    """
                    return self._convert_to_shares(assets, Rounding.ROUND_UP)
                
                @view
                @external
                def previewRedeem(shares: uint256) -> uint256:
                    """
                    @notice Preview the amount of assets that would be withdrawn for a redeem.
                    @param shares The amount of shares to redeem.
                    @return The amount of assets that would be withdrawn.
                    """
                    return self._convert_to_assets(shares, Rounding.ROUND_DOWN)
                
                @view
                @external
                def FACTORY() -> address:
                    """
                    @notice Address of the factory that deployed the vault.
                    @dev Is used to retrieve the protocol fees.
                    @return Address of the vault factory.
                    """
                    return self.factory
                
                @view
                @external
                def apiVersion() -> String[28]:
                    """
                    @notice Get the API version of the vault.
                    @return The API version of the vault.
                    """
                    return API_VERSION
                
                @view
                @external
                def assess_share_of_unrealised_losses(strategy: address, assets_needed: uint256) -> uint256:
                    """
                    @notice Assess the share of unrealised losses that a strategy has.
                    @param strategy The address of the strategy.
                    @param assets_needed The amount of assets needed to be withdrawn.
                    @return The share of unrealised losses that the strategy has.
                    """
                    assert self.strategies[strategy].current_debt >= assets_needed
                
                    return self._assess_share_of_unrealised_losses(strategy, assets_needed)
                
                ## Profit locking getter functions ##
                
                @view
                @external
                def profitMaxUnlockTime() -> uint256:
                    """
                    @notice Gets the current time profits are set to unlock over.
                    @return The current profit max unlock time.
                    """
                    return self.profit_max_unlock_time
                
                @view
                @external
                def fullProfitUnlockDate() -> uint256:
                    """
                    @notice Gets the timestamp at which all profits will be unlocked.
                    @return The full profit unlocking timestamp
                    """
                    return self.full_profit_unlock_date
                
                @view
                @external
                def profitUnlockingRate() -> uint256:
                    """
                    @notice The per second rate at which profits are unlocking.
                    @dev This is denominated in EXTENDED_BPS decimals.
                    @return The current profit unlocking rate.
                    """
                    return self.profit_unlocking_rate
                
                
                @view
                @external
                def lastProfitUpdate() -> uint256:
                    """
                    @notice The timestamp of the last time shares were locked.
                    @return The last profit update.
                    """
                    return self.last_profit_update
                
                # eip-1344
                @view
                @internal
                def domain_separator() -> bytes32:
                    return keccak256(
                        concat(
                            DOMAIN_TYPE_HASH,
                            keccak256(convert("Yearn Vault", Bytes[11])),
                            keccak256(convert(API_VERSION, Bytes[28])),
                            convert(chain.id, bytes32),
                            convert(self, bytes32)
                        )
                    )
                
                @view
                @external
                def DOMAIN_SEPARATOR() -> bytes32:
                    """
                    @notice Get the domain separator.
                    @return The domain separator.
                    """
                    return self.domain_separator()

                File 4 of 7: Yearn V3 Vault
                # @version 0.3.7
                
                """
                @title Yearn V3 Vault
                @license GNU AGPLv3
                @author yearn.finance
                @notice
                    The Yearn VaultV3 is designed as a non-opinionated system to distribute funds of 
                    depositors for a specific `asset` into different opportunities (aka Strategies)
                    and manage accounting in a robust way.
                
                    Depositors receive shares (aka vaults tokens) proportional to their deposit amount. 
                    Vault tokens are yield-bearing and can be redeemed at any time to get back deposit 
                    plus any yield generated.
                
                    Addresses that are given different permissioned roles by the `role_manager` 
                    are then able to allocate funds as they best see fit to different strategies 
                    and adjust the strategies and allocations as needed, as well as reporting realized
                    profits or losses.
                
                    Strategies are any ERC-4626 compliant contracts that use the same underlying `asset` 
                    as the vault. The vault provides no assurances as to the safety of any strategy
                    and it is the responsibility of those that hold the corresponding roles to choose
                    and fund strategies that best fit their desired specifications.
                
                    Those holding vault tokens are able to redeem the tokens for the corresponding
                    amount of underlying asset based on any reported profits or losses since their
                    initial deposit.
                
                    The vault is built to be customized by the management to be able to fit their
                    specific desired needs. Including the customization of strategies, accountants, 
                    ownership etc.
                """
                
                # INTERFACES #
                
                from vyper.interfaces import ERC20
                from vyper.interfaces import ERC20Detailed
                
                interface IStrategy:
                    def asset() -> address: view
                    def balanceOf(owner: address) -> uint256: view
                    def convertToAssets(shares: uint256) -> uint256: view
                    def convertToShares(assets: uint256) -> uint256: view
                    def previewWithdraw(assets: uint256) -> uint256: view
                    def maxDeposit(receiver: address) -> uint256: view
                    def deposit(assets: uint256, receiver: address) -> uint256: nonpayable
                    def maxRedeem(owner: address) -> uint256: view
                    def redeem(shares: uint256, receiver: address, owner: address) -> uint256: nonpayable
                    
                interface IAccountant:
                    def report(strategy: address, gain: uint256, loss: uint256) -> (uint256, uint256): nonpayable
                
                interface IDepositLimitModule:
                    def available_deposit_limit(receiver: address) -> uint256: view
                    
                interface IWithdrawLimitModule:
                    def available_withdraw_limit(owner: address, max_loss: uint256, strategies: DynArray[address, MAX_QUEUE]) -> uint256: view
                
                interface IFactory:
                    def protocol_fee_config() -> (uint16, address): view
                
                # EVENTS #
                # ERC4626 EVENTS
                event Deposit:
                    sender: indexed(address)
                    owner: indexed(address)
                    assets: uint256
                    shares: uint256
                
                event Withdraw:
                    sender: indexed(address)
                    receiver: indexed(address)
                    owner: indexed(address)
                    assets: uint256
                    shares: uint256
                
                # ERC20 EVENTS
                event Transfer:
                    sender: indexed(address)
                    receiver: indexed(address)
                    value: uint256
                
                event Approval:
                    owner: indexed(address)
                    spender: indexed(address)
                    value: uint256
                
                # STRATEGY EVENTS
                event StrategyChanged:
                    strategy: indexed(address)
                    change_type: indexed(StrategyChangeType)
                    
                event StrategyReported:
                    strategy: indexed(address)
                    gain: uint256
                    loss: uint256
                    current_debt: uint256
                    protocol_fees: uint256
                    total_fees: uint256
                    total_refunds: uint256
                
                # DEBT MANAGEMENT EVENTS
                event DebtUpdated:
                    strategy: indexed(address)
                    current_debt: uint256
                    new_debt: uint256
                
                # ROLE UPDATES
                event RoleSet:
                    account: indexed(address)
                    role: indexed(Roles)
                
                # STORAGE MANAGEMENT EVENTS
                event UpdateRoleManager:
                    role_manager: indexed(address)
                
                event UpdateAccountant:
                    accountant: indexed(address)
                
                event UpdateDepositLimitModule:
                    deposit_limit_module: indexed(address)
                
                event UpdateWithdrawLimitModule:
                    withdraw_limit_module: indexed(address)
                
                event UpdateDefaultQueue:
                    new_default_queue: DynArray[address, MAX_QUEUE]
                
                event UpdateUseDefaultQueue:
                    use_default_queue: bool
                
                event UpdatedMaxDebtForStrategy:
                    sender: indexed(address)
                    strategy: indexed(address)
                    new_debt: uint256
                
                event UpdateDepositLimit:
                    deposit_limit: uint256
                
                event UpdateMinimumTotalIdle:
                    minimum_total_idle: uint256
                
                event UpdateProfitMaxUnlockTime:
                    profit_max_unlock_time: uint256
                
                event DebtPurchased:
                    strategy: indexed(address)
                    amount: uint256
                
                event Shutdown:
                    pass
                
                # STRUCTS #
                struct StrategyParams:
                    # Timestamp when the strategy was added.
                    activation: uint256 
                    # Timestamp of the strategies last report.
                    last_report: uint256
                    # The current assets the strategy holds.
                    current_debt: uint256
                    # The max assets the strategy can hold. 
                    max_debt: uint256
                
                # CONSTANTS #
                # The max length the withdrawal queue can be.
                MAX_QUEUE: constant(uint256) = 10
                # 100% in Basis Points.
                MAX_BPS: constant(uint256) = 10_000
                # Extended for profit locking calculations.
                MAX_BPS_EXTENDED: constant(uint256) = 1_000_000_000_000
                # The version of this vault.
                API_VERSION: constant(String[28]) = "3.0.2"
                
                # ENUMS #
                # Each permissioned function has its own Role.
                # Roles can be combined in any combination or all kept separate.
                # Follows python Enum patterns so the first Enum == 1 and doubles each time.
                enum Roles:
                    ADD_STRATEGY_MANAGER # Can add strategies to the vault.
                    REVOKE_STRATEGY_MANAGER # Can remove strategies from the vault.
                    FORCE_REVOKE_MANAGER # Can force remove a strategy causing a loss.
                    ACCOUNTANT_MANAGER # Can set the accountant that assess fees.
                    QUEUE_MANAGER # Can set the default withdrawal queue.
                    REPORTING_MANAGER # Calls report for strategies.
                    DEBT_MANAGER # Adds and removes debt from strategies.
                    MAX_DEBT_MANAGER # Can set the max debt for a strategy.
                    DEPOSIT_LIMIT_MANAGER # Sets deposit limit and module for the vault.
                    WITHDRAW_LIMIT_MANAGER # Sets the withdraw limit module.
                    MINIMUM_IDLE_MANAGER # Sets the minimum total idle the vault should keep.
                    PROFIT_UNLOCK_MANAGER # Sets the profit_max_unlock_time.
                    DEBT_PURCHASER # Can purchase bad debt from the vault.
                    EMERGENCY_MANAGER # Can shutdown vault in an emergency.
                
                enum StrategyChangeType:
                    ADDED
                    REVOKED
                
                enum Rounding:
                    ROUND_DOWN
                    ROUND_UP
                
                # STORAGE #
                # Underlying token used by the vault.
                asset: public(address)
                # Based off the `asset` decimals.
                decimals: public(uint8)
                # Deployer contract used to retrieve the protocol fee config.
                factory: address
                
                # HashMap that records all the strategies that are allowed to receive assets from the vault.
                strategies: public(HashMap[address, StrategyParams])
                # The current default withdrawal queue.
                default_queue: public(DynArray[address, MAX_QUEUE])
                # Should the vault use the default_queue regardless whats passed in.
                use_default_queue: public(bool)
                
                ### ACCOUNTING ###
                # ERC20 - amount of shares per account
                balance_of: HashMap[address, uint256]
                # ERC20 - owner -> (spender -> amount)
                allowance: public(HashMap[address, HashMap[address, uint256]])
                # Total amount of shares that are currently minted including those locked.
                total_supply: uint256
                # Total amount of assets that has been deposited in strategies.
                total_debt: uint256
                # Current assets held in the vault contract. Replacing balanceOf(this) to avoid price_per_share manipulation.
                total_idle: uint256
                # Minimum amount of assets that should be kept in the vault contract to allow for fast, cheap redeems.
                minimum_total_idle: public(uint256)
                # Maximum amount of tokens that the vault can accept. If totalAssets > deposit_limit, deposits will revert.
                deposit_limit: public(uint256)
                
                ### PERIPHERY ###
                # Contract that charges fees and can give refunds.
                accountant: public(address)
                # Contract to control the deposit limit.
                deposit_limit_module: public(address)
                # Contract to control the withdraw limit.
                withdraw_limit_module: public(address)
                
                ### ROLES ###
                # HashMap mapping addresses to their roles
                roles: public(HashMap[address, Roles])
                # Address that can add and remove roles to addresses.
                role_manager: public(address)
                # Temporary variable to store the address of the next role_manager until the role is accepted.
                future_role_manager: public(address)
                
                # ERC20 - name of the vaults token.
                name: public(String[64])
                # ERC20 - symbol of the vaults token.
                symbol: public(String[32])
                
                # State of the vault - if set to true, only withdrawals will be available. It can't be reverted.
                shutdown: bool
                # The amount of time profits will unlock over.
                profit_max_unlock_time: uint256
                # The timestamp of when the current unlocking period ends.
                full_profit_unlock_date: uint256
                # The per second rate at which profit will unlock.
                profit_unlocking_rate: uint256
                # Last timestamp of the most recent profitable report.
                last_profit_update: uint256
                
                # `nonces` track `permit` approvals with signature.
                nonces: public(HashMap[address, uint256])
                DOMAIN_TYPE_HASH: constant(bytes32) = keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')
                PERMIT_TYPE_HASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
                
                # Constructor
                @external
                def __init__():
                    # Set `asset` so it cannot be re-initialized.
                    self.asset = self
                    
                @external
                def initialize(
                    asset: address, 
                    name: String[64], 
                    symbol: String[32], 
                    role_manager: address, 
                    profit_max_unlock_time: uint256
                ):
                    """
                    @notice
                        Initialize a new vault. Sets the asset, name, symbol, and role manager.
                    @param asset
                        The address of the asset that the vault will accept.
                    @param name
                        The name of the vault token.
                    @param symbol
                        The symbol of the vault token.
                    @param role_manager 
                        The address that can add and remove roles to addresses
                    @param profit_max_unlock_time
                        The amount of time that the profit will be locked for
                    """
                    assert self.asset == empty(address), "initialized"
                    assert asset != empty(address), "ZERO ADDRESS"
                    assert role_manager != empty(address), "ZERO ADDRESS"
                
                    self.asset = asset
                    # Get the decimals for the vault to use.
                    self.decimals = ERC20Detailed(asset).decimals()
                    
                    # Set the factory as the deployer address.
                    self.factory = msg.sender
                
                    # Must be less than one year for report cycles
                    assert profit_max_unlock_time <= 31_556_952 # dev: profit unlock time too long
                    self.profit_max_unlock_time = profit_max_unlock_time
                
                    self.name = name
                    self.symbol = symbol
                    self.role_manager = role_manager
                
                ## SHARE MANAGEMENT ##
                ## ERC20 ##
                @internal
                def _spend_allowance(owner: address, spender: address, amount: uint256):
                    # Unlimited approval does nothing (saves an SSTORE)
                    current_allowance: uint256 = self.allowance[owner][spender]
                    if (current_allowance < max_value(uint256)):
                        assert current_allowance >= amount, "insufficient allowance"
                        self._approve(owner, spender, unsafe_sub(current_allowance, amount))
                
                @internal
                def _transfer(sender: address, receiver: address, amount: uint256):
                    sender_balance: uint256 = self.balance_of[sender]
                    assert sender_balance >= amount, "insufficient funds"
                    self.balance_of[sender] = unsafe_sub(sender_balance, amount)
                    self.balance_of[receiver] = unsafe_add(self.balance_of[receiver], amount)
                    log Transfer(sender, receiver, amount)
                
                @internal
                def _transfer_from(sender: address, receiver: address, amount: uint256) -> bool:
                    self._spend_allowance(sender, msg.sender, amount)
                    self._transfer(sender, receiver, amount)
                    return True
                
                @internal
                def _approve(owner: address, spender: address, amount: uint256) -> bool:
                    self.allowance[owner][spender] = amount
                    log Approval(owner, spender, amount)
                    return True
                
                @internal
                def _permit(
                    owner: address, 
                    spender: address, 
                    amount: uint256, 
                    deadline: uint256, 
                    v: uint8, 
                    r: bytes32, 
                    s: bytes32
                ) -> bool:
                    assert owner != empty(address), "invalid owner"
                    assert deadline >= block.timestamp, "permit expired"
                    nonce: uint256 = self.nonces[owner]
                    digest: bytes32 = keccak256(
                        concat(
                            b'\x19\x01',
                            self.domain_separator(),
                            keccak256(
                                concat(
                                    PERMIT_TYPE_HASH,
                                    convert(owner, bytes32),
                                    convert(spender, bytes32),
                                    convert(amount, bytes32),
                                    convert(nonce, bytes32),
                                    convert(deadline, bytes32),
                                )
                            )
                        )
                    )
                    assert ecrecover(
                        digest, v, r, s
                    ) == owner, "invalid signature"
                
                    self.allowance[owner][spender] = amount
                    self.nonces[owner] = nonce + 1
                    log Approval(owner, spender, amount)
                    return True
                
                @internal
                def _burn_shares(shares: uint256, owner: address):
                    self.balance_of[owner] -= shares
                    self.total_supply = unsafe_sub(self.total_supply, shares)
                    log Transfer(owner, empty(address), shares)
                
                @view
                @internal
                def _unlocked_shares() -> uint256:
                    """
                    Returns the amount of shares that have been unlocked.
                    To avoid sudden price_per_share spikes, profits can be processed 
                    through an unlocking period. The mechanism involves shares to be 
                    minted to the vault which are unlocked gradually over time. Shares 
                    that have been locked are gradually unlocked over profit_max_unlock_time.
                    """
                    _full_profit_unlock_date: uint256 = self.full_profit_unlock_date
                    unlocked_shares: uint256 = 0
                    if _full_profit_unlock_date > block.timestamp:
                        # If we have not fully unlocked, we need to calculate how much has been.
                        unlocked_shares = self.profit_unlocking_rate * (block.timestamp - self.last_profit_update) / MAX_BPS_EXTENDED
                
                    elif _full_profit_unlock_date != 0:
                        # All shares have been unlocked
                        unlocked_shares = self.balance_of[self]
                
                    return unlocked_shares
                
                
                @view
                @internal
                def _total_supply() -> uint256:
                    # Need to account for the shares issued to the vault that have unlocked.
                    return self.total_supply - self._unlocked_shares()
                
                @view
                @internal
                def _total_assets() -> uint256:
                    """
                    Total amount of assets that are in the vault and in the strategies. 
                    """
                    return self.total_idle + self.total_debt
                
                @view
                @internal
                def _convert_to_assets(shares: uint256, rounding: Rounding) -> uint256:
                    """ 
                    assets = shares * (total_assets / total_supply) --- (== price_per_share * shares)
                    """
                    if shares == max_value(uint256) or shares == 0:
                        return shares
                
                    total_supply: uint256 = self._total_supply()
                    # if total_supply is 0, price_per_share is 1
                    if total_supply == 0: 
                        return shares
                
                    numerator: uint256 = shares * self._total_assets()
                    amount: uint256 = numerator / total_supply
                    if rounding == Rounding.ROUND_UP and numerator % total_supply != 0:
                        amount += 1
                
                    return amount
                
                @view
                @internal
                def _convert_to_shares(assets: uint256, rounding: Rounding) -> uint256:
                    """
                    shares = amount * (total_supply / total_assets) --- (== amount / price_per_share)
                    """
                    if assets == max_value(uint256) or assets == 0:
                        return assets
                
                    total_supply: uint256 = self._total_supply()
                    total_assets: uint256 = self._total_assets()
                
                    if total_assets == 0:
                        # if total_assets and total_supply is 0, price_per_share is 1
                        if total_supply == 0:
                            return assets
                        else:
                            # Else if total_supply > 0 price_per_share is 0
                            return 0
                
                    numerator: uint256 = assets * total_supply
                    shares: uint256 = numerator / total_assets
                    if rounding == Rounding.ROUND_UP and numerator % total_assets != 0:
                        shares += 1
                
                    return shares
                
                @internal
                def _erc20_safe_approve(token: address, spender: address, amount: uint256):
                    # Used only to approve tokens that are not the type managed by this Vault.
                    # Used to handle non-compliant tokens like USDT
                    assert ERC20(token).approve(spender, amount, default_return_value=True), "approval failed"
                
                @internal
                def _erc20_safe_transfer_from(token: address, sender: address, receiver: address, amount: uint256):
                    # Used only to transfer tokens that are not the type managed by this Vault.
                    # Used to handle non-compliant tokens like USDT
                    assert ERC20(token).transferFrom(sender, receiver, amount, default_return_value=True), "transfer failed"
                
                @internal
                def _erc20_safe_transfer(token: address, receiver: address, amount: uint256):
                    # Used only to send tokens that are not the type managed by this Vault.
                    # Used to handle non-compliant tokens like USDT
                    assert ERC20(token).transfer(receiver, amount, default_return_value=True), "transfer failed"
                
                @internal
                def _issue_shares(shares: uint256, recipient: address):
                    self.balance_of[recipient] = unsafe_add(self.balance_of[recipient], shares)
                    self.total_supply += shares
                
                    log Transfer(empty(address), recipient, shares)
                
                @internal
                def _issue_shares_for_amount(amount: uint256, recipient: address) -> uint256:
                    """
                    Issues shares that are worth 'amount' in the underlying token (asset).
                    WARNING: this takes into account that any new assets have been summed 
                    to total_assets (otherwise pps will go down).
                    """
                    total_supply: uint256 = self._total_supply()
                    total_assets: uint256 = self._total_assets()
                    new_shares: uint256 = 0
                    
                    # If no supply PPS = 1.
                    if total_supply == 0:
                        new_shares = amount
                    elif total_assets > amount:
                        new_shares = amount * total_supply / (total_assets - amount)
                
                    # We don't make the function revert
                    if new_shares == 0:
                       return 0
                
                    self._issue_shares(new_shares, recipient)
                
                    return new_shares
                
                ## ERC4626 ##
                @view
                @internal
                def _max_deposit(receiver: address) -> uint256: 
                    if receiver in [empty(address), self]:
                        return 0
                
                    # If there is a deposit limit module set use that.
                    deposit_limit_module: address = self.deposit_limit_module
                    if deposit_limit_module != empty(address):
                        return IDepositLimitModule(deposit_limit_module).available_deposit_limit(receiver)
                    
                    # Else use the standard flow.
                    _deposit_limit: uint256 = self.deposit_limit
                    if (_deposit_limit == max_value(uint256)):
                        return _deposit_limit
                
                    _total_assets: uint256 = self._total_assets()
                    if (_total_assets >= _deposit_limit):
                        return 0
                
                    return unsafe_sub(_deposit_limit, _total_assets)
                
                @view
                @internal
                def _max_withdraw(
                    owner: address,
                    max_loss: uint256,
                    strategies: DynArray[address, MAX_QUEUE]
                ) -> uint256:
                    """
                    @dev Returns the max amount of `asset` an `owner` can withdraw.
                
                    This will do a full simulation of the withdraw in order to determine
                    how much is currently liquid and if the `max_loss` would allow for the 
                    tx to not revert.
                
                    This will track any expected loss to check if the tx will revert, but
                    not account for it in the amount returned since it is unrealised and 
                    therefore will not be accounted for in the conversion rates.
                
                    i.e. If we have 100 debt and 10 of unrealised loss, the max we can get
                    out is 90, but a user of the vault will need to call withdraw with 100
                    in order to get the full 90 out.
                    """
                
                    # Get the max amount for the owner if fully liquid.
                    max_assets: uint256 = self._convert_to_assets(self.balance_of[owner], Rounding.ROUND_DOWN)
                
                    # If there is a withdraw limit module use that.
                    withdraw_limit_module: address = self.withdraw_limit_module
                    if withdraw_limit_module != empty(address):
                        return min(
                            # Use the min between the returned value and the max.
                            # Means the limit module doesn't need to account for balances or conversions.
                            IWithdrawLimitModule(withdraw_limit_module).available_withdraw_limit(owner, max_loss, strategies),
                            max_assets
                        )
                    
                    # See if we have enough idle to service the withdraw.
                    current_idle: uint256 = self.total_idle
                    if max_assets > current_idle:
                        # Track how much we can pull.
                        have: uint256 = current_idle
                        loss: uint256 = 0
                
                        # Cache the default queue.
                        _strategies: DynArray[address, MAX_QUEUE] = self.default_queue
                
                        # If a custom queue was passed, and we don't force the default queue.
                        if len(strategies) != 0 and not self.use_default_queue:
                            # Use the custom queue.
                            _strategies = strategies
                
                        for strategy in _strategies:
                            # Can't use an invalid strategy.
                            assert self.strategies[strategy].activation != 0, "inactive strategy"
                
                            # Get the maximum amount the vault would withdraw from the strategy.
                            to_withdraw: uint256 = min(
                                # What we still need for the full withdraw.
                                max_assets - have, 
                                # The current debt the strategy has.
                                self.strategies[strategy].current_debt
                            )
                
                            # Get any unrealised loss for the strategy.
                            unrealised_loss: uint256 = self._assess_share_of_unrealised_losses(strategy, to_withdraw)
                
                            # See if any limit is enforced by the strategy.
                            strategy_limit: uint256 = IStrategy(strategy).convertToAssets(
                                IStrategy(strategy).maxRedeem(self)
                            )
                
                            # Adjust accordingly if there is a max withdraw limit.
                            realizable_withdraw: uint256 = to_withdraw - unrealised_loss
                            if strategy_limit < realizable_withdraw:
                                if unrealised_loss != 0:
                                    # lower unrealised loss proportional to the limit.
                                    unrealised_loss = unrealised_loss * strategy_limit / realizable_withdraw
                
                                # Still count the unrealised loss as withdrawable.
                                to_withdraw = strategy_limit + unrealised_loss
                                
                            # If 0 move on to the next strategy.
                            if to_withdraw == 0:
                                continue
                
                            # If there would be a loss with a non-maximum `max_loss` value.
                            if unrealised_loss > 0 and max_loss < MAX_BPS:
                                # Check if the loss is greater than the allowed range.
                                if loss + unrealised_loss > (have + to_withdraw) * max_loss / MAX_BPS:
                                    # If so use the amounts up till now.
                                    break
                
                            # Add to what we can pull.
                            have += to_withdraw
                
                            # If we have all we need break.
                            if have >= max_assets:
                                break
                
                            # Add any unrealised loss to the total
                            loss += unrealised_loss
                
                        # Update the max after going through the queue.
                        # In case we broke early or exhausted the queue.
                        max_assets = have
                
                    return max_assets
                
                @internal
                def _deposit(sender: address, recipient: address, assets: uint256) -> uint256:
                    """
                    Used for `deposit` calls to transfer the amount of `asset` to the vault, 
                    issue the corresponding shares to the `recipient` and update all needed 
                    vault accounting.
                    """
                    assert self.shutdown == False # dev: shutdown
                    assert assets <= self._max_deposit(recipient), "exceed deposit limit"
                 
                    # Transfer the tokens to the vault first.
                    self._erc20_safe_transfer_from(self.asset, msg.sender, self, assets)
                    # Record the change in total assets.
                    self.total_idle += assets
                    
                    # Issue the corresponding shares for assets.
                    shares: uint256 = self._issue_shares_for_amount(assets, recipient)
                
                    assert shares > 0, "cannot mint zero"
                
                    log Deposit(sender, recipient, assets, shares)
                    return shares
                
                @internal
                def _mint(sender: address, recipient: address, shares: uint256) -> uint256:
                    """
                    Used for `mint` calls to issue the corresponding shares to the `recipient`,
                    transfer the amount of `asset` to the vault, and update all needed vault 
                    accounting.
                    """
                    assert self.shutdown == False # dev: shutdown
                    # Get corresponding amount of assets.
                    assets: uint256 = self._convert_to_assets(shares, Rounding.ROUND_UP)
                
                    assert assets > 0, "cannot deposit zero"
                    assert assets <= self._max_deposit(recipient), "exceed deposit limit"
                
                    # Transfer the tokens to the vault first.
                    self._erc20_safe_transfer_from(self.asset, msg.sender, self, assets)
                    # Record the change in total assets.
                    self.total_idle += assets
                    
                    # Issue the corresponding shares for assets.
                    self._issue_shares(shares, recipient)
                
                    log Deposit(sender, recipient, assets, shares)
                    return assets
                
                @view
                @internal
                def _assess_share_of_unrealised_losses(strategy: address, assets_needed: uint256) -> uint256:
                    """
                    Returns the share of losses that a user would take if withdrawing from this strategy
                    This accounts for losses that have been realized at the strategy level but not yet
                    realized at the vault level.
                
                    e.g. if the strategy has unrealised losses for 10% of its current debt and the user 
                    wants to withdraw 1_000 tokens, the losses that they will take is 100 token
                    """
                    # Minimum of how much debt the debt should be worth.
                    strategy_current_debt: uint256 = self.strategies[strategy].current_debt
                    # The actual amount that the debt is currently worth.
                    vault_shares: uint256 = IStrategy(strategy).balanceOf(self)
                    strategy_assets: uint256 = IStrategy(strategy).convertToAssets(vault_shares)
                    
                    # If no losses, return 0
                    if strategy_assets >= strategy_current_debt or strategy_current_debt == 0:
                        return 0
                
                    # Users will withdraw assets_needed divided by loss ratio (strategy_assets / strategy_current_debt - 1).
                    # NOTE: If there are unrealised losses, the user will take his share.
                    numerator: uint256 = assets_needed * strategy_assets
                    users_share_of_loss: uint256 = assets_needed - numerator / strategy_current_debt
                    # Always round up.
                    if numerator % strategy_current_debt != 0:
                        users_share_of_loss += 1
                
                    return users_share_of_loss
                
                @internal
                def _withdraw_from_strategy(strategy: address, assets_to_withdraw: uint256):
                    """
                    This takes the amount denominated in asset and performs a {redeem}
                    with the corresponding amount of shares.
                
                    We use {redeem} to natively take on losses without additional non-4626 standard parameters.
                    """
                    # Need to get shares since we use redeem to be able to take on losses.
                    shares_to_redeem: uint256 = min(
                        # Use previewWithdraw since it should round up.
                        IStrategy(strategy).previewWithdraw(assets_to_withdraw), 
                        # And check against our actual balance.
                        IStrategy(strategy).balanceOf(self)
                    )
                    # Redeem the shares.
                    IStrategy(strategy).redeem(shares_to_redeem, self, self)
                
                @internal
                def _redeem(
                    sender: address, 
                    receiver: address, 
                    owner: address,
                    assets: uint256,
                    shares: uint256, 
                    max_loss: uint256,
                    strategies: DynArray[address, MAX_QUEUE]
                ) -> uint256:
                    """
                    This will attempt to free up the full amount of assets equivalent to
                    `shares` and transfer them to the `receiver`. If the vault does
                    not have enough idle funds it will go through any strategies provided by
                    either the withdrawer or the default_queue to free up enough funds to 
                    service the request.
                
                    The vault will attempt to account for any unrealized losses taken on from
                    strategies since their respective last reports.
                
                    Any losses realized during the withdraw from a strategy will be passed on
                    to the user that is redeeming their vault shares unless it exceeds the given
                    `max_loss`.
                    """
                    assert receiver != empty(address), "ZERO ADDRESS"
                    assert shares > 0, "no shares to redeem"
                    assert assets > 0, "no assets to withdraw"
                    assert max_loss <= MAX_BPS, "max loss"
                    
                    # If there is a withdraw limit module, check the max.
                    withdraw_limit_module: address = self.withdraw_limit_module
                    if withdraw_limit_module != empty(address):
                        assert assets <= IWithdrawLimitModule(withdraw_limit_module).available_withdraw_limit(owner, max_loss, strategies), "exceed withdraw limit"
                
                    assert self.balance_of[owner] >= shares, "insufficient shares to redeem"
                    
                    if sender != owner:
                        self._spend_allowance(owner, sender, shares)
                
                    # The amount of the underlying token to withdraw.
                    requested_assets: uint256 = assets
                
                    # load to memory to save gas
                    current_total_idle: uint256 = self.total_idle
                    _asset: address = self.asset
                
                    # If there are not enough assets in the Vault contract, we try to free
                    # funds from strategies.
                    if requested_assets > current_total_idle:
                
                        # Cache the default queue.
                        _strategies: DynArray[address, MAX_QUEUE] = self.default_queue
                
                        # If a custom queue was passed, and we don't force the default queue.
                        if len(strategies) != 0 and not self.use_default_queue:
                            # Use the custom queue.
                            _strategies = strategies
                
                        # load to memory to save gas
                        current_total_debt: uint256 = self.total_debt
                
                        # Withdraw from strategies only what idle doesn't cover.
                        # `assets_needed` is the total amount we need to fill the request.
                        assets_needed: uint256 = unsafe_sub(requested_assets, current_total_idle)
                        # `assets_to_withdraw` is the amount to request from the current strategy.
                        assets_to_withdraw: uint256 = 0
                
                        # To compare against real withdrawals from strategies
                        previous_balance: uint256 = ERC20(_asset).balanceOf(self)
                
                        for strategy in _strategies:
                            # Make sure we have a valid strategy.
                            assert self.strategies[strategy].activation != 0, "inactive strategy"
                
                            # How much should the strategy have.
                            current_debt: uint256 = self.strategies[strategy].current_debt
                
                            # What is the max amount to withdraw from this strategy.
                            assets_to_withdraw = min(assets_needed, current_debt)
                
                            # Cache max_withdraw now for use if unrealized loss > 0
                            # Use maxRedeem and convert it since we use redeem.
                            max_withdraw: uint256 = IStrategy(strategy).convertToAssets(
                                IStrategy(strategy).maxRedeem(self)
                            )
                
                            # CHECK FOR UNREALISED LOSSES
                            # If unrealised losses > 0, then the user will take the proportional share 
                            # and realize it (required to avoid users withdrawing from lossy strategies).
                            # NOTE: strategies need to manage the fact that realising part of the loss can 
                            # mean the realisation of 100% of the loss!! (i.e. if for withdrawing 10% of the
                            # strategy it needs to unwind the whole position, generated losses might be bigger)
                            unrealised_losses_share: uint256 = self._assess_share_of_unrealised_losses(strategy, assets_to_withdraw)
                            if unrealised_losses_share > 0:
                                # If max withdraw is limiting the amount to pull, we need to adjust the portion of 
                                # the unrealized loss the user should take.
                                if max_withdraw < assets_to_withdraw - unrealised_losses_share:
                                    # How much would we want to withdraw
                                    wanted: uint256 = assets_to_withdraw - unrealised_losses_share
                                    # Get the proportion of unrealised comparing what we want vs. what we can get
                                    unrealised_losses_share = unrealised_losses_share * max_withdraw / wanted
                                    # Adjust assets_to_withdraw so all future calculations work correctly
                                    assets_to_withdraw = max_withdraw + unrealised_losses_share
                                
                                # User now "needs" less assets to be unlocked (as he took some as losses)
                                assets_to_withdraw -= unrealised_losses_share
                                requested_assets -= unrealised_losses_share
                                # NOTE: done here instead of waiting for regular update of these values 
                                # because it's a rare case (so we can save minor amounts of gas)
                                assets_needed -= unrealised_losses_share
                                current_total_debt -= unrealised_losses_share
                
                                # If max withdraw is 0 and unrealised loss is still > 0 then the strategy likely
                                # realized a 100% loss and we will need to realize that loss before moving on.
                                if max_withdraw == 0 and unrealised_losses_share > 0:
                                    # Adjust the strategy debt accordingly.
                                    new_debt: uint256 = current_debt - unrealised_losses_share
                        
                                    # Update strategies storage
                                    self.strategies[strategy].current_debt = new_debt
                                    # Log the debt update
                                    log DebtUpdated(strategy, current_debt, new_debt)
                
                            # Adjust based on the max withdraw of the strategy.
                            assets_to_withdraw = min(assets_to_withdraw, max_withdraw)
                
                            # Can't withdraw 0.
                            if assets_to_withdraw == 0:
                                continue
                            
                            # WITHDRAW FROM STRATEGY
                            self._withdraw_from_strategy(strategy, assets_to_withdraw)
                            post_balance: uint256 = ERC20(_asset).balanceOf(self)
                            
                            # Always check against the real amounts.
                            withdrawn: uint256 = post_balance - previous_balance
                            loss: uint256 = 0
                            # Check if we redeemed too much.
                            if withdrawn > assets_to_withdraw:
                                # Make sure we don't underflow in debt updates.
                                if withdrawn > current_debt:
                                    # Can't withdraw more than our debt.
                                    assets_to_withdraw = current_debt
                                else:
                                    # Add the extra to how much we withdrew.
                                    assets_to_withdraw += (unsafe_sub(withdrawn, assets_to_withdraw))
                
                            # If we have not received what we expected, we consider the difference a loss.
                            elif withdrawn < assets_to_withdraw:
                                loss = unsafe_sub(assets_to_withdraw, withdrawn)
                
                            # NOTE: strategy's debt decreases by the full amount but the total idle increases 
                            # by the actual amount only (as the difference is considered lost).
                            current_total_idle += (assets_to_withdraw - loss)
                            requested_assets -= loss
                            current_total_debt -= assets_to_withdraw
                
                            # Vault will reduce debt because the unrealised loss has been taken by user
                            new_debt: uint256 = current_debt - (assets_to_withdraw + unrealised_losses_share)
                        
                            # Update strategies storage
                            self.strategies[strategy].current_debt = new_debt
                            # Log the debt update
                            log DebtUpdated(strategy, current_debt, new_debt)
                
                            # Break if we have enough total idle to serve initial request.
                            if requested_assets <= current_total_idle:
                                break
                
                            # We update the previous_balance variable here to save gas in next iteration.
                            previous_balance = post_balance
                
                            # Reduce what we still need. Safe to use assets_to_withdraw 
                            # here since it has been checked against requested_assets
                            assets_needed -= assets_to_withdraw
                
                        # If we exhaust the queue and still have insufficient total idle, revert.
                        assert current_total_idle >= requested_assets, "insufficient assets in vault"
                        # Commit memory to storage.
                        self.total_debt = current_total_debt
                
                    # Check if there is a loss and a non-default value was set.
                    if assets > requested_assets and max_loss < MAX_BPS:
                        # Assure the loss is within the allowed range.
                        assert assets - requested_assets <= assets * max_loss / MAX_BPS, "too much loss"
                
                    # First burn the corresponding shares from the redeemer.
                    self._burn_shares(shares, owner)
                    # Commit memory to storage.
                    self.total_idle = current_total_idle - requested_assets
                    # Transfer the requested amount to the receiver.
                    self._erc20_safe_transfer(_asset, receiver, requested_assets)
                
                    log Withdraw(sender, receiver, owner, requested_assets, shares)
                    return requested_assets
                
                ## STRATEGY MANAGEMENT ##
                @internal
                def _add_strategy(new_strategy: address, add_to_queue: bool):
                    assert new_strategy not in [self, empty(address)], "strategy cannot be zero address"
                    assert IStrategy(new_strategy).asset() == self.asset, "invalid asset"
                    assert self.strategies[new_strategy].activation == 0, "strategy already active"
                
                    # Add the new strategy to the mapping.
                    self.strategies[new_strategy] = StrategyParams({
                        activation: block.timestamp,
                        last_report: block.timestamp,
                        current_debt: 0,
                        max_debt: 0
                    })
                
                    # If we are adding to the queue and the default queue has space, add the strategy.
                    if add_to_queue and len(self.default_queue) < MAX_QUEUE:
                        self.default_queue.append(new_strategy)        
                        
                    log StrategyChanged(new_strategy, StrategyChangeType.ADDED)
                
                @internal
                def _revoke_strategy(strategy: address, force: bool=False):
                    assert self.strategies[strategy].activation != 0, "strategy not active"
                
                    # If force revoking a strategy, it will cause a loss.
                    loss: uint256 = 0
                    
                    if self.strategies[strategy].current_debt != 0:
                        assert force, "strategy has debt"
                        # Vault realizes the full loss of outstanding debt.
                        loss = self.strategies[strategy].current_debt
                        # Adjust total vault debt.
                        self.total_debt -= loss
                
                        log StrategyReported(strategy, 0, loss, 0, 0, 0, 0)
                
                    # Set strategy params all back to 0 (WARNING: it can be re-added).
                    self.strategies[strategy] = StrategyParams({
                      activation: 0,
                      last_report: 0,
                      current_debt: 0,
                      max_debt: 0
                    })
                
                    # Remove strategy if it is in the default queue.
                    new_queue: DynArray[address, MAX_QUEUE] = []
                    for _strategy in self.default_queue:
                        # Add all strategies to the new queue besides the one revoked.
                        if _strategy != strategy:
                            new_queue.append(_strategy)
                        
                    # Set the default queue to our updated queue.
                    self.default_queue = new_queue
                
                    log StrategyChanged(strategy, StrategyChangeType.REVOKED)
                
                # DEBT MANAGEMENT #
                @internal
                def _update_debt(strategy: address, target_debt: uint256, max_loss: uint256) -> uint256:
                    """
                    The vault will re-balance the debt vs target debt. Target debt must be
                    smaller or equal to strategy's max_debt. This function will compare the 
                    current debt with the target debt and will take funds or deposit new 
                    funds to the strategy. 
                
                    The strategy can require a maximum amount of funds that it wants to receive
                    to invest. The strategy can also reject freeing funds if they are locked.
                    """
                    # How much we want the strategy to have.
                    new_debt: uint256 = target_debt
                    # How much the strategy currently has.
                    current_debt: uint256 = self.strategies[strategy].current_debt
                
                    # If the vault is shutdown we can only pull funds.
                    if self.shutdown:
                        new_debt = 0
                
                    assert new_debt != current_debt, "new debt equals current debt"
                
                    if current_debt > new_debt:
                        # Reduce debt.
                        assets_to_withdraw: uint256 = unsafe_sub(current_debt, new_debt)
                
                        # Ensure we always have minimum_total_idle when updating debt.
                        minimum_total_idle: uint256 = self.minimum_total_idle
                        total_idle: uint256 = self.total_idle
                        
                        # Respect minimum total idle in vault
                        if total_idle + assets_to_withdraw < minimum_total_idle:
                            assets_to_withdraw = unsafe_sub(minimum_total_idle, total_idle)
                            # Cant withdraw more than the strategy has.
                            if assets_to_withdraw > current_debt:
                                assets_to_withdraw = current_debt
                
                        # Check how much we are able to withdraw.
                        # Use maxRedeem and convert since we use redeem.
                        withdrawable: uint256 = IStrategy(strategy).convertToAssets(
                            IStrategy(strategy).maxRedeem(self)
                        )
                        assert withdrawable != 0, "nothing to withdraw"
                
                        # If insufficient withdrawable, withdraw what we can.
                        if withdrawable < assets_to_withdraw:
                            assets_to_withdraw = withdrawable
                
                        # If there are unrealised losses we don't let the vault reduce its debt until there is a new report
                        unrealised_losses_share: uint256 = self._assess_share_of_unrealised_losses(strategy, assets_to_withdraw)
                        assert unrealised_losses_share == 0, "strategy has unrealised losses"
                        
                        # Cache for repeated use.
                        _asset: address = self.asset
                
                        # Always check the actual amount withdrawn.
                        pre_balance: uint256 = ERC20(_asset).balanceOf(self)
                        self._withdraw_from_strategy(strategy, assets_to_withdraw)
                        post_balance: uint256 = ERC20(_asset).balanceOf(self)
                        
                        # making sure we are changing idle according to the real result no matter what. 
                        # We pull funds with {redeem} so there can be losses or rounding differences.
                        withdrawn: uint256 = min(post_balance - pre_balance, current_debt)
                
                        # If we didn't get the amount we asked for and there is a max loss.
                        if withdrawn < assets_to_withdraw and max_loss < MAX_BPS:
                            # Make sure the loss is within the allowed range.
                            assert assets_to_withdraw - withdrawn <= assets_to_withdraw * max_loss / MAX_BPS, "too much loss"
                
                        # If we got too much make sure not to increase PPS.
                        elif withdrawn > assets_to_withdraw:
                            assets_to_withdraw = withdrawn
                
                        # Update storage.
                        self.total_idle += withdrawn # actual amount we got.
                        # Amount we tried to withdraw in case of losses
                        self.total_debt -= assets_to_withdraw 
                
                        new_debt = current_debt - assets_to_withdraw
                    else: 
                        # We are increasing the strategies debt
                
                        # Revert if target_debt cannot be achieved due to configured max_debt for given strategy
                        assert new_debt <= self.strategies[strategy].max_debt, "target debt higher than max debt"
                
                        # Vault is increasing debt with the strategy by sending more funds.
                        max_deposit: uint256 = IStrategy(strategy).maxDeposit(self)
                        assert max_deposit != 0, "nothing to deposit"
                
                        # Deposit the difference between desired and current.
                        assets_to_deposit: uint256 = new_debt - current_debt
                        if assets_to_deposit > max_deposit:
                            # Deposit as much as possible.
                            assets_to_deposit = max_deposit
                        
                        # Ensure we always have minimum_total_idle when updating debt.
                        minimum_total_idle: uint256 = self.minimum_total_idle
                        total_idle: uint256 = self.total_idle
                
                        assert total_idle > minimum_total_idle, "no funds to deposit"
                        available_idle: uint256 = unsafe_sub(total_idle, minimum_total_idle)
                
                        # If insufficient funds to deposit, transfer only what is free.
                        if assets_to_deposit > available_idle:
                            assets_to_deposit = available_idle
                
                        # Can't Deposit 0.
                        if assets_to_deposit > 0:
                            # Cache for repeated use.
                            _asset: address = self.asset
                
                            # Approve the strategy to pull only what we are giving it.
                            self._erc20_safe_approve(_asset, strategy, assets_to_deposit)
                
                            # Always update based on actual amounts deposited.
                            pre_balance: uint256 = ERC20(_asset).balanceOf(self)
                            IStrategy(strategy).deposit(assets_to_deposit, self)
                            post_balance: uint256 = ERC20(_asset).balanceOf(self)
                
                            # Make sure our approval is always back to 0.
                            self._erc20_safe_approve(_asset, strategy, 0)
                
                            # Making sure we are changing according to the real result no 
                            # matter what. This will spend more gas but makes it more robust.
                            assets_to_deposit = pre_balance - post_balance
                
                            # Update storage.
                            self.total_idle -= assets_to_deposit
                            self.total_debt += assets_to_deposit
                
                        new_debt = current_debt + assets_to_deposit
                
                    # Commit memory to storage.
                    self.strategies[strategy].current_debt = new_debt
                
                    log DebtUpdated(strategy, current_debt, new_debt)
                    return new_debt
                
                ## ACCOUNTING MANAGEMENT ##
                @internal
                def _process_report(strategy: address) -> (uint256, uint256):
                    """
                    Processing a report means comparing the debt that the strategy has taken 
                    with the current amount of funds it is reporting. If the strategy owes 
                    less than it currently has, it means it has had a profit, else (assets < debt) 
                    it has had a loss.
                
                    Different strategies might choose different reporting strategies: pessimistic, 
                    only realised P&L, ... The best way to report depends on the strategy.
                
                    The profit will be distributed following a smooth curve over the vaults 
                    profit_max_unlock_time seconds. Losses will be taken immediately, first from the 
                    profit buffer (avoiding an impact in pps), then will reduce pps.
                
                    Any applicable fees are charged and distributed during the report as well
                    to the specified recipients.
                    """
                    # Make sure we have a valid strategy.
                    assert self.strategies[strategy].activation != 0, "inactive strategy"
                
                    # Vault assesses profits using 4626 compliant interface. 
                    # NOTE: It is important that a strategies `convertToAssets` implementation
                    # cannot be manipulated or else the vault could report incorrect gains/losses.
                    strategy_shares: uint256 = IStrategy(strategy).balanceOf(self)
                    # How much the vaults position is worth.
                    total_assets: uint256 = IStrategy(strategy).convertToAssets(strategy_shares)
                    # How much the vault had deposited to the strategy.
                    current_debt: uint256 = self.strategies[strategy].current_debt
                
                    gain: uint256 = 0
                    loss: uint256 = 0
                
                    ### Asses Gain or Loss ###
                
                    # Compare reported assets vs. the current debt.
                    if total_assets > current_debt:
                        # We have a gain.
                        gain = unsafe_sub(total_assets, current_debt)
                    else:
                        # We have a loss.
                        loss = unsafe_sub(current_debt, total_assets)
                    
                    # Cache `asset` for repeated use.
                    _asset: address = self.asset
                
                    ### Asses Fees and Refunds ###
                
                    # For Accountant fee assessment.
                    total_fees: uint256 = 0
                    total_refunds: uint256 = 0
                    # If accountant is not set, fees and refunds remain unchanged.
                    accountant: address = self.accountant
                    if accountant != empty(address):
                        total_fees, total_refunds = IAccountant(accountant).report(strategy, gain, loss)
                
                        if total_refunds > 0:
                            # Make sure we have enough approval and enough asset to pull.
                            total_refunds = min(total_refunds, min(ERC20(_asset).balanceOf(accountant), ERC20(_asset).allowance(accountant, self)))
                
                    # Total fees to charge in shares.
                    total_fees_shares: uint256 = 0
                    # For Protocol fee assessment.
                    protocol_fee_bps: uint16 = 0
                    protocol_fees_shares: uint256 = 0
                    protocol_fee_recipient: address = empty(address)
                    # `shares_to_burn` is derived from amounts that would reduce the vaults PPS.
                    # NOTE: this needs to be done before any pps changes
                    shares_to_burn: uint256 = 0
                    # Only need to burn shares if there is a loss or fees.
                    if loss + total_fees > 0:
                        # The amount of shares we will want to burn to offset losses and fees.
                        shares_to_burn = self._convert_to_shares(loss + total_fees, Rounding.ROUND_UP)
                
                        # If we have fees then get the proportional amount of shares to issue.
                        if total_fees > 0:
                            # Get the total amount shares to issue for the fees.
                            total_fees_shares = shares_to_burn * total_fees / (loss + total_fees)
                
                            # Get the protocol fee config for this vault.
                            protocol_fee_bps, protocol_fee_recipient = IFactory(self.factory).protocol_fee_config()
                
                            # If there is a protocol fee.
                            if protocol_fee_bps > 0:
                                # Get the percent of fees to go to protocol fees.
                                protocol_fees_shares = total_fees_shares * convert(protocol_fee_bps, uint256) / MAX_BPS
                
                
                    # Shares to lock is any amount that would otherwise increase the vaults PPS.
                    shares_to_lock: uint256 = 0
                    profit_max_unlock_time: uint256 = self.profit_max_unlock_time
                    # Get the amount we will lock to avoid a PPS increase.
                    if gain + total_refunds > 0 and profit_max_unlock_time != 0:
                        shares_to_lock = self._convert_to_shares(gain + total_refunds, Rounding.ROUND_DOWN)
                
                    # The total current supply including locked shares.
                    total_supply: uint256 = self.total_supply
                    # The total shares the vault currently owns. Both locked and unlocked.
                    total_locked_shares: uint256 = self.balance_of[self]
                    # Get the desired end amount of shares after all accounting.
                    ending_supply: uint256 = total_supply + shares_to_lock - shares_to_burn - self._unlocked_shares()
                    
                    # If we will end with more shares than we have now.
                    if ending_supply > total_supply:
                        # Issue the difference.
                        self._issue_shares(unsafe_sub(ending_supply, total_supply), self)
                
                    # Else we need to burn shares.
                    elif total_supply > ending_supply:
                        # Can't burn more than the vault owns.
                        to_burn: uint256 = min(unsafe_sub(total_supply, ending_supply), total_locked_shares)
                        self._burn_shares(to_burn, self)
                
                    # Adjust the amount to lock for this period.
                    if shares_to_lock > shares_to_burn:
                        # Don't lock fees or losses.
                        shares_to_lock = unsafe_sub(shares_to_lock, shares_to_burn)
                    else:
                        shares_to_lock = 0
                
                    # Pull refunds
                    if total_refunds > 0:
                        # Transfer the refunded amount of asset to the vault.
                        self._erc20_safe_transfer_from(_asset, accountant, self, total_refunds)
                        # Update storage to increase total assets.
                        self.total_idle += total_refunds
                
                    # Record any reported gains.
                    if gain > 0:
                        # NOTE: this will increase total_assets
                        current_debt = unsafe_add(current_debt, gain)
                        self.strategies[strategy].current_debt = current_debt
                        self.total_debt += gain
                
                    # Or record any reported loss
                    elif loss > 0:
                        current_debt = unsafe_sub(current_debt, loss)
                        self.strategies[strategy].current_debt = current_debt
                        self.total_debt -= loss
                
                    # Issue shares for fees that were calculated above if applicable.
                    if total_fees_shares > 0:
                        # Accountant fees are (total_fees - protocol_fees).
                        self._issue_shares(total_fees_shares - protocol_fees_shares, accountant)
                
                        # If we also have protocol fees.
                        if protocol_fees_shares > 0:
                            self._issue_shares(protocol_fees_shares, protocol_fee_recipient)
                
                    # Update unlocking rate and time to fully unlocked.
                    total_locked_shares = self.balance_of[self]
                    if total_locked_shares > 0:
                        previously_locked_time: uint256 = 0
                        _full_profit_unlock_date: uint256 = self.full_profit_unlock_date
                        # Check if we need to account for shares still unlocking.
                        if _full_profit_unlock_date > block.timestamp: 
                            # There will only be previously locked shares if time remains.
                            # We calculate this here since it will not occur every time we lock shares.
                            previously_locked_time = (total_locked_shares - shares_to_lock) * (_full_profit_unlock_date - block.timestamp)
                
                        # new_profit_locking_period is a weighted average between the remaining time of the previously locked shares and the profit_max_unlock_time
                        new_profit_locking_period: uint256 = (previously_locked_time + shares_to_lock * profit_max_unlock_time) / total_locked_shares
                        # Calculate how many shares unlock per second.
                        self.profit_unlocking_rate = total_locked_shares * MAX_BPS_EXTENDED / new_profit_locking_period
                        # Calculate how long until the full amount of shares is unlocked.
                        self.full_profit_unlock_date = block.timestamp + new_profit_locking_period
                        # Update the last profitable report timestamp.
                        self.last_profit_update = block.timestamp
                    else:
                        # NOTE: only setting this to the 0 will turn in the desired effect, 
                        # no need to update profit_unlocking_rate
                        self.full_profit_unlock_date = 0
                    
                    # Record the report of profit timestamp.
                    self.strategies[strategy].last_report = block.timestamp
                
                    # We have to recalculate the fees paid for cases with an overall loss or no profit locking
                    if loss + total_fees > gain + total_refunds or profit_max_unlock_time == 0:
                        total_fees = self._convert_to_assets(total_fees_shares, Rounding.ROUND_DOWN)
                
                    log StrategyReported(
                        strategy,
                        gain,
                        loss,
                        current_debt,
                        total_fees * convert(protocol_fee_bps, uint256) / MAX_BPS, # Protocol Fees
                        total_fees,
                        total_refunds
                    )
                
                    return (gain, loss)
                
                # SETTERS #
                @external
                def set_accountant(new_accountant: address):
                    """
                    @notice Set the new accountant address.
                    @param new_accountant The new accountant address.
                    """
                    self._enforce_role(msg.sender, Roles.ACCOUNTANT_MANAGER)
                    self.accountant = new_accountant
                
                    log UpdateAccountant(new_accountant)
                
                @external
                def set_default_queue(new_default_queue: DynArray[address, MAX_QUEUE]):
                    """
                    @notice Set the new default queue array.
                    @dev Will check each strategy to make sure it is active. But will not
                        check that the same strategy is not added twice. maxRedeem and maxWithdraw
                        return values may be inaccurate if a strategy is added twice.
                    @param new_default_queue The new default queue array.
                    """
                    self._enforce_role(msg.sender, Roles.QUEUE_MANAGER)
                
                    # Make sure every strategy in the new queue is active.
                    for strategy in new_default_queue:
                        assert self.strategies[strategy].activation != 0, "!inactive"
                
                    # Save the new queue.
                    self.default_queue = new_default_queue
                
                    log UpdateDefaultQueue(new_default_queue)
                
                @external
                def set_use_default_queue(use_default_queue: bool):
                    """
                    @notice Set a new value for `use_default_queue`.
                    @dev If set `True` the default queue will always be
                        used no matter whats passed in.
                    @param use_default_queue new value.
                    """
                    self._enforce_role(msg.sender, Roles.QUEUE_MANAGER)
                    self.use_default_queue = use_default_queue
                
                    log UpdateUseDefaultQueue(use_default_queue)
                
                @external
                def set_deposit_limit(deposit_limit: uint256, override: bool = False):
                    """
                    @notice Set the new deposit limit.
                    @dev Can not be changed if a deposit_limit_module
                    is set unless the override flag is true or if shutdown.
                    @param deposit_limit The new deposit limit.
                    @param override If a `deposit_limit_module` already set should be overridden.
                    """
                    assert self.shutdown == False # Dev: shutdown
                    self._enforce_role(msg.sender, Roles.DEPOSIT_LIMIT_MANAGER)
                
                    # If we are overriding the deposit limit module.
                    if override:
                        # Make sure it is set to address 0 if not already.
                        if self.deposit_limit_module != empty(address):
                
                            self.deposit_limit_module = empty(address)
                            log UpdateDepositLimitModule(empty(address))
                    else:  
                        # Make sure the deposit_limit_module has been set to address(0).
                        assert self.deposit_limit_module == empty(address), "using module"
                
                    self.deposit_limit = deposit_limit
                
                    log UpdateDepositLimit(deposit_limit)
                
                @external
                def set_deposit_limit_module(deposit_limit_module: address, override: bool = False):
                    """
                    @notice Set a contract to handle the deposit limit.
                    @dev The default `deposit_limit` will need to be set to
                    max uint256 since the module will override it or the override flag
                    must be set to true to set it to max in 1 tx..
                    @param deposit_limit_module Address of the module.
                    @param override If a `deposit_limit` already set should be overridden.
                    """
                    assert self.shutdown == False # Dev: shutdown
                    self._enforce_role(msg.sender, Roles.DEPOSIT_LIMIT_MANAGER)
                
                    # If we are overriding the deposit limit
                    if override:
                        # Make sure it is max uint256 if not already.
                        if self.deposit_limit != max_value(uint256):
                
                            self.deposit_limit = max_value(uint256)
                            log UpdateDepositLimit(max_value(uint256))
                    else:
                        # Make sure the deposit_limit has been set to uint max.
                        assert self.deposit_limit == max_value(uint256), "using deposit limit"
                
                    self.deposit_limit_module = deposit_limit_module
                
                    log UpdateDepositLimitModule(deposit_limit_module)
                
                @external
                def set_withdraw_limit_module(withdraw_limit_module: address):
                    """
                    @notice Set a contract to handle the withdraw limit.
                    @dev This will override the default `max_withdraw`.
                    @param withdraw_limit_module Address of the module.
                    """
                    self._enforce_role(msg.sender, Roles.WITHDRAW_LIMIT_MANAGER)
                
                    self.withdraw_limit_module = withdraw_limit_module
                
                    log UpdateWithdrawLimitModule(withdraw_limit_module)
                
                @external
                def set_minimum_total_idle(minimum_total_idle: uint256):
                    """
                    @notice Set the new minimum total idle.
                    @param minimum_total_idle The new minimum total idle.
                    """
                    self._enforce_role(msg.sender, Roles.MINIMUM_IDLE_MANAGER)
                    self.minimum_total_idle = minimum_total_idle
                
                    log UpdateMinimumTotalIdle(minimum_total_idle)
                
                @external
                def setProfitMaxUnlockTime(new_profit_max_unlock_time: uint256):
                    """
                    @notice Set the new profit max unlock time.
                    @dev The time is denominated in seconds and must be less than 1 year.
                        We only need to update locking period if setting to 0,
                        since the current period will use the old rate and on the next
                        report it will be reset with the new unlocking time.
                    
                        Setting to 0 will cause any currently locked profit to instantly
                        unlock and an immediate increase in the vaults Price Per Share.
                
                    @param new_profit_max_unlock_time The new profit max unlock time.
                    """
                    self._enforce_role(msg.sender, Roles.PROFIT_UNLOCK_MANAGER)
                    # Must be less than one year for report cycles
                    assert new_profit_max_unlock_time <= 31_556_952, "profit unlock time too long"
                
                    # If setting to 0 we need to reset any locked values.
                    if (new_profit_max_unlock_time == 0):
                
                        share_balance: uint256 = self.balance_of[self]
                        if share_balance > 0:
                            # Burn any shares the vault still has.
                            self._burn_shares(share_balance, self)
                
                        # Reset unlocking variables to 0.
                        self.profit_unlocking_rate = 0
                        self.full_profit_unlock_date = 0
                
                    self.profit_max_unlock_time = new_profit_max_unlock_time
                
                    log UpdateProfitMaxUnlockTime(new_profit_max_unlock_time)
                
                # ROLE MANAGEMENT #
                @internal
                def _enforce_role(account: address, role: Roles):
                    # Make sure the sender holds the role.
                    assert role in self.roles[account], "not allowed"
                
                @external
                def set_role(account: address, role: Roles):
                    """
                    @notice Set the roles for an account.
                    @dev This will fully override an accounts current roles
                     so it should include all roles the account should hold.
                    @param account The account to set the role for.
                    @param role The roles the account should hold.
                    """
                    assert msg.sender == self.role_manager
                    self.roles[account] = role
                
                    log RoleSet(account, role)
                
                @external
                def add_role(account: address, role: Roles):
                    """
                    @notice Add a new role to an address.
                    @dev This will add a new role to the account
                     without effecting any of the previously held roles.
                    @param account The account to add a role to.
                    @param role The new role to add to account.
                    """
                    assert msg.sender == self.role_manager
                    self.roles[account] = self.roles[account] | role
                
                    log RoleSet(account, self.roles[account])
                
                @external
                def remove_role(account: address, role: Roles):
                    """
                    @notice Remove a single role from an account.
                    @dev This will leave all other roles for the 
                     account unchanged.
                    @param account The account to remove a Role from.
                    @param role The Role to remove.
                    """
                    assert msg.sender == self.role_manager
                    self.roles[account] = self.roles[account] & ~role
                
                    log RoleSet(account, self.roles[account])
                    
                @external
                def transfer_role_manager(role_manager: address):
                    """
                    @notice Step 1 of 2 in order to transfer the 
                        role manager to a new address. This will set
                        the future_role_manager. Which will then need
                        to be accepted by the new manager.
                    @param role_manager The new role manager address.
                    """
                    assert msg.sender == self.role_manager
                    self.future_role_manager = role_manager
                
                @external
                def accept_role_manager():
                    """
                    @notice Accept the role manager transfer.
                    """
                    assert msg.sender == self.future_role_manager
                    self.role_manager = msg.sender
                    self.future_role_manager = empty(address)
                
                    log UpdateRoleManager(msg.sender)
                
                # VAULT STATUS VIEWS
                
                @view
                @external
                def isShutdown() -> bool:
                    """
                    @notice Get if the vault is shutdown.
                    @return Bool representing the shutdown status
                    """
                    return self.shutdown
                @view
                @external
                def unlockedShares() -> uint256:
                    """
                    @notice Get the amount of shares that have been unlocked.
                    @return The amount of shares that are have been unlocked.
                    """
                    return self._unlocked_shares()
                
                @view
                @external
                def pricePerShare() -> uint256:
                    """
                    @notice Get the price per share (pps) of the vault.
                    @dev This value offers limited precision. Integrations that require 
                        exact precision should use convertToAssets or convertToShares instead.
                    @return The price per share.
                    """
                    return self._convert_to_assets(10 ** convert(self.decimals, uint256), Rounding.ROUND_DOWN)
                
                @view
                @external
                def get_default_queue() -> DynArray[address, MAX_QUEUE]:
                    """
                    @notice Get the full default queue currently set.
                    @return The current default withdrawal queue.
                    """
                    return self.default_queue
                
                ## REPORTING MANAGEMENT ##
                @external
                @nonreentrant("lock")
                def process_report(strategy: address) -> (uint256, uint256):
                    """
                    @notice Process the report of a strategy.
                    @param strategy The strategy to process the report for.
                    @return The gain and loss of the strategy.
                    """
                    self._enforce_role(msg.sender, Roles.REPORTING_MANAGER)
                    return self._process_report(strategy)
                
                @external
                @nonreentrant("lock")
                def buy_debt(strategy: address, amount: uint256):
                    """
                    @notice Used for governance to buy bad debt from the vault.
                    @dev This should only ever be used in an emergency in place
                    of force revoking a strategy in order to not report a loss.
                    It allows the DEBT_PURCHASER role to buy the strategies debt
                    for an equal amount of `asset`. 
                
                    @param strategy The strategy to buy the debt for
                    @param amount The amount of debt to buy from the vault.
                    """
                    self._enforce_role(msg.sender, Roles.DEBT_PURCHASER)
                    assert self.strategies[strategy].activation != 0, "not active"
                    
                    # Cache the current debt.
                    current_debt: uint256 = self.strategies[strategy].current_debt
                    _amount: uint256 = amount
                
                    assert current_debt > 0, "nothing to buy"
                    assert _amount > 0, "nothing to buy with"
                    
                    if _amount > current_debt:
                        _amount = current_debt
                
                    # We get the proportion of the debt that is being bought and
                    # transfer the equivalent shares. We assume this is being used
                    # due to strategy issues so won't rely on its conversion rates.
                    shares: uint256 = IStrategy(strategy).balanceOf(self) * _amount / current_debt
                
                    assert shares > 0, "cannot buy zero"
                
                    self._erc20_safe_transfer_from(self.asset, msg.sender, self, _amount)
                
                    # Lower strategy debt
                    self.strategies[strategy].current_debt -= _amount
                    # lower total debt
                    self.total_debt -= _amount
                    # Increase total idle
                    self.total_idle += _amount
                
                    # log debt change
                    log DebtUpdated(strategy, current_debt, current_debt - _amount)
                
                    # Transfer the strategies shares out.
                    self._erc20_safe_transfer(strategy, msg.sender, shares)
                
                    log DebtPurchased(strategy, _amount)
                
                ## STRATEGY MANAGEMENT ##
                @external
                def add_strategy(new_strategy: address, add_to_queue: bool=True):
                    """
                    @notice Add a new strategy.
                    @param new_strategy The new strategy to add.
                    """
                    self._enforce_role(msg.sender, Roles.ADD_STRATEGY_MANAGER)
                    self._add_strategy(new_strategy, add_to_queue)
                
                @external
                def revoke_strategy(strategy: address):
                    """
                    @notice Revoke a strategy.
                    @param strategy The strategy to revoke.
                    """
                    self._enforce_role(msg.sender, Roles.REVOKE_STRATEGY_MANAGER)
                    self._revoke_strategy(strategy)
                
                @external
                def force_revoke_strategy(strategy: address):
                    """
                    @notice Force revoke a strategy.
                    @dev The vault will remove the strategy and write off any debt left 
                        in it as a loss. This function is a dangerous function as it can force a 
                        strategy to take a loss. All possible assets should be removed from the 
                        strategy first via update_debt. If a strategy is removed erroneously it 
                        can be re-added and the loss will be credited as profit. Fees will apply.
                    @param strategy The strategy to force revoke.
                    """
                    self._enforce_role(msg.sender, Roles.FORCE_REVOKE_MANAGER)
                    self._revoke_strategy(strategy, True)
                
                ## DEBT MANAGEMENT ##
                @external
                def update_max_debt_for_strategy(strategy: address, new_max_debt: uint256):
                    """
                    @notice Update the max debt for a strategy.
                    @param strategy The strategy to update the max debt for.
                    @param new_max_debt The new max debt for the strategy.
                    """
                    self._enforce_role(msg.sender, Roles.MAX_DEBT_MANAGER)
                    assert self.strategies[strategy].activation != 0, "inactive strategy"
                    self.strategies[strategy].max_debt = new_max_debt
                
                    log UpdatedMaxDebtForStrategy(msg.sender, strategy, new_max_debt)
                
                @external
                @nonreentrant("lock")
                def update_debt(
                    strategy: address, 
                    target_debt: uint256, 
                    max_loss: uint256 = MAX_BPS
                ) -> uint256:
                    """
                    @notice Update the debt for a strategy.
                    @param strategy The strategy to update the debt for.
                    @param target_debt The target debt for the strategy.
                    @param max_loss Optional to check realized losses on debt decreases.
                    @return The amount of debt added or removed.
                    """
                    self._enforce_role(msg.sender, Roles.DEBT_MANAGER)
                    return self._update_debt(strategy, target_debt, max_loss)
                
                ## EMERGENCY MANAGEMENT ##
                @external
                def shutdown_vault():
                    """
                    @notice Shutdown the vault.
                    """
                    self._enforce_role(msg.sender, Roles.EMERGENCY_MANAGER)
                    assert self.shutdown == False
                    
                    # Shutdown the vault.
                    self.shutdown = True
                
                    # Set deposit limit to 0.
                    if self.deposit_limit_module != empty(address):
                        self.deposit_limit_module = empty(address)
                
                        log UpdateDepositLimitModule(empty(address))
                
                    self.deposit_limit = 0
                    log UpdateDepositLimit(0)
                
                    self.roles[msg.sender] = self.roles[msg.sender] | Roles.DEBT_MANAGER
                    log Shutdown()
                
                
                ## SHARE MANAGEMENT ##
                ## ERC20 + ERC4626 ##
                @external
                @nonreentrant("lock")
                def deposit(assets: uint256, receiver: address) -> uint256:
                    """
                    @notice Deposit assets into the vault.
                    @param assets The amount of assets to deposit.
                    @param receiver The address to receive the shares.
                    @return The amount of shares minted.
                    """
                    return self._deposit(msg.sender, receiver, assets)
                
                @external
                @nonreentrant("lock")
                def mint(shares: uint256, receiver: address) -> uint256:
                    """
                    @notice Mint shares for the receiver.
                    @param shares The amount of shares to mint.
                    @param receiver The address to receive the shares.
                    @return The amount of assets deposited.
                    """
                    return self._mint(msg.sender, receiver, shares)
                
                @external
                @nonreentrant("lock")
                def withdraw(
                    assets: uint256, 
                    receiver: address, 
                    owner: address, 
                    max_loss: uint256 = 0,
                    strategies: DynArray[address, MAX_QUEUE] = []
                ) -> uint256:
                    """
                    @notice Withdraw an amount of asset to `receiver` burning `owner`s shares.
                    @dev The default behavior is to not allow any loss.
                    @param assets The amount of asset to withdraw.
                    @param receiver The address to receive the assets.
                    @param owner The address who's shares are being burnt.
                    @param max_loss Optional amount of acceptable loss in Basis Points.
                    @param strategies Optional array of strategies to withdraw from.
                    @return The amount of shares actually burnt.
                    """
                    shares: uint256 = self._convert_to_shares(assets, Rounding.ROUND_UP)
                    self._redeem(msg.sender, receiver, owner, assets, shares, max_loss, strategies)
                    return shares
                
                @external
                @nonreentrant("lock")
                def redeem(
                    shares: uint256, 
                    receiver: address, 
                    owner: address, 
                    max_loss: uint256 = MAX_BPS,
                    strategies: DynArray[address, MAX_QUEUE] = []
                ) -> uint256:
                    """
                    @notice Redeems an amount of shares of `owners` shares sending funds to `receiver`.
                    @dev The default behavior is to allow losses to be realized.
                    @param shares The amount of shares to burn.
                    @param receiver The address to receive the assets.
                    @param owner The address who's shares are being burnt.
                    @param max_loss Optional amount of acceptable loss in Basis Points.
                    @param strategies Optional array of strategies to withdraw from.
                    @return The amount of assets actually withdrawn.
                    """
                    assets: uint256 = self._convert_to_assets(shares, Rounding.ROUND_DOWN)
                    # Always return the actual amount of assets withdrawn.
                    return self._redeem(msg.sender, receiver, owner, assets, shares, max_loss, strategies)
                
                
                @external
                def approve(spender: address, amount: uint256) -> bool:
                    """
                    @notice Approve an address to spend the vault's shares.
                    @param spender The address to approve.
                    @param amount The amount of shares to approve.
                    @return True if the approval was successful.
                    """
                    return self._approve(msg.sender, spender, amount)
                
                @external
                def transfer(receiver: address, amount: uint256) -> bool:
                    """
                    @notice Transfer shares to a receiver.
                    @param receiver The address to transfer shares to.
                    @param amount The amount of shares to transfer.
                    @return True if the transfer was successful.
                    """
                    assert receiver not in [self, empty(address)]
                    self._transfer(msg.sender, receiver, amount)
                    return True
                
                @external
                def transferFrom(sender: address, receiver: address, amount: uint256) -> bool:
                    """
                    @notice Transfer shares from a sender to a receiver.
                    @param sender The address to transfer shares from.
                    @param receiver The address to transfer shares to.
                    @param amount The amount of shares to transfer.
                    @return True if the transfer was successful.
                    """
                    assert receiver not in [self, empty(address)]
                    return self._transfer_from(sender, receiver, amount)
                
                ## ERC20+4626 compatibility
                @external
                def permit(
                    owner: address, 
                    spender: address, 
                    amount: uint256, 
                    deadline: uint256, 
                    v: uint8, 
                    r: bytes32, 
                    s: bytes32
                ) -> bool:
                    """
                    @notice Approve an address to spend the vault's shares.
                    @param owner The address to approve.
                    @param spender The address to approve.
                    @param amount The amount of shares to approve.
                    @param deadline The deadline for the permit.
                    @param v The v component of the signature.
                    @param r The r component of the signature.
                    @param s The s component of the signature.
                    @return True if the approval was successful.
                    """
                    return self._permit(owner, spender, amount, deadline, v, r, s)
                
                @view
                @external
                def balanceOf(addr: address) -> uint256:
                    """
                    @notice Get the balance of a user.
                    @param addr The address to get the balance of.
                    @return The balance of the user.
                    """
                    if(addr == self):
                        # If the address is the vault, account for locked shares.
                        return self.balance_of[addr] - self._unlocked_shares()
                
                    return self.balance_of[addr]
                
                @view
                @external
                def totalSupply() -> uint256:
                    """
                    @notice Get the total supply of shares.
                    @return The total supply of shares.
                    """
                    return self._total_supply()
                
                @view
                @external
                def totalAssets() -> uint256:
                    """
                    @notice Get the total assets held by the vault.
                    @return The total assets held by the vault.
                    """
                    return self._total_assets()
                
                @view
                @external
                def totalIdle() -> uint256:
                    """
                    @notice Get the amount of loose `asset` the vault holds.
                    @return The current total idle.
                    """
                    return self.total_idle
                
                @view
                @external
                def totalDebt() -> uint256:
                    """
                    @notice Get the the total amount of funds invested
                    across all strategies.
                    @return The current total debt.
                    """
                    return self.total_debt
                
                @view
                @external
                def convertToShares(assets: uint256) -> uint256:
                    """
                    @notice Convert an amount of assets to shares.
                    @param assets The amount of assets to convert.
                    @return The amount of shares.
                    """
                    return self._convert_to_shares(assets, Rounding.ROUND_DOWN)
                
                @view
                @external
                def previewDeposit(assets: uint256) -> uint256:
                    """
                    @notice Preview the amount of shares that would be minted for a deposit.
                    @param assets The amount of assets to deposit.
                    @return The amount of shares that would be minted.
                    """
                    return self._convert_to_shares(assets, Rounding.ROUND_DOWN)
                
                @view
                @external
                def previewMint(shares: uint256) -> uint256:
                    """
                    @notice Preview the amount of assets that would be deposited for a mint.
                    @param shares The amount of shares to mint.
                    @return The amount of assets that would be deposited.
                    """
                    return self._convert_to_assets(shares, Rounding.ROUND_UP)
                
                @view
                @external
                def convertToAssets(shares: uint256) -> uint256:
                    """
                    @notice Convert an amount of shares to assets.
                    @param shares The amount of shares to convert.
                    @return The amount of assets.
                    """
                    return self._convert_to_assets(shares, Rounding.ROUND_DOWN)
                
                @view
                @external
                def maxDeposit(receiver: address) -> uint256:
                    """
                    @notice Get the maximum amount of assets that can be deposited.
                    @param receiver The address that will receive the shares.
                    @return The maximum amount of assets that can be deposited.
                    """
                    return self._max_deposit(receiver)
                
                @view
                @external
                def maxMint(receiver: address) -> uint256:
                    """
                    @notice Get the maximum amount of shares that can be minted.
                    @param receiver The address that will receive the shares.
                    @return The maximum amount of shares that can be minted.
                    """
                    max_deposit: uint256 = self._max_deposit(receiver)
                    return self._convert_to_shares(max_deposit, Rounding.ROUND_DOWN)
                
                @view
                @external
                def maxWithdraw(
                    owner: address,
                    max_loss: uint256 = 0,
                    strategies: DynArray[address, MAX_QUEUE] = []
                ) -> uint256:
                    """
                    @notice Get the maximum amount of assets that can be withdrawn.
                    @dev Complies to normal 4626 interface and takes custom params.
                    NOTE: Passing in a incorrectly ordered queue may result in
                     incorrect returns values.
                    @param owner The address that owns the shares.
                    @param max_loss Custom max_loss if any.
                    @param strategies Custom strategies queue if any.
                    @return The maximum amount of assets that can be withdrawn.
                    """
                    return self._max_withdraw(owner, max_loss, strategies)
                
                @view
                @external
                def maxRedeem(
                    owner: address,
                    max_loss: uint256 = MAX_BPS,
                    strategies: DynArray[address, MAX_QUEUE] = []
                ) -> uint256:
                    """
                    @notice Get the maximum amount of shares that can be redeemed.
                    @dev Complies to normal 4626 interface and takes custom params.
                    NOTE: Passing in a incorrectly ordered queue may result in
                     incorrect returns values.
                    @param owner The address that owns the shares.
                    @param max_loss Custom max_loss if any.
                    @param strategies Custom strategies queue if any.
                    @return The maximum amount of shares that can be redeemed.
                    """
                    return min(
                        # Min of the shares equivalent of max_withdraw or the full balance
                        self._convert_to_shares(self._max_withdraw(owner, max_loss, strategies), Rounding.ROUND_DOWN),
                        self.balance_of[owner]
                    )
                
                @view
                @external
                def previewWithdraw(assets: uint256) -> uint256:
                    """
                    @notice Preview the amount of shares that would be redeemed for a withdraw.
                    @param assets The amount of assets to withdraw.
                    @return The amount of shares that would be redeemed.
                    """
                    return self._convert_to_shares(assets, Rounding.ROUND_UP)
                
                @view
                @external
                def previewRedeem(shares: uint256) -> uint256:
                    """
                    @notice Preview the amount of assets that would be withdrawn for a redeem.
                    @param shares The amount of shares to redeem.
                    @return The amount of assets that would be withdrawn.
                    """
                    return self._convert_to_assets(shares, Rounding.ROUND_DOWN)
                
                @view
                @external
                def FACTORY() -> address:
                    """
                    @notice Address of the factory that deployed the vault.
                    @dev Is used to retrieve the protocol fees.
                    @return Address of the vault factory.
                    """
                    return self.factory
                
                @view
                @external
                def apiVersion() -> String[28]:
                    """
                    @notice Get the API version of the vault.
                    @return The API version of the vault.
                    """
                    return API_VERSION
                
                @view
                @external
                def assess_share_of_unrealised_losses(strategy: address, assets_needed: uint256) -> uint256:
                    """
                    @notice Assess the share of unrealised losses that a strategy has.
                    @param strategy The address of the strategy.
                    @param assets_needed The amount of assets needed to be withdrawn.
                    @return The share of unrealised losses that the strategy has.
                    """
                    assert self.strategies[strategy].current_debt >= assets_needed
                
                    return self._assess_share_of_unrealised_losses(strategy, assets_needed)
                
                ## Profit locking getter functions ##
                
                @view
                @external
                def profitMaxUnlockTime() -> uint256:
                    """
                    @notice Gets the current time profits are set to unlock over.
                    @return The current profit max unlock time.
                    """
                    return self.profit_max_unlock_time
                
                @view
                @external
                def fullProfitUnlockDate() -> uint256:
                    """
                    @notice Gets the timestamp at which all profits will be unlocked.
                    @return The full profit unlocking timestamp
                    """
                    return self.full_profit_unlock_date
                
                @view
                @external
                def profitUnlockingRate() -> uint256:
                    """
                    @notice The per second rate at which profits are unlocking.
                    @dev This is denominated in EXTENDED_BPS decimals.
                    @return The current profit unlocking rate.
                    """
                    return self.profit_unlocking_rate
                
                
                @view
                @external
                def lastProfitUpdate() -> uint256:
                    """
                    @notice The timestamp of the last time shares were locked.
                    @return The last profit update.
                    """
                    return self.last_profit_update
                
                # eip-1344
                @view
                @internal
                def domain_separator() -> bytes32:
                    return keccak256(
                        concat(
                            DOMAIN_TYPE_HASH,
                            keccak256(convert("Yearn Vault", Bytes[11])),
                            keccak256(convert(API_VERSION, Bytes[28])),
                            convert(chain.id, bytes32),
                            convert(self, bytes32)
                        )
                    )
                
                @view
                @external
                def DOMAIN_SEPARATOR() -> bytes32:
                    """
                    @notice Get the domain separator.
                    @return The domain separator.
                    """
                    return self.domain_separator()

                File 5 of 7: DebtAllocator
                // SPDX-License-Identifier: GNU AGPLv3
                pragma solidity >=0.8.18 ^0.8.0;
                
                // lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol
                
                // 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);
                }
                
                // lib/openzeppelin-contracts/contracts/utils/math/Math.sol
                
                // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
                
                /**
                 * @dev Standard math utilities missing in the Solidity language.
                 */
                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);
                        }
                    }
                }
                
                // lib/tokenized-strategy-periphery/src/utils/Governance.sol
                
                contract Governance {
                    /// @notice Emitted when the governance address is updated.
                    event GovernanceTransferred(
                        address indexed previousGovernance,
                        address indexed newGovernance
                    );
                
                    modifier onlyGovernance() {
                        _checkGovernance();
                        _;
                    }
                
                    /// @notice Checks if the msg sender is the governance.
                    function _checkGovernance() internal view virtual {
                        require(governance == msg.sender, "!governance");
                    }
                
                    /// @notice Address that can set the default base fee and provider
                    address public governance;
                
                    constructor(address _governance) {
                        governance = _governance;
                
                        emit GovernanceTransferred(address(0), _governance);
                    }
                
                    /**
                     * @notice Sets a new address as the governance of the contract.
                     * @dev Throws if the caller is not current governance.
                     * @param _newGovernance The new governance address.
                     */
                    function transferGovernance(
                        address _newGovernance
                    ) external virtual onlyGovernance {
                        require(_newGovernance != address(0), "ZERO ADDRESS");
                        address oldGovernance = governance;
                        governance = _newGovernance;
                
                        emit GovernanceTransferred(oldGovernance, _newGovernance);
                    }
                }
                
                // lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol
                
                // 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);
                }
                
                // lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol
                
                // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol)
                
                /**
                 * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
                 * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
                 *
                 * _Available since v4.7._
                 */
                interface 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);
                }
                
                // lib/yearn-vaults-v3/contracts/interfaces/IVault.sol
                
                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 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 UpdateAutoAllocate(bool auto_allocate);
                    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 auto_allocate() external view returns (bool);
                
                    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 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 initialize(
                        address,
                        string memory,
                        string memory,
                        address,
                        uint256
                    ) external;
                
                    function setName(string memory) external;
                
                    function setSymbol(string memory) external;
                
                    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_auto_allocate(bool) external;
                
                    function set_deposit_limit(uint256 deposit_limit) external;
                
                    function set_deposit_limit(
                        uint256 deposit_limit,
                        bool should_override
                    ) external;
                
                    function set_deposit_limit_module(
                        address new_deposit_limit_module
                    ) external;
                
                    function set_deposit_limit_module(
                        address new_deposit_limit_module,
                        bool should_override
                    ) 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 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 update_debt(
                        address strategy,
                        uint256 target_debt,
                        uint256 max_loss
                    ) 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 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);
                }
                
                // src/debtAllocators/DebtAllocator.sol
                
                interface IBaseFee {
                    function basefee_global() external view returns (uint256);
                }
                
                /**
                 * @title YearnV3  Debt Allocator
                 * @author yearn.finance
                 * @notice
                 *  This Debt Allocator is meant to be used alongside
                 *  Yearn V3 vaults to provide the needed triggers for a keeper
                 *  to perform automated debt updates for the vaults strategies.
                 *
                 * @dev
                 *  Each vault that should be managed by this allocator will
                 *  need to be added by first setting a `minimumChange` for the
                 *  vault, which will act as the minimum amount of funds to move that will
                 *  trigger a debt update. Then adding each strategy by setting a
                 *  `targetRatio` and optionally a `maxRatio`.
                 *
                 *  The allocator aims to allocate debt between the strategies
                 *  based on their set target ratios. Which are denominated in basis
                 *  points and represent the percent of total assets that specific
                 *  strategy should hold (i.e 1_000 == 10% of the vaults `totalAssets`).
                 *
                 *  The trigger will attempt to allocate up to the `maxRatio` when
                 *  the strategy has `minimumChange` amount less than the `targetRatio`.
                 *  And will pull funds to the `targetRatio` when it has `minimumChange`
                 *  more than its `maxRatio`.
                 */
                contract DebtAllocator is Governance {
                    /// @notice An event emitted when the base fee provider is set.
                    event UpdatedBaseFeeProvider(address baseFeeProvider);
                
                    /// @notice An event emitted when a keeper is added or removed.
                    event UpdateKeeper(address indexed keeper, bool allowed);
                
                    /// @notice An event emitted when the max base fee is updated.
                    event UpdateMaxAcceptableBaseFee(uint256 newMaxAcceptableBaseFee);
                
                    /// @notice An event emitted when a strategies debt ratios are Updated.
                    event UpdateStrategyDebtRatio(
                        address indexed vault,
                        address indexed strategy,
                        uint256 newTargetRatio,
                        uint256 newMaxRatio,
                        uint256 newTotalDebtRatio
                    );
                
                    /// @notice An event emitted when a strategy is added or removed.
                    event StrategyChanged(
                        address indexed vault,
                        address indexed strategy,
                        Status status
                    );
                
                    /// @notice An event emitted when the minimum change is updated.
                    event UpdateMinimumChange(address indexed vault, uint256 newMinimumChange);
                
                    /// @notice An even emitted when the paused status is updated.
                    event UpdatePaused(address indexed vault, bool indexed status);
                
                    /// @notice An event emitted when the minimum time to wait is updated.
                    event UpdateMinimumWait(uint256 newMinimumWait);
                
                    /// @notice An event emitted when a keeper is added or removed.
                    event UpdateManager(address indexed manager, bool allowed);
                
                    /// @notice An event emitted when the max debt update loss is updated.
                    event UpdateMaxDebtUpdateLoss(uint256 newMaxDebtUpdateLoss);
                
                    /// @notice Status when a strategy is added or removed from the allocator.
                    enum Status {
                        NULL,
                        ADDED,
                        REMOVED
                    }
                
                    /// @notice Struct for each strategies info.
                    struct StrategyConfig {
                        // Flag to set when a strategy is added.
                        bool added;
                        // The ideal percent in Basis Points the strategy should have.
                        uint16 targetRatio;
                        // The max percent of assets the strategy should hold.
                        uint16 maxRatio;
                        // Timestamp of the last time debt was updated.
                        // The debt updates must be done through this allocator
                        // for this to be used.
                        uint96 lastUpdate;
                        // We have an extra 120 bits in the slot.
                        // So we declare the variable in the struct so it can be
                        // used if this contract is inherited.
                        uint120 open;
                    }
                
                    /// @notice Struct to hold the vault's info.
                    struct VaultConfig {
                        // Optional flag to stop the triggers.
                        bool paused;
                        // The minimum amount denominated in asset that will
                        // need to be moved to trigger a debt update.
                        uint128 minimumChange;
                        // Total debt ratio currently allocated in basis points.
                        // Can't be more than 10_000.
                        uint16 totalDebtRatio;
                    }
                
                    /// @notice Used during the `shouldUpdateDebt` to hold the data.
                    struct StrategyDebtInfo {
                        VaultConfig vaultConfig;
                        StrategyConfig strategyConfig;
                        uint256 vaultAssets;
                        uint256 targetDebt;
                        uint256 maxDebt;
                        uint256 currentIdle;
                        uint256 minIdle;
                        uint256 toChange;
                    }
                
                    /// @notice Make sure the caller is governance or a manager.
                    modifier onlyManagers() {
                        _isManager();
                        _;
                    }
                
                    /// @notice Make sure the caller is a keeper
                    modifier onlyKeepers() {
                        _isKeeper();
                        _;
                    }
                
                    /// @notice Check is either factories governance or local manager.
                    function _isManager() internal view virtual {
                        require(managers[msg.sender] || msg.sender == governance, "!manager");
                    }
                
                    /// @notice Check is one of the allowed keepers.
                    function _isKeeper() internal view virtual {
                        require(keepers[msg.sender], "!keeper");
                    }
                
                    uint256 internal constant MAX_BPS = 10_000;
                
                    /// @notice Time to wait between debt updates in seconds.
                    uint256 public minimumWait;
                
                    /// @notice Provider to read current block's base fee.
                    address public baseFeeProvider;
                
                    /// @notice Max loss to accept on debt updates in basis points.
                    uint256 public maxDebtUpdateLoss;
                
                    /// @notice Max the chains base fee can be during debt update.
                    // Will default to max uint256 and need to be set to be used.
                    uint256 public maxAcceptableBaseFee;
                
                    /// @notice Mapping of addresses that are allowed to update debt.
                    mapping(address => bool) public keepers;
                
                    /// @notice Mapping of addresses that are allowed to update debt ratios.
                    mapping(address => bool) public managers;
                
                    mapping(address => VaultConfig) internal _vaultConfigs;
                
                    /// @notice Mapping of vault => strategy => its config.
                    mapping(address => mapping(address => StrategyConfig))
                        internal _strategyConfigs;
                
                    constructor() Governance(msg.sender) {}
                
                    /**
                     * @notice Initialize the contract after being cloned.
                     * @dev Sets default values for the global variables.
                     */
                    function initialize(address _governance) external {
                        require(governance == address(0), "initialized");
                        require(_governance != address(0), "ZERO ADDRESS");
                
                        governance = _governance;
                        emit GovernanceTransferred(address(0), _governance);
                
                        // Default max base fee to uint max.
                        maxAcceptableBaseFee = type(uint256).max;
                
                        // Default to allow 1 BP loss.
                        maxDebtUpdateLoss = 1;
                
                        // Default minimum wait to 6 hours
                        minimumWait = 60 * 60 * 6;
                
                        // Default to allow governance to be a keeper.
                        keepers[_governance] = true;
                        emit UpdateKeeper(_governance, true);
                    }
                
                    /**
                     * @notice Debt update wrapper for the vault.
                     * @dev This contract must have the DEBT_MANAGER role assigned to them.
                     *
                     *   This will also uses the `maxUpdateDebtLoss` during debt
                     *   updates to assure decreases did not realize profits outside
                     *   of the allowed range.
                     */
                    function update_debt(
                        address _vault,
                        address _strategy,
                        uint256 _targetDebt
                    ) public virtual onlyKeepers {
                        // If going to 0 record full balance first.
                        if (_targetDebt == 0) {
                            IVault(_vault).process_report(_strategy);
                        }
                
                        // Update debt with the default max loss.
                        IVault(_vault).update_debt(_strategy, _targetDebt, maxDebtUpdateLoss);
                
                        // Update the last time the strategies debt was updated.
                        _strategyConfigs[_vault][_strategy].lastUpdate = uint96(
                            block.timestamp
                        );
                    }
                
                    /**
                     * @notice Check if a strategy's debt should be updated.
                     * @dev This should be called by a keeper to decide if a strategies
                     * debt should be updated and if so by how much.
                     *
                     * @param _vault Address of the vault to update.
                     * @param _strategy Address of the strategy to check.
                     * @return . Bool representing if the debt should be updated.
                     * @return . Calldata if `true` or reason if `false`.
                     */
                    function shouldUpdateDebt(
                        address _vault,
                        address _strategy
                    ) public view virtual returns (bool, bytes memory) {
                        // Store all local variables in a struct to avoid stack to deep
                        StrategyDebtInfo memory strategyDebtInfo;
                
                        strategyDebtInfo.vaultConfig = getVaultConfig(_vault);
                
                        // Don't do anything if paused.
                        if (strategyDebtInfo.vaultConfig.paused) {
                            return (false, bytes("Paused"));
                        }
                
                        // Check the base fee isn't too high.
                        if (!isCurrentBaseFeeAcceptable()) return (false, bytes("Base Fee"));
                
                        // Get the strategy specific debt config.
                        strategyDebtInfo.strategyConfig = getStrategyConfig(_vault, _strategy);
                
                        // Make sure the strategy has been added to the allocator.
                        if (!strategyDebtInfo.strategyConfig.added) {
                            return (false, bytes("!added"));
                        }
                
                        if (
                            block.timestamp - strategyDebtInfo.strategyConfig.lastUpdate <=
                            minimumWait
                        ) {
                            return (false, bytes("min wait"));
                        }
                
                        // Retrieve the strategy specific parameters.
                        IVault.StrategyParams memory params = IVault(_vault).strategies(
                            _strategy
                        );
                        // Make sure its an active strategy.
                        require(params.activation != 0, "!active");
                
                        strategyDebtInfo.vaultAssets = IVault(_vault).totalAssets();
                
                        // Get the target debt for the strategy based on vault assets.
                        strategyDebtInfo.targetDebt = Math.min(
                            (strategyDebtInfo.vaultAssets *
                                strategyDebtInfo.strategyConfig.targetRatio) / MAX_BPS,
                            // Make sure it is not more than the max allowed.
                            params.max_debt
                        );
                
                        // Get the max debt we would want the strategy to have.
                        strategyDebtInfo.maxDebt = Math.min(
                            (strategyDebtInfo.vaultAssets *
                                strategyDebtInfo.strategyConfig.maxRatio) / MAX_BPS,
                            // Make sure it is not more than the max allowed.
                            params.max_debt
                        );
                
                        // If we need to add more.
                        if (strategyDebtInfo.targetDebt > params.current_debt) {
                            strategyDebtInfo.currentIdle = IVault(_vault).totalIdle();
                            strategyDebtInfo.minIdle = IVault(_vault).minimum_total_idle();
                
                            // We can't add more than the available idle.
                            if (strategyDebtInfo.minIdle >= strategyDebtInfo.currentIdle) {
                                return (false, bytes("No Idle"));
                            }
                
                            // Add up to the max if possible
                            strategyDebtInfo.toChange = Math.min(
                                strategyDebtInfo.maxDebt - params.current_debt,
                                // Can't take more than is available.
                                Math.min(
                                    strategyDebtInfo.currentIdle - strategyDebtInfo.minIdle,
                                    IVault(_strategy).maxDeposit(_vault)
                                )
                            );
                
                            // If the amount to add is over our threshold.
                            if (
                                strategyDebtInfo.toChange >
                                strategyDebtInfo.vaultConfig.minimumChange
                            ) {
                                // Return true and the calldata.
                                return (
                                    true,
                                    abi.encodeCall(
                                        this.update_debt,
                                        (
                                            _vault,
                                            _strategy,
                                            params.current_debt + strategyDebtInfo.toChange
                                        )
                                    )
                                );
                            }
                            // If current debt is greater than our max.
                        } else if (strategyDebtInfo.maxDebt < params.current_debt) {
                            strategyDebtInfo.toChange =
                                params.current_debt -
                                strategyDebtInfo.targetDebt;
                
                            strategyDebtInfo.currentIdle = IVault(_vault).totalIdle();
                            strategyDebtInfo.minIdle = IVault(_vault).minimum_total_idle();
                
                            if (strategyDebtInfo.minIdle > strategyDebtInfo.currentIdle) {
                                // Pull at least the amount needed for minIdle.
                                strategyDebtInfo.toChange = Math.max(
                                    strategyDebtInfo.toChange,
                                    strategyDebtInfo.minIdle - strategyDebtInfo.currentIdle
                                );
                            }
                
                            // Find out by how much. Aim for the target.
                            strategyDebtInfo.toChange = Math.min(
                                strategyDebtInfo.toChange,
                                // Account for the current liquidity constraints.
                                // Use max redeem to match vault logic.
                                IVault(_strategy).convertToAssets(
                                    IVault(_strategy).maxRedeem(_vault)
                                )
                            );
                
                            // Check if it's over the threshold.
                            if (
                                strategyDebtInfo.toChange >
                                strategyDebtInfo.vaultConfig.minimumChange
                            ) {
                                // Can't lower debt if there are unrealised losses.
                                if (
                                    IVault(_vault).assess_share_of_unrealised_losses(
                                        _strategy,
                                        params.current_debt
                                    ) != 0
                                ) {
                                    return (false, bytes("unrealised loss"));
                                }
                
                                // If so return true and the calldata.
                                return (
                                    true,
                                    abi.encodeCall(
                                        this.update_debt,
                                        (
                                            _vault,
                                            _strategy,
                                            params.current_debt - strategyDebtInfo.toChange
                                        )
                                    )
                                );
                            }
                        }
                
                        // Either no change or below our minimumChange.
                        return (false, bytes("Below Min"));
                    }
                
                    /*//////////////////////////////////////////////////////////////
                                        STRATEGY MANAGEMENT
                    //////////////////////////////////////////////////////////////*/
                
                    /**
                     * @notice Increase a strategies target debt ratio.
                     * @dev `setStrategyDebtRatio` functions will do all needed checks.
                     * @param _strategy The address of the strategy to increase the debt ratio for.
                     * @param _increase The amount in Basis Points to increase it.
                     */
                    function increaseStrategyDebtRatio(
                        address _vault,
                        address _strategy,
                        uint256 _increase
                    ) external virtual {
                        setStrategyDebtRatio(
                            _vault,
                            _strategy,
                            getStrategyTargetRatio(_vault, _strategy) + _increase
                        );
                    }
                
                    /**
                     * @notice Decrease a strategies target debt ratio.
                     * @param _strategy The address of the strategy to decrease the debt ratio for.
                     * @param _decrease The amount in Basis Points to decrease it.
                     */
                    function decreaseStrategyDebtRatio(
                        address _vault,
                        address _strategy,
                        uint256 _decrease
                    ) external virtual {
                        setStrategyDebtRatio(
                            _vault,
                            _strategy,
                            getStrategyTargetRatio(_vault, _strategy) - _decrease
                        );
                    }
                
                    /**
                     * @notice Sets a new target debt ratio for a strategy.
                     * @dev This will default to a 20% increase for max debt.
                     *
                     * @param _strategy Address of the strategy to set.
                     * @param _targetRatio Amount in Basis points to allocate.
                     */
                    function setStrategyDebtRatio(
                        address _vault,
                        address _strategy,
                        uint256 _targetRatio
                    ) public virtual {
                        uint256 maxRatio = Math.min((_targetRatio * 12_000) / MAX_BPS, MAX_BPS);
                        setStrategyDebtRatio(_vault, _strategy, _targetRatio, maxRatio);
                    }
                
                    /**
                     * @notice Sets a new target debt ratio for a strategy.
                     * @dev A `minimumChange` for that strategy must be set first.
                     * This is to prevent debt from being updated too frequently.
                     *
                     * @param _vault Address of the vault
                     * @param _strategy Address of the strategy to set.
                     * @param _targetRatio Amount in Basis points to allocate.
                     * @param _maxRatio Max ratio to give on debt increases.
                     */
                    function setStrategyDebtRatio(
                        address _vault,
                        address _strategy,
                        uint256 _targetRatio,
                        uint256 _maxRatio
                    ) public virtual onlyManagers {
                        VaultConfig memory vaultConfig = getVaultConfig(_vault);
                        // Make sure a minimumChange has been set.
                        require(vaultConfig.minimumChange != 0, "!minimum");
                        // Cannot be more than 100%.
                        require(_maxRatio <= MAX_BPS, "max too high");
                        // Max cannot be lower than the target.
                        require(_maxRatio >= _targetRatio, "max ratio");
                
                        // Get the current config.
                        StrategyConfig memory strategyConfig = getStrategyConfig(
                            _vault,
                            _strategy
                        );
                
                        // Set added flag if not set yet.
                        if (!strategyConfig.added) {
                            strategyConfig.added = true;
                            emit StrategyChanged(_vault, _strategy, Status.ADDED);
                        }
                
                        // Get what will be the new total debt ratio.
                        uint256 newTotalDebtRatio = vaultConfig.totalDebtRatio -
                            strategyConfig.targetRatio +
                            _targetRatio;
                
                        // Make sure it is under 100% allocated
                        require(newTotalDebtRatio <= MAX_BPS, "ratio too high");
                
                        // Update local config.
                        strategyConfig.targetRatio = uint16(_targetRatio);
                        strategyConfig.maxRatio = uint16(_maxRatio);
                
                        // Write to storage.
                        _strategyConfigs[_vault][_strategy] = strategyConfig;
                        _vaultConfigs[_vault].totalDebtRatio = uint16(newTotalDebtRatio);
                
                        emit UpdateStrategyDebtRatio(
                            _vault,
                            _strategy,
                            _targetRatio,
                            _maxRatio,
                            newTotalDebtRatio
                        );
                    }
                
                    /**
                     * @notice Remove a strategy from this debt allocator.
                     * @dev Will delete the full config for the strategy
                     * @param _vault Address of the vault
                     * @param _strategy Address of the address ro remove.
                     */
                    function removeStrategy(
                        address _vault,
                        address _strategy
                    ) external virtual onlyManagers {
                        StrategyConfig memory strategyConfig = getStrategyConfig(
                            _vault,
                            _strategy
                        );
                        require(strategyConfig.added, "!added");
                
                        uint256 target = strategyConfig.targetRatio;
                
                        // Remove any debt ratio the strategy holds.
                        if (target != 0) {
                            uint256 newRatio = _vaultConfigs[_vault].totalDebtRatio - target;
                            _vaultConfigs[_vault].totalDebtRatio = uint16(newRatio);
                            emit UpdateStrategyDebtRatio(_vault, _strategy, 0, 0, newRatio);
                        }
                
                        // Remove the full config including the `added` flag.
                        delete _strategyConfigs[_vault][_strategy];
                
                        // Emit Event.
                        emit StrategyChanged(_vault, _strategy, Status.REMOVED);
                    }
                
                    /*//////////////////////////////////////////////////////////////
                                        VAULT MANAGEMENT
                    //////////////////////////////////////////////////////////////*/
                
                    /**
                     * @notice Set the minimum change variable for a strategy.
                     * @dev This is the minimum amount of debt to be
                     * added or pulled for it to trigger an update.
                     *
                     * @param _vault Address of the vault
                     * @param _minimumChange The new minimum to set for the strategy.
                     */
                    function setMinimumChange(
                        address _vault,
                        uint256 _minimumChange
                    ) external virtual onlyGovernance {
                        require(_minimumChange > 0, "zero change");
                        // Make sure it fits in the slot size.
                        require(_minimumChange < type(uint128).max, "too high");
                
                        // Set the new minimum.
                        _vaultConfigs[_vault].minimumChange = uint128(_minimumChange);
                
                        emit UpdateMinimumChange(_vault, _minimumChange);
                    }
                
                    /**
                     * @notice Allows governance to pause the triggers.
                     * @param _vault Address of the vault
                     * @param _status Status to set the `paused` bool to.
                     */
                    function setPaused(
                        address _vault,
                        bool _status
                    ) external virtual onlyGovernance {
                        require(_status != _vaultConfigs[_vault].paused, "already set");
                        _vaultConfigs[_vault].paused = _status;
                
                        emit UpdatePaused(_vault, _status);
                    }
                
                    /*//////////////////////////////////////////////////////////////
                                        ALLOCATOR MANAGEMENT
                    //////////////////////////////////////////////////////////////*/
                
                    /**
                     * @notice Set the minimum time to wait before re-updating a strategies debt.
                     * @dev This is only enforced per strategy.
                     * @param _minimumWait The minimum time in seconds to wait.
                     */
                    function setMinimumWait(
                        uint256 _minimumWait
                    ) external virtual onlyGovernance {
                        minimumWait = _minimumWait;
                
                        emit UpdateMinimumWait(_minimumWait);
                    }
                
                    /**
                     * @notice Set if a manager can update ratios.
                     * @param _address The address to set mapping for.
                     * @param _allowed If the address can call {update_debt}.
                     */
                    function setManager(
                        address _address,
                        bool _allowed
                    ) external virtual onlyGovernance {
                        managers[_address] = _allowed;
                
                        emit UpdateManager(_address, _allowed);
                    }
                
                    /**
                     * @notice Set the max loss in Basis points to allow on debt updates.
                     * @dev Withdrawing during debt updates use {redeem} which allows for 100% loss.
                     *      This can be used to assure a loss is not realized on redeem outside the tolerance.
                     * @param _maxDebtUpdateLoss The max loss to accept on debt updates.
                     */
                    function setMaxDebtUpdateLoss(
                        uint256 _maxDebtUpdateLoss
                    ) external virtual onlyGovernance {
                        require(_maxDebtUpdateLoss <= MAX_BPS, "higher than max");
                        maxDebtUpdateLoss = _maxDebtUpdateLoss;
                
                        emit UpdateMaxDebtUpdateLoss(_maxDebtUpdateLoss);
                    }
                
                    /**
                     * @notice
                     *  Used to set our baseFeeProvider, which checks the network's current base
                     *  fee price to determine whether it is an optimal time to harvest or tend.
                     *
                     *  This may only be called by governance.
                     * @param _baseFeeProvider Address of our baseFeeProvider
                     */
                    function setBaseFeeProvider(
                        address _baseFeeProvider
                    ) external virtual onlyGovernance {
                        baseFeeProvider = _baseFeeProvider;
                
                        emit UpdatedBaseFeeProvider(_baseFeeProvider);
                    }
                
                    /**
                     * @notice Set the max acceptable base fee.
                     * @dev This defaults to max uint256 and will need to
                     * be set for it to be used.
                     *
                     * Is denominated in gwei. So 50gwei would be set as 50e9.
                     *
                     * @param _maxAcceptableBaseFee The new max base fee.
                     */
                    function setMaxAcceptableBaseFee(
                        uint256 _maxAcceptableBaseFee
                    ) external virtual onlyGovernance {
                        maxAcceptableBaseFee = _maxAcceptableBaseFee;
                
                        emit UpdateMaxAcceptableBaseFee(_maxAcceptableBaseFee);
                    }
                
                    /**
                     * @notice Set if a keeper can update debt.
                     * @param _address The address to set mapping for.
                     * @param _allowed If the address can call {update_debt}.
                     */
                    function setKeeper(
                        address _address,
                        bool _allowed
                    ) external virtual onlyGovernance {
                        keepers[_address] = _allowed;
                
                        emit UpdateKeeper(_address, _allowed);
                    }
                
                    /**
                     * @notice Get a strategies full config.
                     * @dev Used for customizations by inheriting the contract.
                     * @param _vault Address of the vault
                     * @param _strategy Address of the strategy.
                     * @return The strategies current Config.
                     */
                    function getStrategyConfig(
                        address _vault,
                        address _strategy
                    ) public view virtual returns (StrategyConfig memory) {
                        return _strategyConfigs[_vault][_strategy];
                    }
                
                    /**
                     * @notice Get a vaults full config.
                     * @dev Used for customizations by inheriting the contract.
                     * @param _vault Address of the vault.
                     * @return The vaults current Config.
                     */
                    function getVaultConfig(
                        address _vault
                    ) public view virtual returns (VaultConfig memory) {
                        return _vaultConfigs[_vault];
                    }
                
                    /**
                     * @notice Get a vaults current total debt.
                     * @param _vault Address of the vault
                     */
                    function totalDebtRatio(
                        address _vault
                    ) external view virtual returns (uint256) {
                        return getVaultConfig(_vault).totalDebtRatio;
                    }
                
                    /**
                     * @notice Get a vaults minimum change required.
                     * @param _vault Address of the vault
                     */
                    function minimumChange(
                        address _vault
                    ) external view virtual returns (uint256) {
                        return getVaultConfig(_vault).minimumChange;
                    }
                
                    /**
                     * @notice Get the paused status of a vault
                     * @param _vault Address of the vault
                     */
                    function isPaused(address _vault) public view virtual returns (bool) {
                        return getVaultConfig(_vault).paused;
                    }
                
                    /**
                     * @notice Get a strategies target debt ratio.
                     * @param _vault Address of the vault
                     * @param _strategy Address of the strategy.
                     * @return The strategies current targetRatio.
                     */
                    function getStrategyTargetRatio(
                        address _vault,
                        address _strategy
                    ) public view virtual returns (uint256) {
                        return getStrategyConfig(_vault, _strategy).targetRatio;
                    }
                
                    /**
                     * @notice Get a strategies max debt ratio.
                     * @param _vault Address of the vault
                     * @param _strategy Address of the strategy.
                     * @return The strategies current maxRatio.
                     */
                    function getStrategyMaxRatio(
                        address _vault,
                        address _strategy
                    ) public view virtual returns (uint256) {
                        return getStrategyConfig(_vault, _strategy).maxRatio;
                    }
                
                    /**
                     * @notice Returns wether or not the current base fee is acceptable
                     *   based on the `maxAcceptableBaseFee`.
                     * @return . If the current base fee is acceptable.
                     */
                    function isCurrentBaseFeeAcceptable() public view virtual returns (bool) {
                        address _baseFeeProvider = baseFeeProvider;
                        if (_baseFeeProvider == address(0)) return true;
                        return
                            maxAcceptableBaseFee >= IBaseFee(_baseFeeProvider).basefee_global();
                    }
                }

                File 6 of 7: DebtAllocator
                // SPDX-License-Identifier: GNU AGPLv3
                pragma solidity >=0.8.18 ^0.8.0;
                
                // lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol
                
                // 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);
                }
                
                // lib/openzeppelin-contracts/contracts/utils/math/Math.sol
                
                // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
                
                /**
                 * @dev Standard math utilities missing in the Solidity language.
                 */
                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);
                        }
                    }
                }
                
                // lib/tokenized-strategy-periphery/src/utils/Governance.sol
                
                contract Governance {
                    /// @notice Emitted when the governance address is updated.
                    event GovernanceTransferred(
                        address indexed previousGovernance,
                        address indexed newGovernance
                    );
                
                    modifier onlyGovernance() {
                        _checkGovernance();
                        _;
                    }
                
                    /// @notice Checks if the msg sender is the governance.
                    function _checkGovernance() internal view virtual {
                        require(governance == msg.sender, "!governance");
                    }
                
                    /// @notice Address that can set the default base fee and provider
                    address public governance;
                
                    constructor(address _governance) {
                        governance = _governance;
                
                        emit GovernanceTransferred(address(0), _governance);
                    }
                
                    /**
                     * @notice Sets a new address as the governance of the contract.
                     * @dev Throws if the caller is not current governance.
                     * @param _newGovernance The new governance address.
                     */
                    function transferGovernance(
                        address _newGovernance
                    ) external virtual onlyGovernance {
                        require(_newGovernance != address(0), "ZERO ADDRESS");
                        address oldGovernance = governance;
                        governance = _newGovernance;
                
                        emit GovernanceTransferred(oldGovernance, _newGovernance);
                    }
                }
                
                // lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol
                
                // 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);
                }
                
                // lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol
                
                // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol)
                
                /**
                 * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
                 * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
                 *
                 * _Available since v4.7._
                 */
                interface 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);
                }
                
                // lib/yearn-vaults-v3/contracts/interfaces/IVault.sol
                
                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 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 UpdateAutoAllocate(bool auto_allocate);
                    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 auto_allocate() external view returns (bool);
                
                    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 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 initialize(
                        address,
                        string memory,
                        string memory,
                        address,
                        uint256
                    ) external;
                
                    function setName(string memory) external;
                
                    function setSymbol(string memory) external;
                
                    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_auto_allocate(bool) external;
                
                    function set_deposit_limit(uint256 deposit_limit) external;
                
                    function set_deposit_limit(
                        uint256 deposit_limit,
                        bool should_override
                    ) external;
                
                    function set_deposit_limit_module(
                        address new_deposit_limit_module
                    ) external;
                
                    function set_deposit_limit_module(
                        address new_deposit_limit_module,
                        bool should_override
                    ) 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 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 update_debt(
                        address strategy,
                        uint256 target_debt,
                        uint256 max_loss
                    ) 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 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);
                }
                
                // src/debtAllocators/DebtAllocator.sol
                
                interface IBaseFee {
                    function basefee_global() external view returns (uint256);
                }
                
                /**
                 * @title YearnV3  Debt Allocator
                 * @author yearn.finance
                 * @notice
                 *  This Debt Allocator is meant to be used alongside
                 *  Yearn V3 vaults to provide the needed triggers for a keeper
                 *  to perform automated debt updates for the vaults strategies.
                 *
                 * @dev
                 *  Each vault that should be managed by this allocator will
                 *  need to be added by first setting a `minimumChange` for the
                 *  vault, which will act as the minimum amount of funds to move that will
                 *  trigger a debt update. Then adding each strategy by setting a
                 *  `targetRatio` and optionally a `maxRatio`.
                 *
                 *  The allocator aims to allocate debt between the strategies
                 *  based on their set target ratios. Which are denominated in basis
                 *  points and represent the percent of total assets that specific
                 *  strategy should hold (i.e 1_000 == 10% of the vaults `totalAssets`).
                 *
                 *  The trigger will attempt to allocate up to the `maxRatio` when
                 *  the strategy has `minimumChange` amount less than the `targetRatio`.
                 *  And will pull funds to the `targetRatio` when it has `minimumChange`
                 *  more than its `maxRatio`.
                 */
                contract DebtAllocator is Governance {
                    /// @notice An event emitted when the base fee provider is set.
                    event UpdatedBaseFeeProvider(address baseFeeProvider);
                
                    /// @notice An event emitted when a keeper is added or removed.
                    event UpdateKeeper(address indexed keeper, bool allowed);
                
                    /// @notice An event emitted when the max base fee is updated.
                    event UpdateMaxAcceptableBaseFee(uint256 newMaxAcceptableBaseFee);
                
                    /// @notice An event emitted when a strategies debt ratios are Updated.
                    event UpdateStrategyDebtRatio(
                        address indexed vault,
                        address indexed strategy,
                        uint256 newTargetRatio,
                        uint256 newMaxRatio,
                        uint256 newTotalDebtRatio
                    );
                
                    /// @notice An event emitted when a strategy is added or removed.
                    event StrategyChanged(
                        address indexed vault,
                        address indexed strategy,
                        Status status
                    );
                
                    /// @notice An event emitted when the minimum change is updated.
                    event UpdateMinimumChange(address indexed vault, uint256 newMinimumChange);
                
                    /// @notice An even emitted when the paused status is updated.
                    event UpdatePaused(address indexed vault, bool indexed status);
                
                    /// @notice An event emitted when the minimum time to wait is updated.
                    event UpdateMinimumWait(uint256 newMinimumWait);
                
                    /// @notice An event emitted when a keeper is added or removed.
                    event UpdateManager(address indexed manager, bool allowed);
                
                    /// @notice An event emitted when the max debt update loss is updated.
                    event UpdateMaxDebtUpdateLoss(uint256 newMaxDebtUpdateLoss);
                
                    /// @notice Status when a strategy is added or removed from the allocator.
                    enum Status {
                        NULL,
                        ADDED,
                        REMOVED
                    }
                
                    /// @notice Struct for each strategies info.
                    struct StrategyConfig {
                        // Flag to set when a strategy is added.
                        bool added;
                        // The ideal percent in Basis Points the strategy should have.
                        uint16 targetRatio;
                        // The max percent of assets the strategy should hold.
                        uint16 maxRatio;
                        // Timestamp of the last time debt was updated.
                        // The debt updates must be done through this allocator
                        // for this to be used.
                        uint96 lastUpdate;
                        // We have an extra 120 bits in the slot.
                        // So we declare the variable in the struct so it can be
                        // used if this contract is inherited.
                        uint120 open;
                    }
                
                    /// @notice Struct to hold the vault's info.
                    struct VaultConfig {
                        // Optional flag to stop the triggers.
                        bool paused;
                        // The minimum amount denominated in asset that will
                        // need to be moved to trigger a debt update.
                        uint128 minimumChange;
                        // Total debt ratio currently allocated in basis points.
                        // Can't be more than 10_000.
                        uint16 totalDebtRatio;
                    }
                
                    /// @notice Used during the `shouldUpdateDebt` to hold the data.
                    struct StrategyDebtInfo {
                        VaultConfig vaultConfig;
                        StrategyConfig strategyConfig;
                        uint256 vaultAssets;
                        uint256 targetDebt;
                        uint256 maxDebt;
                        uint256 currentIdle;
                        uint256 minIdle;
                        uint256 toChange;
                    }
                
                    /// @notice Make sure the caller is governance or a manager.
                    modifier onlyManagers() {
                        _isManager();
                        _;
                    }
                
                    /// @notice Make sure the caller is a keeper
                    modifier onlyKeepers() {
                        _isKeeper();
                        _;
                    }
                
                    /// @notice Check is either factories governance or local manager.
                    function _isManager() internal view virtual {
                        require(managers[msg.sender] || msg.sender == governance, "!manager");
                    }
                
                    /// @notice Check is one of the allowed keepers.
                    function _isKeeper() internal view virtual {
                        require(keepers[msg.sender], "!keeper");
                    }
                
                    uint256 internal constant MAX_BPS = 10_000;
                
                    /// @notice Time to wait between debt updates in seconds.
                    uint256 public minimumWait;
                
                    /// @notice Provider to read current block's base fee.
                    address public baseFeeProvider;
                
                    /// @notice Max loss to accept on debt updates in basis points.
                    uint256 public maxDebtUpdateLoss;
                
                    /// @notice Max the chains base fee can be during debt update.
                    // Will default to max uint256 and need to be set to be used.
                    uint256 public maxAcceptableBaseFee;
                
                    /// @notice Mapping of addresses that are allowed to update debt.
                    mapping(address => bool) public keepers;
                
                    /// @notice Mapping of addresses that are allowed to update debt ratios.
                    mapping(address => bool) public managers;
                
                    mapping(address => VaultConfig) internal _vaultConfigs;
                
                    /// @notice Mapping of vault => strategy => its config.
                    mapping(address => mapping(address => StrategyConfig))
                        internal _strategyConfigs;
                
                    constructor() Governance(msg.sender) {}
                
                    /**
                     * @notice Initialize the contract after being cloned.
                     * @dev Sets default values for the global variables.
                     */
                    function initialize(address _governance) external {
                        require(governance == address(0), "initialized");
                        require(_governance != address(0), "ZERO ADDRESS");
                
                        governance = _governance;
                        emit GovernanceTransferred(address(0), _governance);
                
                        // Default max base fee to uint max.
                        maxAcceptableBaseFee = type(uint256).max;
                
                        // Default to allow 1 BP loss.
                        maxDebtUpdateLoss = 1;
                
                        // Default minimum wait to 6 hours
                        minimumWait = 60 * 60 * 6;
                
                        // Default to allow governance to be a keeper.
                        keepers[_governance] = true;
                        emit UpdateKeeper(_governance, true);
                    }
                
                    /**
                     * @notice Debt update wrapper for the vault.
                     * @dev This contract must have the DEBT_MANAGER role assigned to them.
                     *
                     *   This will also uses the `maxUpdateDebtLoss` during debt
                     *   updates to assure decreases did not realize profits outside
                     *   of the allowed range.
                     */
                    function update_debt(
                        address _vault,
                        address _strategy,
                        uint256 _targetDebt
                    ) public virtual onlyKeepers {
                        // If going to 0 record full balance first.
                        if (_targetDebt == 0) {
                            IVault(_vault).process_report(_strategy);
                        }
                
                        // Update debt with the default max loss.
                        IVault(_vault).update_debt(_strategy, _targetDebt, maxDebtUpdateLoss);
                
                        // Update the last time the strategies debt was updated.
                        _strategyConfigs[_vault][_strategy].lastUpdate = uint96(
                            block.timestamp
                        );
                    }
                
                    /**
                     * @notice Check if a strategy's debt should be updated.
                     * @dev This should be called by a keeper to decide if a strategies
                     * debt should be updated and if so by how much.
                     *
                     * @param _vault Address of the vault to update.
                     * @param _strategy Address of the strategy to check.
                     * @return . Bool representing if the debt should be updated.
                     * @return . Calldata if `true` or reason if `false`.
                     */
                    function shouldUpdateDebt(
                        address _vault,
                        address _strategy
                    ) public view virtual returns (bool, bytes memory) {
                        // Store all local variables in a struct to avoid stack to deep
                        StrategyDebtInfo memory strategyDebtInfo;
                
                        strategyDebtInfo.vaultConfig = getVaultConfig(_vault);
                
                        // Don't do anything if paused.
                        if (strategyDebtInfo.vaultConfig.paused) {
                            return (false, bytes("Paused"));
                        }
                
                        // Check the base fee isn't too high.
                        if (!isCurrentBaseFeeAcceptable()) return (false, bytes("Base Fee"));
                
                        // Get the strategy specific debt config.
                        strategyDebtInfo.strategyConfig = getStrategyConfig(_vault, _strategy);
                
                        // Make sure the strategy has been added to the allocator.
                        if (!strategyDebtInfo.strategyConfig.added) {
                            return (false, bytes("!added"));
                        }
                
                        if (
                            block.timestamp - strategyDebtInfo.strategyConfig.lastUpdate <=
                            minimumWait
                        ) {
                            return (false, bytes("min wait"));
                        }
                
                        // Retrieve the strategy specific parameters.
                        IVault.StrategyParams memory params = IVault(_vault).strategies(
                            _strategy
                        );
                        // Make sure its an active strategy.
                        require(params.activation != 0, "!active");
                
                        strategyDebtInfo.vaultAssets = IVault(_vault).totalAssets();
                
                        // Get the target debt for the strategy based on vault assets.
                        strategyDebtInfo.targetDebt = Math.min(
                            (strategyDebtInfo.vaultAssets *
                                strategyDebtInfo.strategyConfig.targetRatio) / MAX_BPS,
                            // Make sure it is not more than the max allowed.
                            params.max_debt
                        );
                
                        // Get the max debt we would want the strategy to have.
                        strategyDebtInfo.maxDebt = Math.min(
                            (strategyDebtInfo.vaultAssets *
                                strategyDebtInfo.strategyConfig.maxRatio) / MAX_BPS,
                            // Make sure it is not more than the max allowed.
                            params.max_debt
                        );
                
                        // If we need to add more.
                        if (strategyDebtInfo.targetDebt > params.current_debt) {
                            strategyDebtInfo.currentIdle = IVault(_vault).totalIdle();
                            strategyDebtInfo.minIdle = IVault(_vault).minimum_total_idle();
                
                            // We can't add more than the available idle.
                            if (strategyDebtInfo.minIdle >= strategyDebtInfo.currentIdle) {
                                return (false, bytes("No Idle"));
                            }
                
                            // Add up to the max if possible
                            strategyDebtInfo.toChange = Math.min(
                                strategyDebtInfo.maxDebt - params.current_debt,
                                // Can't take more than is available.
                                Math.min(
                                    strategyDebtInfo.currentIdle - strategyDebtInfo.minIdle,
                                    IVault(_strategy).maxDeposit(_vault)
                                )
                            );
                
                            // If the amount to add is over our threshold.
                            if (
                                strategyDebtInfo.toChange >
                                strategyDebtInfo.vaultConfig.minimumChange
                            ) {
                                // Return true and the calldata.
                                return (
                                    true,
                                    abi.encodeCall(
                                        this.update_debt,
                                        (
                                            _vault,
                                            _strategy,
                                            params.current_debt + strategyDebtInfo.toChange
                                        )
                                    )
                                );
                            }
                            // If current debt is greater than our max.
                        } else if (strategyDebtInfo.maxDebt < params.current_debt) {
                            strategyDebtInfo.toChange =
                                params.current_debt -
                                strategyDebtInfo.targetDebt;
                
                            strategyDebtInfo.currentIdle = IVault(_vault).totalIdle();
                            strategyDebtInfo.minIdle = IVault(_vault).minimum_total_idle();
                
                            if (strategyDebtInfo.minIdle > strategyDebtInfo.currentIdle) {
                                // Pull at least the amount needed for minIdle.
                                strategyDebtInfo.toChange = Math.max(
                                    strategyDebtInfo.toChange,
                                    strategyDebtInfo.minIdle - strategyDebtInfo.currentIdle
                                );
                            }
                
                            // Find out by how much. Aim for the target.
                            strategyDebtInfo.toChange = Math.min(
                                strategyDebtInfo.toChange,
                                // Account for the current liquidity constraints.
                                // Use max redeem to match vault logic.
                                IVault(_strategy).convertToAssets(
                                    IVault(_strategy).maxRedeem(_vault)
                                )
                            );
                
                            // Check if it's over the threshold.
                            if (
                                strategyDebtInfo.toChange >
                                strategyDebtInfo.vaultConfig.minimumChange
                            ) {
                                // Can't lower debt if there are unrealised losses.
                                if (
                                    IVault(_vault).assess_share_of_unrealised_losses(
                                        _strategy,
                                        params.current_debt
                                    ) != 0
                                ) {
                                    return (false, bytes("unrealised loss"));
                                }
                
                                // If so return true and the calldata.
                                return (
                                    true,
                                    abi.encodeCall(
                                        this.update_debt,
                                        (
                                            _vault,
                                            _strategy,
                                            params.current_debt - strategyDebtInfo.toChange
                                        )
                                    )
                                );
                            }
                        }
                
                        // Either no change or below our minimumChange.
                        return (false, bytes("Below Min"));
                    }
                
                    /*//////////////////////////////////////////////////////////////
                                        STRATEGY MANAGEMENT
                    //////////////////////////////////////////////////////////////*/
                
                    /**
                     * @notice Increase a strategies target debt ratio.
                     * @dev `setStrategyDebtRatio` functions will do all needed checks.
                     * @param _strategy The address of the strategy to increase the debt ratio for.
                     * @param _increase The amount in Basis Points to increase it.
                     */
                    function increaseStrategyDebtRatio(
                        address _vault,
                        address _strategy,
                        uint256 _increase
                    ) external virtual {
                        setStrategyDebtRatio(
                            _vault,
                            _strategy,
                            getStrategyTargetRatio(_vault, _strategy) + _increase
                        );
                    }
                
                    /**
                     * @notice Decrease a strategies target debt ratio.
                     * @param _strategy The address of the strategy to decrease the debt ratio for.
                     * @param _decrease The amount in Basis Points to decrease it.
                     */
                    function decreaseStrategyDebtRatio(
                        address _vault,
                        address _strategy,
                        uint256 _decrease
                    ) external virtual {
                        setStrategyDebtRatio(
                            _vault,
                            _strategy,
                            getStrategyTargetRatio(_vault, _strategy) - _decrease
                        );
                    }
                
                    /**
                     * @notice Sets a new target debt ratio for a strategy.
                     * @dev This will default to a 20% increase for max debt.
                     *
                     * @param _strategy Address of the strategy to set.
                     * @param _targetRatio Amount in Basis points to allocate.
                     */
                    function setStrategyDebtRatio(
                        address _vault,
                        address _strategy,
                        uint256 _targetRatio
                    ) public virtual {
                        uint256 maxRatio = Math.min((_targetRatio * 12_000) / MAX_BPS, MAX_BPS);
                        setStrategyDebtRatio(_vault, _strategy, _targetRatio, maxRatio);
                    }
                
                    /**
                     * @notice Sets a new target debt ratio for a strategy.
                     * @dev A `minimumChange` for that strategy must be set first.
                     * This is to prevent debt from being updated too frequently.
                     *
                     * @param _vault Address of the vault
                     * @param _strategy Address of the strategy to set.
                     * @param _targetRatio Amount in Basis points to allocate.
                     * @param _maxRatio Max ratio to give on debt increases.
                     */
                    function setStrategyDebtRatio(
                        address _vault,
                        address _strategy,
                        uint256 _targetRatio,
                        uint256 _maxRatio
                    ) public virtual onlyManagers {
                        VaultConfig memory vaultConfig = getVaultConfig(_vault);
                        // Make sure a minimumChange has been set.
                        require(vaultConfig.minimumChange != 0, "!minimum");
                        // Cannot be more than 100%.
                        require(_maxRatio <= MAX_BPS, "max too high");
                        // Max cannot be lower than the target.
                        require(_maxRatio >= _targetRatio, "max ratio");
                
                        // Get the current config.
                        StrategyConfig memory strategyConfig = getStrategyConfig(
                            _vault,
                            _strategy
                        );
                
                        // Set added flag if not set yet.
                        if (!strategyConfig.added) {
                            strategyConfig.added = true;
                            emit StrategyChanged(_vault, _strategy, Status.ADDED);
                        }
                
                        // Get what will be the new total debt ratio.
                        uint256 newTotalDebtRatio = vaultConfig.totalDebtRatio -
                            strategyConfig.targetRatio +
                            _targetRatio;
                
                        // Make sure it is under 100% allocated
                        require(newTotalDebtRatio <= MAX_BPS, "ratio too high");
                
                        // Update local config.
                        strategyConfig.targetRatio = uint16(_targetRatio);
                        strategyConfig.maxRatio = uint16(_maxRatio);
                
                        // Write to storage.
                        _strategyConfigs[_vault][_strategy] = strategyConfig;
                        _vaultConfigs[_vault].totalDebtRatio = uint16(newTotalDebtRatio);
                
                        emit UpdateStrategyDebtRatio(
                            _vault,
                            _strategy,
                            _targetRatio,
                            _maxRatio,
                            newTotalDebtRatio
                        );
                    }
                
                    /**
                     * @notice Remove a strategy from this debt allocator.
                     * @dev Will delete the full config for the strategy
                     * @param _vault Address of the vault
                     * @param _strategy Address of the address ro remove.
                     */
                    function removeStrategy(
                        address _vault,
                        address _strategy
                    ) external virtual onlyManagers {
                        StrategyConfig memory strategyConfig = getStrategyConfig(
                            _vault,
                            _strategy
                        );
                        require(strategyConfig.added, "!added");
                
                        uint256 target = strategyConfig.targetRatio;
                
                        // Remove any debt ratio the strategy holds.
                        if (target != 0) {
                            uint256 newRatio = _vaultConfigs[_vault].totalDebtRatio - target;
                            _vaultConfigs[_vault].totalDebtRatio = uint16(newRatio);
                            emit UpdateStrategyDebtRatio(_vault, _strategy, 0, 0, newRatio);
                        }
                
                        // Remove the full config including the `added` flag.
                        delete _strategyConfigs[_vault][_strategy];
                
                        // Emit Event.
                        emit StrategyChanged(_vault, _strategy, Status.REMOVED);
                    }
                
                    /*//////////////////////////////////////////////////////////////
                                        VAULT MANAGEMENT
                    //////////////////////////////////////////////////////////////*/
                
                    /**
                     * @notice Set the minimum change variable for a strategy.
                     * @dev This is the minimum amount of debt to be
                     * added or pulled for it to trigger an update.
                     *
                     * @param _vault Address of the vault
                     * @param _minimumChange The new minimum to set for the strategy.
                     */
                    function setMinimumChange(
                        address _vault,
                        uint256 _minimumChange
                    ) external virtual onlyGovernance {
                        require(_minimumChange > 0, "zero change");
                        // Make sure it fits in the slot size.
                        require(_minimumChange < type(uint128).max, "too high");
                
                        // Set the new minimum.
                        _vaultConfigs[_vault].minimumChange = uint128(_minimumChange);
                
                        emit UpdateMinimumChange(_vault, _minimumChange);
                    }
                
                    /**
                     * @notice Allows governance to pause the triggers.
                     * @param _vault Address of the vault
                     * @param _status Status to set the `paused` bool to.
                     */
                    function setPaused(
                        address _vault,
                        bool _status
                    ) external virtual onlyGovernance {
                        require(_status != _vaultConfigs[_vault].paused, "already set");
                        _vaultConfigs[_vault].paused = _status;
                
                        emit UpdatePaused(_vault, _status);
                    }
                
                    /*//////////////////////////////////////////////////////////////
                                        ALLOCATOR MANAGEMENT
                    //////////////////////////////////////////////////////////////*/
                
                    /**
                     * @notice Set the minimum time to wait before re-updating a strategies debt.
                     * @dev This is only enforced per strategy.
                     * @param _minimumWait The minimum time in seconds to wait.
                     */
                    function setMinimumWait(
                        uint256 _minimumWait
                    ) external virtual onlyGovernance {
                        minimumWait = _minimumWait;
                
                        emit UpdateMinimumWait(_minimumWait);
                    }
                
                    /**
                     * @notice Set if a manager can update ratios.
                     * @param _address The address to set mapping for.
                     * @param _allowed If the address can call {update_debt}.
                     */
                    function setManager(
                        address _address,
                        bool _allowed
                    ) external virtual onlyGovernance {
                        managers[_address] = _allowed;
                
                        emit UpdateManager(_address, _allowed);
                    }
                
                    /**
                     * @notice Set the max loss in Basis points to allow on debt updates.
                     * @dev Withdrawing during debt updates use {redeem} which allows for 100% loss.
                     *      This can be used to assure a loss is not realized on redeem outside the tolerance.
                     * @param _maxDebtUpdateLoss The max loss to accept on debt updates.
                     */
                    function setMaxDebtUpdateLoss(
                        uint256 _maxDebtUpdateLoss
                    ) external virtual onlyGovernance {
                        require(_maxDebtUpdateLoss <= MAX_BPS, "higher than max");
                        maxDebtUpdateLoss = _maxDebtUpdateLoss;
                
                        emit UpdateMaxDebtUpdateLoss(_maxDebtUpdateLoss);
                    }
                
                    /**
                     * @notice
                     *  Used to set our baseFeeProvider, which checks the network's current base
                     *  fee price to determine whether it is an optimal time to harvest or tend.
                     *
                     *  This may only be called by governance.
                     * @param _baseFeeProvider Address of our baseFeeProvider
                     */
                    function setBaseFeeProvider(
                        address _baseFeeProvider
                    ) external virtual onlyGovernance {
                        baseFeeProvider = _baseFeeProvider;
                
                        emit UpdatedBaseFeeProvider(_baseFeeProvider);
                    }
                
                    /**
                     * @notice Set the max acceptable base fee.
                     * @dev This defaults to max uint256 and will need to
                     * be set for it to be used.
                     *
                     * Is denominated in gwei. So 50gwei would be set as 50e9.
                     *
                     * @param _maxAcceptableBaseFee The new max base fee.
                     */
                    function setMaxAcceptableBaseFee(
                        uint256 _maxAcceptableBaseFee
                    ) external virtual onlyGovernance {
                        maxAcceptableBaseFee = _maxAcceptableBaseFee;
                
                        emit UpdateMaxAcceptableBaseFee(_maxAcceptableBaseFee);
                    }
                
                    /**
                     * @notice Set if a keeper can update debt.
                     * @param _address The address to set mapping for.
                     * @param _allowed If the address can call {update_debt}.
                     */
                    function setKeeper(
                        address _address,
                        bool _allowed
                    ) external virtual onlyGovernance {
                        keepers[_address] = _allowed;
                
                        emit UpdateKeeper(_address, _allowed);
                    }
                
                    /**
                     * @notice Get a strategies full config.
                     * @dev Used for customizations by inheriting the contract.
                     * @param _vault Address of the vault
                     * @param _strategy Address of the strategy.
                     * @return The strategies current Config.
                     */
                    function getStrategyConfig(
                        address _vault,
                        address _strategy
                    ) public view virtual returns (StrategyConfig memory) {
                        return _strategyConfigs[_vault][_strategy];
                    }
                
                    /**
                     * @notice Get a vaults full config.
                     * @dev Used for customizations by inheriting the contract.
                     * @param _vault Address of the vault.
                     * @return The vaults current Config.
                     */
                    function getVaultConfig(
                        address _vault
                    ) public view virtual returns (VaultConfig memory) {
                        return _vaultConfigs[_vault];
                    }
                
                    /**
                     * @notice Get a vaults current total debt.
                     * @param _vault Address of the vault
                     */
                    function totalDebtRatio(
                        address _vault
                    ) external view virtual returns (uint256) {
                        return getVaultConfig(_vault).totalDebtRatio;
                    }
                
                    /**
                     * @notice Get a vaults minimum change required.
                     * @param _vault Address of the vault
                     */
                    function minimumChange(
                        address _vault
                    ) external view virtual returns (uint256) {
                        return getVaultConfig(_vault).minimumChange;
                    }
                
                    /**
                     * @notice Get the paused status of a vault
                     * @param _vault Address of the vault
                     */
                    function isPaused(address _vault) public view virtual returns (bool) {
                        return getVaultConfig(_vault).paused;
                    }
                
                    /**
                     * @notice Get a strategies target debt ratio.
                     * @param _vault Address of the vault
                     * @param _strategy Address of the strategy.
                     * @return The strategies current targetRatio.
                     */
                    function getStrategyTargetRatio(
                        address _vault,
                        address _strategy
                    ) public view virtual returns (uint256) {
                        return getStrategyConfig(_vault, _strategy).targetRatio;
                    }
                
                    /**
                     * @notice Get a strategies max debt ratio.
                     * @param _vault Address of the vault
                     * @param _strategy Address of the strategy.
                     * @return The strategies current maxRatio.
                     */
                    function getStrategyMaxRatio(
                        address _vault,
                        address _strategy
                    ) public view virtual returns (uint256) {
                        return getStrategyConfig(_vault, _strategy).maxRatio;
                    }
                
                    /**
                     * @notice Returns wether or not the current base fee is acceptable
                     *   based on the `maxAcceptableBaseFee`.
                     * @return . If the current base fee is acceptable.
                     */
                    function isCurrentBaseFeeAcceptable() public view virtual returns (bool) {
                        address _baseFeeProvider = baseFeeProvider;
                        if (_baseFeeProvider == address(0)) return true;
                        return
                            maxAcceptableBaseFee >= IBaseFee(_baseFeeProvider).basefee_global();
                    }
                }

                File 7 of 7: Yearn V3 Vault
                # @version 0.3.7
                
                """
                @title Yearn V3 Vault
                @license GNU AGPLv3
                @author yearn.finance
                @notice
                    The Yearn VaultV3 is designed as a non-opinionated system to distribute funds of 
                    depositors for a specific `asset` into different opportunities (aka Strategies)
                    and manage accounting in a robust way.
                
                    Depositors receive shares (aka vaults tokens) proportional to their deposit amount. 
                    Vault tokens are yield-bearing and can be redeemed at any time to get back deposit 
                    plus any yield generated.
                
                    Addresses that are given different permissioned roles by the `role_manager` 
                    are then able to allocate funds as they best see fit to different strategies 
                    and adjust the strategies and allocations as needed, as well as reporting realized
                    profits or losses.
                
                    Strategies are any ERC-4626 compliant contracts that use the same underlying `asset` 
                    as the vault. The vault provides no assurances as to the safety of any strategy
                    and it is the responsibility of those that hold the corresponding roles to choose
                    and fund strategies that best fit their desired specifications.
                
                    Those holding vault tokens are able to redeem the tokens for the corresponding
                    amount of underlying asset based on any reported profits or losses since their
                    initial deposit.
                
                    The vault is built to be customized by the management to be able to fit their
                    specific desired needs. Including the customization of strategies, accountants, 
                    ownership etc.
                """
                
                # INTERFACES #
                
                from vyper.interfaces import ERC20
                from vyper.interfaces import ERC20Detailed
                
                interface IStrategy:
                    def asset() -> address: view
                    def balanceOf(owner: address) -> uint256: view
                    def convertToAssets(shares: uint256) -> uint256: view
                    def convertToShares(assets: uint256) -> uint256: view
                    def previewWithdraw(assets: uint256) -> uint256: view
                    def maxDeposit(receiver: address) -> uint256: view
                    def deposit(assets: uint256, receiver: address) -> uint256: nonpayable
                    def maxRedeem(owner: address) -> uint256: view
                    def redeem(shares: uint256, receiver: address, owner: address) -> uint256: nonpayable
                    
                interface IAccountant:
                    def report(strategy: address, gain: uint256, loss: uint256) -> (uint256, uint256): nonpayable
                
                interface IDepositLimitModule:
                    def available_deposit_limit(receiver: address) -> uint256: view
                    
                interface IWithdrawLimitModule:
                    def available_withdraw_limit(owner: address, max_loss: uint256, strategies: DynArray[address, MAX_QUEUE]) -> uint256: view
                
                interface IFactory:
                    def protocol_fee_config() -> (uint16, address): view
                
                # EVENTS #
                # ERC4626 EVENTS
                event Deposit:
                    sender: indexed(address)
                    owner: indexed(address)
                    assets: uint256
                    shares: uint256
                
                event Withdraw:
                    sender: indexed(address)
                    receiver: indexed(address)
                    owner: indexed(address)
                    assets: uint256
                    shares: uint256
                
                # ERC20 EVENTS
                event Transfer:
                    sender: indexed(address)
                    receiver: indexed(address)
                    value: uint256
                
                event Approval:
                    owner: indexed(address)
                    spender: indexed(address)
                    value: uint256
                
                # STRATEGY EVENTS
                event StrategyChanged:
                    strategy: indexed(address)
                    change_type: indexed(StrategyChangeType)
                    
                event StrategyReported:
                    strategy: indexed(address)
                    gain: uint256
                    loss: uint256
                    current_debt: uint256
                    protocol_fees: uint256
                    total_fees: uint256
                    total_refunds: uint256
                
                # DEBT MANAGEMENT EVENTS
                event DebtUpdated:
                    strategy: indexed(address)
                    current_debt: uint256
                    new_debt: uint256
                
                # ROLE UPDATES
                event RoleSet:
                    account: indexed(address)
                    role: indexed(Roles)
                
                # STORAGE MANAGEMENT EVENTS
                event UpdateRoleManager:
                    role_manager: indexed(address)
                
                event UpdateAccountant:
                    accountant: indexed(address)
                
                event UpdateDepositLimitModule:
                    deposit_limit_module: indexed(address)
                
                event UpdateWithdrawLimitModule:
                    withdraw_limit_module: indexed(address)
                
                event UpdateDefaultQueue:
                    new_default_queue: DynArray[address, MAX_QUEUE]
                
                event UpdateUseDefaultQueue:
                    use_default_queue: bool
                
                event UpdatedMaxDebtForStrategy:
                    sender: indexed(address)
                    strategy: indexed(address)
                    new_debt: uint256
                
                event UpdateDepositLimit:
                    deposit_limit: uint256
                
                event UpdateMinimumTotalIdle:
                    minimum_total_idle: uint256
                
                event UpdateProfitMaxUnlockTime:
                    profit_max_unlock_time: uint256
                
                event DebtPurchased:
                    strategy: indexed(address)
                    amount: uint256
                
                event Shutdown:
                    pass
                
                # STRUCTS #
                struct StrategyParams:
                    # Timestamp when the strategy was added.
                    activation: uint256 
                    # Timestamp of the strategies last report.
                    last_report: uint256
                    # The current assets the strategy holds.
                    current_debt: uint256
                    # The max assets the strategy can hold. 
                    max_debt: uint256
                
                # CONSTANTS #
                # The max length the withdrawal queue can be.
                MAX_QUEUE: constant(uint256) = 10
                # 100% in Basis Points.
                MAX_BPS: constant(uint256) = 10_000
                # Extended for profit locking calculations.
                MAX_BPS_EXTENDED: constant(uint256) = 1_000_000_000_000
                # The version of this vault.
                API_VERSION: constant(String[28]) = "3.0.2"
                
                # ENUMS #
                # Each permissioned function has its own Role.
                # Roles can be combined in any combination or all kept separate.
                # Follows python Enum patterns so the first Enum == 1 and doubles each time.
                enum Roles:
                    ADD_STRATEGY_MANAGER # Can add strategies to the vault.
                    REVOKE_STRATEGY_MANAGER # Can remove strategies from the vault.
                    FORCE_REVOKE_MANAGER # Can force remove a strategy causing a loss.
                    ACCOUNTANT_MANAGER # Can set the accountant that assess fees.
                    QUEUE_MANAGER # Can set the default withdrawal queue.
                    REPORTING_MANAGER # Calls report for strategies.
                    DEBT_MANAGER # Adds and removes debt from strategies.
                    MAX_DEBT_MANAGER # Can set the max debt for a strategy.
                    DEPOSIT_LIMIT_MANAGER # Sets deposit limit and module for the vault.
                    WITHDRAW_LIMIT_MANAGER # Sets the withdraw limit module.
                    MINIMUM_IDLE_MANAGER # Sets the minimum total idle the vault should keep.
                    PROFIT_UNLOCK_MANAGER # Sets the profit_max_unlock_time.
                    DEBT_PURCHASER # Can purchase bad debt from the vault.
                    EMERGENCY_MANAGER # Can shutdown vault in an emergency.
                
                enum StrategyChangeType:
                    ADDED
                    REVOKED
                
                enum Rounding:
                    ROUND_DOWN
                    ROUND_UP
                
                # STORAGE #
                # Underlying token used by the vault.
                asset: public(address)
                # Based off the `asset` decimals.
                decimals: public(uint8)
                # Deployer contract used to retrieve the protocol fee config.
                factory: address
                
                # HashMap that records all the strategies that are allowed to receive assets from the vault.
                strategies: public(HashMap[address, StrategyParams])
                # The current default withdrawal queue.
                default_queue: public(DynArray[address, MAX_QUEUE])
                # Should the vault use the default_queue regardless whats passed in.
                use_default_queue: public(bool)
                
                ### ACCOUNTING ###
                # ERC20 - amount of shares per account
                balance_of: HashMap[address, uint256]
                # ERC20 - owner -> (spender -> amount)
                allowance: public(HashMap[address, HashMap[address, uint256]])
                # Total amount of shares that are currently minted including those locked.
                total_supply: uint256
                # Total amount of assets that has been deposited in strategies.
                total_debt: uint256
                # Current assets held in the vault contract. Replacing balanceOf(this) to avoid price_per_share manipulation.
                total_idle: uint256
                # Minimum amount of assets that should be kept in the vault contract to allow for fast, cheap redeems.
                minimum_total_idle: public(uint256)
                # Maximum amount of tokens that the vault can accept. If totalAssets > deposit_limit, deposits will revert.
                deposit_limit: public(uint256)
                
                ### PERIPHERY ###
                # Contract that charges fees and can give refunds.
                accountant: public(address)
                # Contract to control the deposit limit.
                deposit_limit_module: public(address)
                # Contract to control the withdraw limit.
                withdraw_limit_module: public(address)
                
                ### ROLES ###
                # HashMap mapping addresses to their roles
                roles: public(HashMap[address, Roles])
                # Address that can add and remove roles to addresses.
                role_manager: public(address)
                # Temporary variable to store the address of the next role_manager until the role is accepted.
                future_role_manager: public(address)
                
                # ERC20 - name of the vaults token.
                name: public(String[64])
                # ERC20 - symbol of the vaults token.
                symbol: public(String[32])
                
                # State of the vault - if set to true, only withdrawals will be available. It can't be reverted.
                shutdown: bool
                # The amount of time profits will unlock over.
                profit_max_unlock_time: uint256
                # The timestamp of when the current unlocking period ends.
                full_profit_unlock_date: uint256
                # The per second rate at which profit will unlock.
                profit_unlocking_rate: uint256
                # Last timestamp of the most recent profitable report.
                last_profit_update: uint256
                
                # `nonces` track `permit` approvals with signature.
                nonces: public(HashMap[address, uint256])
                DOMAIN_TYPE_HASH: constant(bytes32) = keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')
                PERMIT_TYPE_HASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
                
                # Constructor
                @external
                def __init__():
                    # Set `asset` so it cannot be re-initialized.
                    self.asset = self
                    
                @external
                def initialize(
                    asset: address, 
                    name: String[64], 
                    symbol: String[32], 
                    role_manager: address, 
                    profit_max_unlock_time: uint256
                ):
                    """
                    @notice
                        Initialize a new vault. Sets the asset, name, symbol, and role manager.
                    @param asset
                        The address of the asset that the vault will accept.
                    @param name
                        The name of the vault token.
                    @param symbol
                        The symbol of the vault token.
                    @param role_manager 
                        The address that can add and remove roles to addresses
                    @param profit_max_unlock_time
                        The amount of time that the profit will be locked for
                    """
                    assert self.asset == empty(address), "initialized"
                    assert asset != empty(address), "ZERO ADDRESS"
                    assert role_manager != empty(address), "ZERO ADDRESS"
                
                    self.asset = asset
                    # Get the decimals for the vault to use.
                    self.decimals = ERC20Detailed(asset).decimals()
                    
                    # Set the factory as the deployer address.
                    self.factory = msg.sender
                
                    # Must be less than one year for report cycles
                    assert profit_max_unlock_time <= 31_556_952 # dev: profit unlock time too long
                    self.profit_max_unlock_time = profit_max_unlock_time
                
                    self.name = name
                    self.symbol = symbol
                    self.role_manager = role_manager
                
                ## SHARE MANAGEMENT ##
                ## ERC20 ##
                @internal
                def _spend_allowance(owner: address, spender: address, amount: uint256):
                    # Unlimited approval does nothing (saves an SSTORE)
                    current_allowance: uint256 = self.allowance[owner][spender]
                    if (current_allowance < max_value(uint256)):
                        assert current_allowance >= amount, "insufficient allowance"
                        self._approve(owner, spender, unsafe_sub(current_allowance, amount))
                
                @internal
                def _transfer(sender: address, receiver: address, amount: uint256):
                    sender_balance: uint256 = self.balance_of[sender]
                    assert sender_balance >= amount, "insufficient funds"
                    self.balance_of[sender] = unsafe_sub(sender_balance, amount)
                    self.balance_of[receiver] = unsafe_add(self.balance_of[receiver], amount)
                    log Transfer(sender, receiver, amount)
                
                @internal
                def _transfer_from(sender: address, receiver: address, amount: uint256) -> bool:
                    self._spend_allowance(sender, msg.sender, amount)
                    self._transfer(sender, receiver, amount)
                    return True
                
                @internal
                def _approve(owner: address, spender: address, amount: uint256) -> bool:
                    self.allowance[owner][spender] = amount
                    log Approval(owner, spender, amount)
                    return True
                
                @internal
                def _permit(
                    owner: address, 
                    spender: address, 
                    amount: uint256, 
                    deadline: uint256, 
                    v: uint8, 
                    r: bytes32, 
                    s: bytes32
                ) -> bool:
                    assert owner != empty(address), "invalid owner"
                    assert deadline >= block.timestamp, "permit expired"
                    nonce: uint256 = self.nonces[owner]
                    digest: bytes32 = keccak256(
                        concat(
                            b'\x19\x01',
                            self.domain_separator(),
                            keccak256(
                                concat(
                                    PERMIT_TYPE_HASH,
                                    convert(owner, bytes32),
                                    convert(spender, bytes32),
                                    convert(amount, bytes32),
                                    convert(nonce, bytes32),
                                    convert(deadline, bytes32),
                                )
                            )
                        )
                    )
                    assert ecrecover(
                        digest, v, r, s
                    ) == owner, "invalid signature"
                
                    self.allowance[owner][spender] = amount
                    self.nonces[owner] = nonce + 1
                    log Approval(owner, spender, amount)
                    return True
                
                @internal
                def _burn_shares(shares: uint256, owner: address):
                    self.balance_of[owner] -= shares
                    self.total_supply = unsafe_sub(self.total_supply, shares)
                    log Transfer(owner, empty(address), shares)
                
                @view
                @internal
                def _unlocked_shares() -> uint256:
                    """
                    Returns the amount of shares that have been unlocked.
                    To avoid sudden price_per_share spikes, profits can be processed 
                    through an unlocking period. The mechanism involves shares to be 
                    minted to the vault which are unlocked gradually over time. Shares 
                    that have been locked are gradually unlocked over profit_max_unlock_time.
                    """
                    _full_profit_unlock_date: uint256 = self.full_profit_unlock_date
                    unlocked_shares: uint256 = 0
                    if _full_profit_unlock_date > block.timestamp:
                        # If we have not fully unlocked, we need to calculate how much has been.
                        unlocked_shares = self.profit_unlocking_rate * (block.timestamp - self.last_profit_update) / MAX_BPS_EXTENDED
                
                    elif _full_profit_unlock_date != 0:
                        # All shares have been unlocked
                        unlocked_shares = self.balance_of[self]
                
                    return unlocked_shares
                
                
                @view
                @internal
                def _total_supply() -> uint256:
                    # Need to account for the shares issued to the vault that have unlocked.
                    return self.total_supply - self._unlocked_shares()
                
                @view
                @internal
                def _total_assets() -> uint256:
                    """
                    Total amount of assets that are in the vault and in the strategies. 
                    """
                    return self.total_idle + self.total_debt
                
                @view
                @internal
                def _convert_to_assets(shares: uint256, rounding: Rounding) -> uint256:
                    """ 
                    assets = shares * (total_assets / total_supply) --- (== price_per_share * shares)
                    """
                    if shares == max_value(uint256) or shares == 0:
                        return shares
                
                    total_supply: uint256 = self._total_supply()
                    # if total_supply is 0, price_per_share is 1
                    if total_supply == 0: 
                        return shares
                
                    numerator: uint256 = shares * self._total_assets()
                    amount: uint256 = numerator / total_supply
                    if rounding == Rounding.ROUND_UP and numerator % total_supply != 0:
                        amount += 1
                
                    return amount
                
                @view
                @internal
                def _convert_to_shares(assets: uint256, rounding: Rounding) -> uint256:
                    """
                    shares = amount * (total_supply / total_assets) --- (== amount / price_per_share)
                    """
                    if assets == max_value(uint256) or assets == 0:
                        return assets
                
                    total_supply: uint256 = self._total_supply()
                    total_assets: uint256 = self._total_assets()
                
                    if total_assets == 0:
                        # if total_assets and total_supply is 0, price_per_share is 1
                        if total_supply == 0:
                            return assets
                        else:
                            # Else if total_supply > 0 price_per_share is 0
                            return 0
                
                    numerator: uint256 = assets * total_supply
                    shares: uint256 = numerator / total_assets
                    if rounding == Rounding.ROUND_UP and numerator % total_assets != 0:
                        shares += 1
                
                    return shares
                
                @internal
                def _erc20_safe_approve(token: address, spender: address, amount: uint256):
                    # Used only to approve tokens that are not the type managed by this Vault.
                    # Used to handle non-compliant tokens like USDT
                    assert ERC20(token).approve(spender, amount, default_return_value=True), "approval failed"
                
                @internal
                def _erc20_safe_transfer_from(token: address, sender: address, receiver: address, amount: uint256):
                    # Used only to transfer tokens that are not the type managed by this Vault.
                    # Used to handle non-compliant tokens like USDT
                    assert ERC20(token).transferFrom(sender, receiver, amount, default_return_value=True), "transfer failed"
                
                @internal
                def _erc20_safe_transfer(token: address, receiver: address, amount: uint256):
                    # Used only to send tokens that are not the type managed by this Vault.
                    # Used to handle non-compliant tokens like USDT
                    assert ERC20(token).transfer(receiver, amount, default_return_value=True), "transfer failed"
                
                @internal
                def _issue_shares(shares: uint256, recipient: address):
                    self.balance_of[recipient] = unsafe_add(self.balance_of[recipient], shares)
                    self.total_supply += shares
                
                    log Transfer(empty(address), recipient, shares)
                
                @internal
                def _issue_shares_for_amount(amount: uint256, recipient: address) -> uint256:
                    """
                    Issues shares that are worth 'amount' in the underlying token (asset).
                    WARNING: this takes into account that any new assets have been summed 
                    to total_assets (otherwise pps will go down).
                    """
                    total_supply: uint256 = self._total_supply()
                    total_assets: uint256 = self._total_assets()
                    new_shares: uint256 = 0
                    
                    # If no supply PPS = 1.
                    if total_supply == 0:
                        new_shares = amount
                    elif total_assets > amount:
                        new_shares = amount * total_supply / (total_assets - amount)
                
                    # We don't make the function revert
                    if new_shares == 0:
                       return 0
                
                    self._issue_shares(new_shares, recipient)
                
                    return new_shares
                
                ## ERC4626 ##
                @view
                @internal
                def _max_deposit(receiver: address) -> uint256: 
                    if receiver in [empty(address), self]:
                        return 0
                
                    # If there is a deposit limit module set use that.
                    deposit_limit_module: address = self.deposit_limit_module
                    if deposit_limit_module != empty(address):
                        return IDepositLimitModule(deposit_limit_module).available_deposit_limit(receiver)
                    
                    # Else use the standard flow.
                    _deposit_limit: uint256 = self.deposit_limit
                    if (_deposit_limit == max_value(uint256)):
                        return _deposit_limit
                
                    _total_assets: uint256 = self._total_assets()
                    if (_total_assets >= _deposit_limit):
                        return 0
                
                    return unsafe_sub(_deposit_limit, _total_assets)
                
                @view
                @internal
                def _max_withdraw(
                    owner: address,
                    max_loss: uint256,
                    strategies: DynArray[address, MAX_QUEUE]
                ) -> uint256:
                    """
                    @dev Returns the max amount of `asset` an `owner` can withdraw.
                
                    This will do a full simulation of the withdraw in order to determine
                    how much is currently liquid and if the `max_loss` would allow for the 
                    tx to not revert.
                
                    This will track any expected loss to check if the tx will revert, but
                    not account for it in the amount returned since it is unrealised and 
                    therefore will not be accounted for in the conversion rates.
                
                    i.e. If we have 100 debt and 10 of unrealised loss, the max we can get
                    out is 90, but a user of the vault will need to call withdraw with 100
                    in order to get the full 90 out.
                    """
                
                    # Get the max amount for the owner if fully liquid.
                    max_assets: uint256 = self._convert_to_assets(self.balance_of[owner], Rounding.ROUND_DOWN)
                
                    # If there is a withdraw limit module use that.
                    withdraw_limit_module: address = self.withdraw_limit_module
                    if withdraw_limit_module != empty(address):
                        return min(
                            # Use the min between the returned value and the max.
                            # Means the limit module doesn't need to account for balances or conversions.
                            IWithdrawLimitModule(withdraw_limit_module).available_withdraw_limit(owner, max_loss, strategies),
                            max_assets
                        )
                    
                    # See if we have enough idle to service the withdraw.
                    current_idle: uint256 = self.total_idle
                    if max_assets > current_idle:
                        # Track how much we can pull.
                        have: uint256 = current_idle
                        loss: uint256 = 0
                
                        # Cache the default queue.
                        _strategies: DynArray[address, MAX_QUEUE] = self.default_queue
                
                        # If a custom queue was passed, and we don't force the default queue.
                        if len(strategies) != 0 and not self.use_default_queue:
                            # Use the custom queue.
                            _strategies = strategies
                
                        for strategy in _strategies:
                            # Can't use an invalid strategy.
                            assert self.strategies[strategy].activation != 0, "inactive strategy"
                
                            # Get the maximum amount the vault would withdraw from the strategy.
                            to_withdraw: uint256 = min(
                                # What we still need for the full withdraw.
                                max_assets - have, 
                                # The current debt the strategy has.
                                self.strategies[strategy].current_debt
                            )
                
                            # Get any unrealised loss for the strategy.
                            unrealised_loss: uint256 = self._assess_share_of_unrealised_losses(strategy, to_withdraw)
                
                            # See if any limit is enforced by the strategy.
                            strategy_limit: uint256 = IStrategy(strategy).convertToAssets(
                                IStrategy(strategy).maxRedeem(self)
                            )
                
                            # Adjust accordingly if there is a max withdraw limit.
                            realizable_withdraw: uint256 = to_withdraw - unrealised_loss
                            if strategy_limit < realizable_withdraw:
                                if unrealised_loss != 0:
                                    # lower unrealised loss proportional to the limit.
                                    unrealised_loss = unrealised_loss * strategy_limit / realizable_withdraw
                
                                # Still count the unrealised loss as withdrawable.
                                to_withdraw = strategy_limit + unrealised_loss
                                
                            # If 0 move on to the next strategy.
                            if to_withdraw == 0:
                                continue
                
                            # If there would be a loss with a non-maximum `max_loss` value.
                            if unrealised_loss > 0 and max_loss < MAX_BPS:
                                # Check if the loss is greater than the allowed range.
                                if loss + unrealised_loss > (have + to_withdraw) * max_loss / MAX_BPS:
                                    # If so use the amounts up till now.
                                    break
                
                            # Add to what we can pull.
                            have += to_withdraw
                
                            # If we have all we need break.
                            if have >= max_assets:
                                break
                
                            # Add any unrealised loss to the total
                            loss += unrealised_loss
                
                        # Update the max after going through the queue.
                        # In case we broke early or exhausted the queue.
                        max_assets = have
                
                    return max_assets
                
                @internal
                def _deposit(sender: address, recipient: address, assets: uint256) -> uint256:
                    """
                    Used for `deposit` calls to transfer the amount of `asset` to the vault, 
                    issue the corresponding shares to the `recipient` and update all needed 
                    vault accounting.
                    """
                    assert self.shutdown == False # dev: shutdown
                    assert assets <= self._max_deposit(recipient), "exceed deposit limit"
                 
                    # Transfer the tokens to the vault first.
                    self._erc20_safe_transfer_from(self.asset, msg.sender, self, assets)
                    # Record the change in total assets.
                    self.total_idle += assets
                    
                    # Issue the corresponding shares for assets.
                    shares: uint256 = self._issue_shares_for_amount(assets, recipient)
                
                    assert shares > 0, "cannot mint zero"
                
                    log Deposit(sender, recipient, assets, shares)
                    return shares
                
                @internal
                def _mint(sender: address, recipient: address, shares: uint256) -> uint256:
                    """
                    Used for `mint` calls to issue the corresponding shares to the `recipient`,
                    transfer the amount of `asset` to the vault, and update all needed vault 
                    accounting.
                    """
                    assert self.shutdown == False # dev: shutdown
                    # Get corresponding amount of assets.
                    assets: uint256 = self._convert_to_assets(shares, Rounding.ROUND_UP)
                
                    assert assets > 0, "cannot deposit zero"
                    assert assets <= self._max_deposit(recipient), "exceed deposit limit"
                
                    # Transfer the tokens to the vault first.
                    self._erc20_safe_transfer_from(self.asset, msg.sender, self, assets)
                    # Record the change in total assets.
                    self.total_idle += assets
                    
                    # Issue the corresponding shares for assets.
                    self._issue_shares(shares, recipient)
                
                    log Deposit(sender, recipient, assets, shares)
                    return assets
                
                @view
                @internal
                def _assess_share_of_unrealised_losses(strategy: address, assets_needed: uint256) -> uint256:
                    """
                    Returns the share of losses that a user would take if withdrawing from this strategy
                    This accounts for losses that have been realized at the strategy level but not yet
                    realized at the vault level.
                
                    e.g. if the strategy has unrealised losses for 10% of its current debt and the user 
                    wants to withdraw 1_000 tokens, the losses that they will take is 100 token
                    """
                    # Minimum of how much debt the debt should be worth.
                    strategy_current_debt: uint256 = self.strategies[strategy].current_debt
                    # The actual amount that the debt is currently worth.
                    vault_shares: uint256 = IStrategy(strategy).balanceOf(self)
                    strategy_assets: uint256 = IStrategy(strategy).convertToAssets(vault_shares)
                    
                    # If no losses, return 0
                    if strategy_assets >= strategy_current_debt or strategy_current_debt == 0:
                        return 0
                
                    # Users will withdraw assets_needed divided by loss ratio (strategy_assets / strategy_current_debt - 1).
                    # NOTE: If there are unrealised losses, the user will take his share.
                    numerator: uint256 = assets_needed * strategy_assets
                    users_share_of_loss: uint256 = assets_needed - numerator / strategy_current_debt
                    # Always round up.
                    if numerator % strategy_current_debt != 0:
                        users_share_of_loss += 1
                
                    return users_share_of_loss
                
                @internal
                def _withdraw_from_strategy(strategy: address, assets_to_withdraw: uint256):
                    """
                    This takes the amount denominated in asset and performs a {redeem}
                    with the corresponding amount of shares.
                
                    We use {redeem} to natively take on losses without additional non-4626 standard parameters.
                    """
                    # Need to get shares since we use redeem to be able to take on losses.
                    shares_to_redeem: uint256 = min(
                        # Use previewWithdraw since it should round up.
                        IStrategy(strategy).previewWithdraw(assets_to_withdraw), 
                        # And check against our actual balance.
                        IStrategy(strategy).balanceOf(self)
                    )
                    # Redeem the shares.
                    IStrategy(strategy).redeem(shares_to_redeem, self, self)
                
                @internal
                def _redeem(
                    sender: address, 
                    receiver: address, 
                    owner: address,
                    assets: uint256,
                    shares: uint256, 
                    max_loss: uint256,
                    strategies: DynArray[address, MAX_QUEUE]
                ) -> uint256:
                    """
                    This will attempt to free up the full amount of assets equivalent to
                    `shares` and transfer them to the `receiver`. If the vault does
                    not have enough idle funds it will go through any strategies provided by
                    either the withdrawer or the default_queue to free up enough funds to 
                    service the request.
                
                    The vault will attempt to account for any unrealized losses taken on from
                    strategies since their respective last reports.
                
                    Any losses realized during the withdraw from a strategy will be passed on
                    to the user that is redeeming their vault shares unless it exceeds the given
                    `max_loss`.
                    """
                    assert receiver != empty(address), "ZERO ADDRESS"
                    assert shares > 0, "no shares to redeem"
                    assert assets > 0, "no assets to withdraw"
                    assert max_loss <= MAX_BPS, "max loss"
                    
                    # If there is a withdraw limit module, check the max.
                    withdraw_limit_module: address = self.withdraw_limit_module
                    if withdraw_limit_module != empty(address):
                        assert assets <= IWithdrawLimitModule(withdraw_limit_module).available_withdraw_limit(owner, max_loss, strategies), "exceed withdraw limit"
                
                    assert self.balance_of[owner] >= shares, "insufficient shares to redeem"
                    
                    if sender != owner:
                        self._spend_allowance(owner, sender, shares)
                
                    # The amount of the underlying token to withdraw.
                    requested_assets: uint256 = assets
                
                    # load to memory to save gas
                    current_total_idle: uint256 = self.total_idle
                    _asset: address = self.asset
                
                    # If there are not enough assets in the Vault contract, we try to free
                    # funds from strategies.
                    if requested_assets > current_total_idle:
                
                        # Cache the default queue.
                        _strategies: DynArray[address, MAX_QUEUE] = self.default_queue
                
                        # If a custom queue was passed, and we don't force the default queue.
                        if len(strategies) != 0 and not self.use_default_queue:
                            # Use the custom queue.
                            _strategies = strategies
                
                        # load to memory to save gas
                        current_total_debt: uint256 = self.total_debt
                
                        # Withdraw from strategies only what idle doesn't cover.
                        # `assets_needed` is the total amount we need to fill the request.
                        assets_needed: uint256 = unsafe_sub(requested_assets, current_total_idle)
                        # `assets_to_withdraw` is the amount to request from the current strategy.
                        assets_to_withdraw: uint256 = 0
                
                        # To compare against real withdrawals from strategies
                        previous_balance: uint256 = ERC20(_asset).balanceOf(self)
                
                        for strategy in _strategies:
                            # Make sure we have a valid strategy.
                            assert self.strategies[strategy].activation != 0, "inactive strategy"
                
                            # How much should the strategy have.
                            current_debt: uint256 = self.strategies[strategy].current_debt
                
                            # What is the max amount to withdraw from this strategy.
                            assets_to_withdraw = min(assets_needed, current_debt)
                
                            # Cache max_withdraw now for use if unrealized loss > 0
                            # Use maxRedeem and convert it since we use redeem.
                            max_withdraw: uint256 = IStrategy(strategy).convertToAssets(
                                IStrategy(strategy).maxRedeem(self)
                            )
                
                            # CHECK FOR UNREALISED LOSSES
                            # If unrealised losses > 0, then the user will take the proportional share 
                            # and realize it (required to avoid users withdrawing from lossy strategies).
                            # NOTE: strategies need to manage the fact that realising part of the loss can 
                            # mean the realisation of 100% of the loss!! (i.e. if for withdrawing 10% of the
                            # strategy it needs to unwind the whole position, generated losses might be bigger)
                            unrealised_losses_share: uint256 = self._assess_share_of_unrealised_losses(strategy, assets_to_withdraw)
                            if unrealised_losses_share > 0:
                                # If max withdraw is limiting the amount to pull, we need to adjust the portion of 
                                # the unrealized loss the user should take.
                                if max_withdraw < assets_to_withdraw - unrealised_losses_share:
                                    # How much would we want to withdraw
                                    wanted: uint256 = assets_to_withdraw - unrealised_losses_share
                                    # Get the proportion of unrealised comparing what we want vs. what we can get
                                    unrealised_losses_share = unrealised_losses_share * max_withdraw / wanted
                                    # Adjust assets_to_withdraw so all future calculations work correctly
                                    assets_to_withdraw = max_withdraw + unrealised_losses_share
                                
                                # User now "needs" less assets to be unlocked (as he took some as losses)
                                assets_to_withdraw -= unrealised_losses_share
                                requested_assets -= unrealised_losses_share
                                # NOTE: done here instead of waiting for regular update of these values 
                                # because it's a rare case (so we can save minor amounts of gas)
                                assets_needed -= unrealised_losses_share
                                current_total_debt -= unrealised_losses_share
                
                                # If max withdraw is 0 and unrealised loss is still > 0 then the strategy likely
                                # realized a 100% loss and we will need to realize that loss before moving on.
                                if max_withdraw == 0 and unrealised_losses_share > 0:
                                    # Adjust the strategy debt accordingly.
                                    new_debt: uint256 = current_debt - unrealised_losses_share
                        
                                    # Update strategies storage
                                    self.strategies[strategy].current_debt = new_debt
                                    # Log the debt update
                                    log DebtUpdated(strategy, current_debt, new_debt)
                
                            # Adjust based on the max withdraw of the strategy.
                            assets_to_withdraw = min(assets_to_withdraw, max_withdraw)
                
                            # Can't withdraw 0.
                            if assets_to_withdraw == 0:
                                continue
                            
                            # WITHDRAW FROM STRATEGY
                            self._withdraw_from_strategy(strategy, assets_to_withdraw)
                            post_balance: uint256 = ERC20(_asset).balanceOf(self)
                            
                            # Always check against the real amounts.
                            withdrawn: uint256 = post_balance - previous_balance
                            loss: uint256 = 0
                            # Check if we redeemed too much.
                            if withdrawn > assets_to_withdraw:
                                # Make sure we don't underflow in debt updates.
                                if withdrawn > current_debt:
                                    # Can't withdraw more than our debt.
                                    assets_to_withdraw = current_debt
                                else:
                                    # Add the extra to how much we withdrew.
                                    assets_to_withdraw += (unsafe_sub(withdrawn, assets_to_withdraw))
                
                            # If we have not received what we expected, we consider the difference a loss.
                            elif withdrawn < assets_to_withdraw:
                                loss = unsafe_sub(assets_to_withdraw, withdrawn)
                
                            # NOTE: strategy's debt decreases by the full amount but the total idle increases 
                            # by the actual amount only (as the difference is considered lost).
                            current_total_idle += (assets_to_withdraw - loss)
                            requested_assets -= loss
                            current_total_debt -= assets_to_withdraw
                
                            # Vault will reduce debt because the unrealised loss has been taken by user
                            new_debt: uint256 = current_debt - (assets_to_withdraw + unrealised_losses_share)
                        
                            # Update strategies storage
                            self.strategies[strategy].current_debt = new_debt
                            # Log the debt update
                            log DebtUpdated(strategy, current_debt, new_debt)
                
                            # Break if we have enough total idle to serve initial request.
                            if requested_assets <= current_total_idle:
                                break
                
                            # We update the previous_balance variable here to save gas in next iteration.
                            previous_balance = post_balance
                
                            # Reduce what we still need. Safe to use assets_to_withdraw 
                            # here since it has been checked against requested_assets
                            assets_needed -= assets_to_withdraw
                
                        # If we exhaust the queue and still have insufficient total idle, revert.
                        assert current_total_idle >= requested_assets, "insufficient assets in vault"
                        # Commit memory to storage.
                        self.total_debt = current_total_debt
                
                    # Check if there is a loss and a non-default value was set.
                    if assets > requested_assets and max_loss < MAX_BPS:
                        # Assure the loss is within the allowed range.
                        assert assets - requested_assets <= assets * max_loss / MAX_BPS, "too much loss"
                
                    # First burn the corresponding shares from the redeemer.
                    self._burn_shares(shares, owner)
                    # Commit memory to storage.
                    self.total_idle = current_total_idle - requested_assets
                    # Transfer the requested amount to the receiver.
                    self._erc20_safe_transfer(_asset, receiver, requested_assets)
                
                    log Withdraw(sender, receiver, owner, requested_assets, shares)
                    return requested_assets
                
                ## STRATEGY MANAGEMENT ##
                @internal
                def _add_strategy(new_strategy: address, add_to_queue: bool):
                    assert new_strategy not in [self, empty(address)], "strategy cannot be zero address"
                    assert IStrategy(new_strategy).asset() == self.asset, "invalid asset"
                    assert self.strategies[new_strategy].activation == 0, "strategy already active"
                
                    # Add the new strategy to the mapping.
                    self.strategies[new_strategy] = StrategyParams({
                        activation: block.timestamp,
                        last_report: block.timestamp,
                        current_debt: 0,
                        max_debt: 0
                    })
                
                    # If we are adding to the queue and the default queue has space, add the strategy.
                    if add_to_queue and len(self.default_queue) < MAX_QUEUE:
                        self.default_queue.append(new_strategy)        
                        
                    log StrategyChanged(new_strategy, StrategyChangeType.ADDED)
                
                @internal
                def _revoke_strategy(strategy: address, force: bool=False):
                    assert self.strategies[strategy].activation != 0, "strategy not active"
                
                    # If force revoking a strategy, it will cause a loss.
                    loss: uint256 = 0
                    
                    if self.strategies[strategy].current_debt != 0:
                        assert force, "strategy has debt"
                        # Vault realizes the full loss of outstanding debt.
                        loss = self.strategies[strategy].current_debt
                        # Adjust total vault debt.
                        self.total_debt -= loss
                
                        log StrategyReported(strategy, 0, loss, 0, 0, 0, 0)
                
                    # Set strategy params all back to 0 (WARNING: it can be re-added).
                    self.strategies[strategy] = StrategyParams({
                      activation: 0,
                      last_report: 0,
                      current_debt: 0,
                      max_debt: 0
                    })
                
                    # Remove strategy if it is in the default queue.
                    new_queue: DynArray[address, MAX_QUEUE] = []
                    for _strategy in self.default_queue:
                        # Add all strategies to the new queue besides the one revoked.
                        if _strategy != strategy:
                            new_queue.append(_strategy)
                        
                    # Set the default queue to our updated queue.
                    self.default_queue = new_queue
                
                    log StrategyChanged(strategy, StrategyChangeType.REVOKED)
                
                # DEBT MANAGEMENT #
                @internal
                def _update_debt(strategy: address, target_debt: uint256, max_loss: uint256) -> uint256:
                    """
                    The vault will re-balance the debt vs target debt. Target debt must be
                    smaller or equal to strategy's max_debt. This function will compare the 
                    current debt with the target debt and will take funds or deposit new 
                    funds to the strategy. 
                
                    The strategy can require a maximum amount of funds that it wants to receive
                    to invest. The strategy can also reject freeing funds if they are locked.
                    """
                    # How much we want the strategy to have.
                    new_debt: uint256 = target_debt
                    # How much the strategy currently has.
                    current_debt: uint256 = self.strategies[strategy].current_debt
                
                    # If the vault is shutdown we can only pull funds.
                    if self.shutdown:
                        new_debt = 0
                
                    assert new_debt != current_debt, "new debt equals current debt"
                
                    if current_debt > new_debt:
                        # Reduce debt.
                        assets_to_withdraw: uint256 = unsafe_sub(current_debt, new_debt)
                
                        # Ensure we always have minimum_total_idle when updating debt.
                        minimum_total_idle: uint256 = self.minimum_total_idle
                        total_idle: uint256 = self.total_idle
                        
                        # Respect minimum total idle in vault
                        if total_idle + assets_to_withdraw < minimum_total_idle:
                            assets_to_withdraw = unsafe_sub(minimum_total_idle, total_idle)
                            # Cant withdraw more than the strategy has.
                            if assets_to_withdraw > current_debt:
                                assets_to_withdraw = current_debt
                
                        # Check how much we are able to withdraw.
                        # Use maxRedeem and convert since we use redeem.
                        withdrawable: uint256 = IStrategy(strategy).convertToAssets(
                            IStrategy(strategy).maxRedeem(self)
                        )
                        assert withdrawable != 0, "nothing to withdraw"
                
                        # If insufficient withdrawable, withdraw what we can.
                        if withdrawable < assets_to_withdraw:
                            assets_to_withdraw = withdrawable
                
                        # If there are unrealised losses we don't let the vault reduce its debt until there is a new report
                        unrealised_losses_share: uint256 = self._assess_share_of_unrealised_losses(strategy, assets_to_withdraw)
                        assert unrealised_losses_share == 0, "strategy has unrealised losses"
                        
                        # Cache for repeated use.
                        _asset: address = self.asset
                
                        # Always check the actual amount withdrawn.
                        pre_balance: uint256 = ERC20(_asset).balanceOf(self)
                        self._withdraw_from_strategy(strategy, assets_to_withdraw)
                        post_balance: uint256 = ERC20(_asset).balanceOf(self)
                        
                        # making sure we are changing idle according to the real result no matter what. 
                        # We pull funds with {redeem} so there can be losses or rounding differences.
                        withdrawn: uint256 = min(post_balance - pre_balance, current_debt)
                
                        # If we didn't get the amount we asked for and there is a max loss.
                        if withdrawn < assets_to_withdraw and max_loss < MAX_BPS:
                            # Make sure the loss is within the allowed range.
                            assert assets_to_withdraw - withdrawn <= assets_to_withdraw * max_loss / MAX_BPS, "too much loss"
                
                        # If we got too much make sure not to increase PPS.
                        elif withdrawn > assets_to_withdraw:
                            assets_to_withdraw = withdrawn
                
                        # Update storage.
                        self.total_idle += withdrawn # actual amount we got.
                        # Amount we tried to withdraw in case of losses
                        self.total_debt -= assets_to_withdraw 
                
                        new_debt = current_debt - assets_to_withdraw
                    else: 
                        # We are increasing the strategies debt
                
                        # Revert if target_debt cannot be achieved due to configured max_debt for given strategy
                        assert new_debt <= self.strategies[strategy].max_debt, "target debt higher than max debt"
                
                        # Vault is increasing debt with the strategy by sending more funds.
                        max_deposit: uint256 = IStrategy(strategy).maxDeposit(self)
                        assert max_deposit != 0, "nothing to deposit"
                
                        # Deposit the difference between desired and current.
                        assets_to_deposit: uint256 = new_debt - current_debt
                        if assets_to_deposit > max_deposit:
                            # Deposit as much as possible.
                            assets_to_deposit = max_deposit
                        
                        # Ensure we always have minimum_total_idle when updating debt.
                        minimum_total_idle: uint256 = self.minimum_total_idle
                        total_idle: uint256 = self.total_idle
                
                        assert total_idle > minimum_total_idle, "no funds to deposit"
                        available_idle: uint256 = unsafe_sub(total_idle, minimum_total_idle)
                
                        # If insufficient funds to deposit, transfer only what is free.
                        if assets_to_deposit > available_idle:
                            assets_to_deposit = available_idle
                
                        # Can't Deposit 0.
                        if assets_to_deposit > 0:
                            # Cache for repeated use.
                            _asset: address = self.asset
                
                            # Approve the strategy to pull only what we are giving it.
                            self._erc20_safe_approve(_asset, strategy, assets_to_deposit)
                
                            # Always update based on actual amounts deposited.
                            pre_balance: uint256 = ERC20(_asset).balanceOf(self)
                            IStrategy(strategy).deposit(assets_to_deposit, self)
                            post_balance: uint256 = ERC20(_asset).balanceOf(self)
                
                            # Make sure our approval is always back to 0.
                            self._erc20_safe_approve(_asset, strategy, 0)
                
                            # Making sure we are changing according to the real result no 
                            # matter what. This will spend more gas but makes it more robust.
                            assets_to_deposit = pre_balance - post_balance
                
                            # Update storage.
                            self.total_idle -= assets_to_deposit
                            self.total_debt += assets_to_deposit
                
                        new_debt = current_debt + assets_to_deposit
                
                    # Commit memory to storage.
                    self.strategies[strategy].current_debt = new_debt
                
                    log DebtUpdated(strategy, current_debt, new_debt)
                    return new_debt
                
                ## ACCOUNTING MANAGEMENT ##
                @internal
                def _process_report(strategy: address) -> (uint256, uint256):
                    """
                    Processing a report means comparing the debt that the strategy has taken 
                    with the current amount of funds it is reporting. If the strategy owes 
                    less than it currently has, it means it has had a profit, else (assets < debt) 
                    it has had a loss.
                
                    Different strategies might choose different reporting strategies: pessimistic, 
                    only realised P&L, ... The best way to report depends on the strategy.
                
                    The profit will be distributed following a smooth curve over the vaults 
                    profit_max_unlock_time seconds. Losses will be taken immediately, first from the 
                    profit buffer (avoiding an impact in pps), then will reduce pps.
                
                    Any applicable fees are charged and distributed during the report as well
                    to the specified recipients.
                    """
                    # Make sure we have a valid strategy.
                    assert self.strategies[strategy].activation != 0, "inactive strategy"
                
                    # Vault assesses profits using 4626 compliant interface. 
                    # NOTE: It is important that a strategies `convertToAssets` implementation
                    # cannot be manipulated or else the vault could report incorrect gains/losses.
                    strategy_shares: uint256 = IStrategy(strategy).balanceOf(self)
                    # How much the vaults position is worth.
                    total_assets: uint256 = IStrategy(strategy).convertToAssets(strategy_shares)
                    # How much the vault had deposited to the strategy.
                    current_debt: uint256 = self.strategies[strategy].current_debt
                
                    gain: uint256 = 0
                    loss: uint256 = 0
                
                    ### Asses Gain or Loss ###
                
                    # Compare reported assets vs. the current debt.
                    if total_assets > current_debt:
                        # We have a gain.
                        gain = unsafe_sub(total_assets, current_debt)
                    else:
                        # We have a loss.
                        loss = unsafe_sub(current_debt, total_assets)
                    
                    # Cache `asset` for repeated use.
                    _asset: address = self.asset
                
                    ### Asses Fees and Refunds ###
                
                    # For Accountant fee assessment.
                    total_fees: uint256 = 0
                    total_refunds: uint256 = 0
                    # If accountant is not set, fees and refunds remain unchanged.
                    accountant: address = self.accountant
                    if accountant != empty(address):
                        total_fees, total_refunds = IAccountant(accountant).report(strategy, gain, loss)
                
                        if total_refunds > 0:
                            # Make sure we have enough approval and enough asset to pull.
                            total_refunds = min(total_refunds, min(ERC20(_asset).balanceOf(accountant), ERC20(_asset).allowance(accountant, self)))
                
                    # Total fees to charge in shares.
                    total_fees_shares: uint256 = 0
                    # For Protocol fee assessment.
                    protocol_fee_bps: uint16 = 0
                    protocol_fees_shares: uint256 = 0
                    protocol_fee_recipient: address = empty(address)
                    # `shares_to_burn` is derived from amounts that would reduce the vaults PPS.
                    # NOTE: this needs to be done before any pps changes
                    shares_to_burn: uint256 = 0
                    # Only need to burn shares if there is a loss or fees.
                    if loss + total_fees > 0:
                        # The amount of shares we will want to burn to offset losses and fees.
                        shares_to_burn = self._convert_to_shares(loss + total_fees, Rounding.ROUND_UP)
                
                        # If we have fees then get the proportional amount of shares to issue.
                        if total_fees > 0:
                            # Get the total amount shares to issue for the fees.
                            total_fees_shares = shares_to_burn * total_fees / (loss + total_fees)
                
                            # Get the protocol fee config for this vault.
                            protocol_fee_bps, protocol_fee_recipient = IFactory(self.factory).protocol_fee_config()
                
                            # If there is a protocol fee.
                            if protocol_fee_bps > 0:
                                # Get the percent of fees to go to protocol fees.
                                protocol_fees_shares = total_fees_shares * convert(protocol_fee_bps, uint256) / MAX_BPS
                
                
                    # Shares to lock is any amount that would otherwise increase the vaults PPS.
                    shares_to_lock: uint256 = 0
                    profit_max_unlock_time: uint256 = self.profit_max_unlock_time
                    # Get the amount we will lock to avoid a PPS increase.
                    if gain + total_refunds > 0 and profit_max_unlock_time != 0:
                        shares_to_lock = self._convert_to_shares(gain + total_refunds, Rounding.ROUND_DOWN)
                
                    # The total current supply including locked shares.
                    total_supply: uint256 = self.total_supply
                    # The total shares the vault currently owns. Both locked and unlocked.
                    total_locked_shares: uint256 = self.balance_of[self]
                    # Get the desired end amount of shares after all accounting.
                    ending_supply: uint256 = total_supply + shares_to_lock - shares_to_burn - self._unlocked_shares()
                    
                    # If we will end with more shares than we have now.
                    if ending_supply > total_supply:
                        # Issue the difference.
                        self._issue_shares(unsafe_sub(ending_supply, total_supply), self)
                
                    # Else we need to burn shares.
                    elif total_supply > ending_supply:
                        # Can't burn more than the vault owns.
                        to_burn: uint256 = min(unsafe_sub(total_supply, ending_supply), total_locked_shares)
                        self._burn_shares(to_burn, self)
                
                    # Adjust the amount to lock for this period.
                    if shares_to_lock > shares_to_burn:
                        # Don't lock fees or losses.
                        shares_to_lock = unsafe_sub(shares_to_lock, shares_to_burn)
                    else:
                        shares_to_lock = 0
                
                    # Pull refunds
                    if total_refunds > 0:
                        # Transfer the refunded amount of asset to the vault.
                        self._erc20_safe_transfer_from(_asset, accountant, self, total_refunds)
                        # Update storage to increase total assets.
                        self.total_idle += total_refunds
                
                    # Record any reported gains.
                    if gain > 0:
                        # NOTE: this will increase total_assets
                        current_debt = unsafe_add(current_debt, gain)
                        self.strategies[strategy].current_debt = current_debt
                        self.total_debt += gain
                
                    # Or record any reported loss
                    elif loss > 0:
                        current_debt = unsafe_sub(current_debt, loss)
                        self.strategies[strategy].current_debt = current_debt
                        self.total_debt -= loss
                
                    # Issue shares for fees that were calculated above if applicable.
                    if total_fees_shares > 0:
                        # Accountant fees are (total_fees - protocol_fees).
                        self._issue_shares(total_fees_shares - protocol_fees_shares, accountant)
                
                        # If we also have protocol fees.
                        if protocol_fees_shares > 0:
                            self._issue_shares(protocol_fees_shares, protocol_fee_recipient)
                
                    # Update unlocking rate and time to fully unlocked.
                    total_locked_shares = self.balance_of[self]
                    if total_locked_shares > 0:
                        previously_locked_time: uint256 = 0
                        _full_profit_unlock_date: uint256 = self.full_profit_unlock_date
                        # Check if we need to account for shares still unlocking.
                        if _full_profit_unlock_date > block.timestamp: 
                            # There will only be previously locked shares if time remains.
                            # We calculate this here since it will not occur every time we lock shares.
                            previously_locked_time = (total_locked_shares - shares_to_lock) * (_full_profit_unlock_date - block.timestamp)
                
                        # new_profit_locking_period is a weighted average between the remaining time of the previously locked shares and the profit_max_unlock_time
                        new_profit_locking_period: uint256 = (previously_locked_time + shares_to_lock * profit_max_unlock_time) / total_locked_shares
                        # Calculate how many shares unlock per second.
                        self.profit_unlocking_rate = total_locked_shares * MAX_BPS_EXTENDED / new_profit_locking_period
                        # Calculate how long until the full amount of shares is unlocked.
                        self.full_profit_unlock_date = block.timestamp + new_profit_locking_period
                        # Update the last profitable report timestamp.
                        self.last_profit_update = block.timestamp
                    else:
                        # NOTE: only setting this to the 0 will turn in the desired effect, 
                        # no need to update profit_unlocking_rate
                        self.full_profit_unlock_date = 0
                    
                    # Record the report of profit timestamp.
                    self.strategies[strategy].last_report = block.timestamp
                
                    # We have to recalculate the fees paid for cases with an overall loss or no profit locking
                    if loss + total_fees > gain + total_refunds or profit_max_unlock_time == 0:
                        total_fees = self._convert_to_assets(total_fees_shares, Rounding.ROUND_DOWN)
                
                    log StrategyReported(
                        strategy,
                        gain,
                        loss,
                        current_debt,
                        total_fees * convert(protocol_fee_bps, uint256) / MAX_BPS, # Protocol Fees
                        total_fees,
                        total_refunds
                    )
                
                    return (gain, loss)
                
                # SETTERS #
                @external
                def set_accountant(new_accountant: address):
                    """
                    @notice Set the new accountant address.
                    @param new_accountant The new accountant address.
                    """
                    self._enforce_role(msg.sender, Roles.ACCOUNTANT_MANAGER)
                    self.accountant = new_accountant
                
                    log UpdateAccountant(new_accountant)
                
                @external
                def set_default_queue(new_default_queue: DynArray[address, MAX_QUEUE]):
                    """
                    @notice Set the new default queue array.
                    @dev Will check each strategy to make sure it is active. But will not
                        check that the same strategy is not added twice. maxRedeem and maxWithdraw
                        return values may be inaccurate if a strategy is added twice.
                    @param new_default_queue The new default queue array.
                    """
                    self._enforce_role(msg.sender, Roles.QUEUE_MANAGER)
                
                    # Make sure every strategy in the new queue is active.
                    for strategy in new_default_queue:
                        assert self.strategies[strategy].activation != 0, "!inactive"
                
                    # Save the new queue.
                    self.default_queue = new_default_queue
                
                    log UpdateDefaultQueue(new_default_queue)
                
                @external
                def set_use_default_queue(use_default_queue: bool):
                    """
                    @notice Set a new value for `use_default_queue`.
                    @dev If set `True` the default queue will always be
                        used no matter whats passed in.
                    @param use_default_queue new value.
                    """
                    self._enforce_role(msg.sender, Roles.QUEUE_MANAGER)
                    self.use_default_queue = use_default_queue
                
                    log UpdateUseDefaultQueue(use_default_queue)
                
                @external
                def set_deposit_limit(deposit_limit: uint256, override: bool = False):
                    """
                    @notice Set the new deposit limit.
                    @dev Can not be changed if a deposit_limit_module
                    is set unless the override flag is true or if shutdown.
                    @param deposit_limit The new deposit limit.
                    @param override If a `deposit_limit_module` already set should be overridden.
                    """
                    assert self.shutdown == False # Dev: shutdown
                    self._enforce_role(msg.sender, Roles.DEPOSIT_LIMIT_MANAGER)
                
                    # If we are overriding the deposit limit module.
                    if override:
                        # Make sure it is set to address 0 if not already.
                        if self.deposit_limit_module != empty(address):
                
                            self.deposit_limit_module = empty(address)
                            log UpdateDepositLimitModule(empty(address))
                    else:  
                        # Make sure the deposit_limit_module has been set to address(0).
                        assert self.deposit_limit_module == empty(address), "using module"
                
                    self.deposit_limit = deposit_limit
                
                    log UpdateDepositLimit(deposit_limit)
                
                @external
                def set_deposit_limit_module(deposit_limit_module: address, override: bool = False):
                    """
                    @notice Set a contract to handle the deposit limit.
                    @dev The default `deposit_limit` will need to be set to
                    max uint256 since the module will override it or the override flag
                    must be set to true to set it to max in 1 tx..
                    @param deposit_limit_module Address of the module.
                    @param override If a `deposit_limit` already set should be overridden.
                    """
                    assert self.shutdown == False # Dev: shutdown
                    self._enforce_role(msg.sender, Roles.DEPOSIT_LIMIT_MANAGER)
                
                    # If we are overriding the deposit limit
                    if override:
                        # Make sure it is max uint256 if not already.
                        if self.deposit_limit != max_value(uint256):
                
                            self.deposit_limit = max_value(uint256)
                            log UpdateDepositLimit(max_value(uint256))
                    else:
                        # Make sure the deposit_limit has been set to uint max.
                        assert self.deposit_limit == max_value(uint256), "using deposit limit"
                
                    self.deposit_limit_module = deposit_limit_module
                
                    log UpdateDepositLimitModule(deposit_limit_module)
                
                @external
                def set_withdraw_limit_module(withdraw_limit_module: address):
                    """
                    @notice Set a contract to handle the withdraw limit.
                    @dev This will override the default `max_withdraw`.
                    @param withdraw_limit_module Address of the module.
                    """
                    self._enforce_role(msg.sender, Roles.WITHDRAW_LIMIT_MANAGER)
                
                    self.withdraw_limit_module = withdraw_limit_module
                
                    log UpdateWithdrawLimitModule(withdraw_limit_module)
                
                @external
                def set_minimum_total_idle(minimum_total_idle: uint256):
                    """
                    @notice Set the new minimum total idle.
                    @param minimum_total_idle The new minimum total idle.
                    """
                    self._enforce_role(msg.sender, Roles.MINIMUM_IDLE_MANAGER)
                    self.minimum_total_idle = minimum_total_idle
                
                    log UpdateMinimumTotalIdle(minimum_total_idle)
                
                @external
                def setProfitMaxUnlockTime(new_profit_max_unlock_time: uint256):
                    """
                    @notice Set the new profit max unlock time.
                    @dev The time is denominated in seconds and must be less than 1 year.
                        We only need to update locking period if setting to 0,
                        since the current period will use the old rate and on the next
                        report it will be reset with the new unlocking time.
                    
                        Setting to 0 will cause any currently locked profit to instantly
                        unlock and an immediate increase in the vaults Price Per Share.
                
                    @param new_profit_max_unlock_time The new profit max unlock time.
                    """
                    self._enforce_role(msg.sender, Roles.PROFIT_UNLOCK_MANAGER)
                    # Must be less than one year for report cycles
                    assert new_profit_max_unlock_time <= 31_556_952, "profit unlock time too long"
                
                    # If setting to 0 we need to reset any locked values.
                    if (new_profit_max_unlock_time == 0):
                
                        share_balance: uint256 = self.balance_of[self]
                        if share_balance > 0:
                            # Burn any shares the vault still has.
                            self._burn_shares(share_balance, self)
                
                        # Reset unlocking variables to 0.
                        self.profit_unlocking_rate = 0
                        self.full_profit_unlock_date = 0
                
                    self.profit_max_unlock_time = new_profit_max_unlock_time
                
                    log UpdateProfitMaxUnlockTime(new_profit_max_unlock_time)
                
                # ROLE MANAGEMENT #
                @internal
                def _enforce_role(account: address, role: Roles):
                    # Make sure the sender holds the role.
                    assert role in self.roles[account], "not allowed"
                
                @external
                def set_role(account: address, role: Roles):
                    """
                    @notice Set the roles for an account.
                    @dev This will fully override an accounts current roles
                     so it should include all roles the account should hold.
                    @param account The account to set the role for.
                    @param role The roles the account should hold.
                    """
                    assert msg.sender == self.role_manager
                    self.roles[account] = role
                
                    log RoleSet(account, role)
                
                @external
                def add_role(account: address, role: Roles):
                    """
                    @notice Add a new role to an address.
                    @dev This will add a new role to the account
                     without effecting any of the previously held roles.
                    @param account The account to add a role to.
                    @param role The new role to add to account.
                    """
                    assert msg.sender == self.role_manager
                    self.roles[account] = self.roles[account] | role
                
                    log RoleSet(account, self.roles[account])
                
                @external
                def remove_role(account: address, role: Roles):
                    """
                    @notice Remove a single role from an account.
                    @dev This will leave all other roles for the 
                     account unchanged.
                    @param account The account to remove a Role from.
                    @param role The Role to remove.
                    """
                    assert msg.sender == self.role_manager
                    self.roles[account] = self.roles[account] & ~role
                
                    log RoleSet(account, self.roles[account])
                    
                @external
                def transfer_role_manager(role_manager: address):
                    """
                    @notice Step 1 of 2 in order to transfer the 
                        role manager to a new address. This will set
                        the future_role_manager. Which will then need
                        to be accepted by the new manager.
                    @param role_manager The new role manager address.
                    """
                    assert msg.sender == self.role_manager
                    self.future_role_manager = role_manager
                
                @external
                def accept_role_manager():
                    """
                    @notice Accept the role manager transfer.
                    """
                    assert msg.sender == self.future_role_manager
                    self.role_manager = msg.sender
                    self.future_role_manager = empty(address)
                
                    log UpdateRoleManager(msg.sender)
                
                # VAULT STATUS VIEWS
                
                @view
                @external
                def isShutdown() -> bool:
                    """
                    @notice Get if the vault is shutdown.
                    @return Bool representing the shutdown status
                    """
                    return self.shutdown
                @view
                @external
                def unlockedShares() -> uint256:
                    """
                    @notice Get the amount of shares that have been unlocked.
                    @return The amount of shares that are have been unlocked.
                    """
                    return self._unlocked_shares()
                
                @view
                @external
                def pricePerShare() -> uint256:
                    """
                    @notice Get the price per share (pps) of the vault.
                    @dev This value offers limited precision. Integrations that require 
                        exact precision should use convertToAssets or convertToShares instead.
                    @return The price per share.
                    """
                    return self._convert_to_assets(10 ** convert(self.decimals, uint256), Rounding.ROUND_DOWN)
                
                @view
                @external
                def get_default_queue() -> DynArray[address, MAX_QUEUE]:
                    """
                    @notice Get the full default queue currently set.
                    @return The current default withdrawal queue.
                    """
                    return self.default_queue
                
                ## REPORTING MANAGEMENT ##
                @external
                @nonreentrant("lock")
                def process_report(strategy: address) -> (uint256, uint256):
                    """
                    @notice Process the report of a strategy.
                    @param strategy The strategy to process the report for.
                    @return The gain and loss of the strategy.
                    """
                    self._enforce_role(msg.sender, Roles.REPORTING_MANAGER)
                    return self._process_report(strategy)
                
                @external
                @nonreentrant("lock")
                def buy_debt(strategy: address, amount: uint256):
                    """
                    @notice Used for governance to buy bad debt from the vault.
                    @dev This should only ever be used in an emergency in place
                    of force revoking a strategy in order to not report a loss.
                    It allows the DEBT_PURCHASER role to buy the strategies debt
                    for an equal amount of `asset`. 
                
                    @param strategy The strategy to buy the debt for
                    @param amount The amount of debt to buy from the vault.
                    """
                    self._enforce_role(msg.sender, Roles.DEBT_PURCHASER)
                    assert self.strategies[strategy].activation != 0, "not active"
                    
                    # Cache the current debt.
                    current_debt: uint256 = self.strategies[strategy].current_debt
                    _amount: uint256 = amount
                
                    assert current_debt > 0, "nothing to buy"
                    assert _amount > 0, "nothing to buy with"
                    
                    if _amount > current_debt:
                        _amount = current_debt
                
                    # We get the proportion of the debt that is being bought and
                    # transfer the equivalent shares. We assume this is being used
                    # due to strategy issues so won't rely on its conversion rates.
                    shares: uint256 = IStrategy(strategy).balanceOf(self) * _amount / current_debt
                
                    assert shares > 0, "cannot buy zero"
                
                    self._erc20_safe_transfer_from(self.asset, msg.sender, self, _amount)
                
                    # Lower strategy debt
                    self.strategies[strategy].current_debt -= _amount
                    # lower total debt
                    self.total_debt -= _amount
                    # Increase total idle
                    self.total_idle += _amount
                
                    # log debt change
                    log DebtUpdated(strategy, current_debt, current_debt - _amount)
                
                    # Transfer the strategies shares out.
                    self._erc20_safe_transfer(strategy, msg.sender, shares)
                
                    log DebtPurchased(strategy, _amount)
                
                ## STRATEGY MANAGEMENT ##
                @external
                def add_strategy(new_strategy: address, add_to_queue: bool=True):
                    """
                    @notice Add a new strategy.
                    @param new_strategy The new strategy to add.
                    """
                    self._enforce_role(msg.sender, Roles.ADD_STRATEGY_MANAGER)
                    self._add_strategy(new_strategy, add_to_queue)
                
                @external
                def revoke_strategy(strategy: address):
                    """
                    @notice Revoke a strategy.
                    @param strategy The strategy to revoke.
                    """
                    self._enforce_role(msg.sender, Roles.REVOKE_STRATEGY_MANAGER)
                    self._revoke_strategy(strategy)
                
                @external
                def force_revoke_strategy(strategy: address):
                    """
                    @notice Force revoke a strategy.
                    @dev The vault will remove the strategy and write off any debt left 
                        in it as a loss. This function is a dangerous function as it can force a 
                        strategy to take a loss. All possible assets should be removed from the 
                        strategy first via update_debt. If a strategy is removed erroneously it 
                        can be re-added and the loss will be credited as profit. Fees will apply.
                    @param strategy The strategy to force revoke.
                    """
                    self._enforce_role(msg.sender, Roles.FORCE_REVOKE_MANAGER)
                    self._revoke_strategy(strategy, True)
                
                ## DEBT MANAGEMENT ##
                @external
                def update_max_debt_for_strategy(strategy: address, new_max_debt: uint256):
                    """
                    @notice Update the max debt for a strategy.
                    @param strategy The strategy to update the max debt for.
                    @param new_max_debt The new max debt for the strategy.
                    """
                    self._enforce_role(msg.sender, Roles.MAX_DEBT_MANAGER)
                    assert self.strategies[strategy].activation != 0, "inactive strategy"
                    self.strategies[strategy].max_debt = new_max_debt
                
                    log UpdatedMaxDebtForStrategy(msg.sender, strategy, new_max_debt)
                
                @external
                @nonreentrant("lock")
                def update_debt(
                    strategy: address, 
                    target_debt: uint256, 
                    max_loss: uint256 = MAX_BPS
                ) -> uint256:
                    """
                    @notice Update the debt for a strategy.
                    @param strategy The strategy to update the debt for.
                    @param target_debt The target debt for the strategy.
                    @param max_loss Optional to check realized losses on debt decreases.
                    @return The amount of debt added or removed.
                    """
                    self._enforce_role(msg.sender, Roles.DEBT_MANAGER)
                    return self._update_debt(strategy, target_debt, max_loss)
                
                ## EMERGENCY MANAGEMENT ##
                @external
                def shutdown_vault():
                    """
                    @notice Shutdown the vault.
                    """
                    self._enforce_role(msg.sender, Roles.EMERGENCY_MANAGER)
                    assert self.shutdown == False
                    
                    # Shutdown the vault.
                    self.shutdown = True
                
                    # Set deposit limit to 0.
                    if self.deposit_limit_module != empty(address):
                        self.deposit_limit_module = empty(address)
                
                        log UpdateDepositLimitModule(empty(address))
                
                    self.deposit_limit = 0
                    log UpdateDepositLimit(0)
                
                    self.roles[msg.sender] = self.roles[msg.sender] | Roles.DEBT_MANAGER
                    log Shutdown()
                
                
                ## SHARE MANAGEMENT ##
                ## ERC20 + ERC4626 ##
                @external
                @nonreentrant("lock")
                def deposit(assets: uint256, receiver: address) -> uint256:
                    """
                    @notice Deposit assets into the vault.
                    @param assets The amount of assets to deposit.
                    @param receiver The address to receive the shares.
                    @return The amount of shares minted.
                    """
                    return self._deposit(msg.sender, receiver, assets)
                
                @external
                @nonreentrant("lock")
                def mint(shares: uint256, receiver: address) -> uint256:
                    """
                    @notice Mint shares for the receiver.
                    @param shares The amount of shares to mint.
                    @param receiver The address to receive the shares.
                    @return The amount of assets deposited.
                    """
                    return self._mint(msg.sender, receiver, shares)
                
                @external
                @nonreentrant("lock")
                def withdraw(
                    assets: uint256, 
                    receiver: address, 
                    owner: address, 
                    max_loss: uint256 = 0,
                    strategies: DynArray[address, MAX_QUEUE] = []
                ) -> uint256:
                    """
                    @notice Withdraw an amount of asset to `receiver` burning `owner`s shares.
                    @dev The default behavior is to not allow any loss.
                    @param assets The amount of asset to withdraw.
                    @param receiver The address to receive the assets.
                    @param owner The address who's shares are being burnt.
                    @param max_loss Optional amount of acceptable loss in Basis Points.
                    @param strategies Optional array of strategies to withdraw from.
                    @return The amount of shares actually burnt.
                    """
                    shares: uint256 = self._convert_to_shares(assets, Rounding.ROUND_UP)
                    self._redeem(msg.sender, receiver, owner, assets, shares, max_loss, strategies)
                    return shares
                
                @external
                @nonreentrant("lock")
                def redeem(
                    shares: uint256, 
                    receiver: address, 
                    owner: address, 
                    max_loss: uint256 = MAX_BPS,
                    strategies: DynArray[address, MAX_QUEUE] = []
                ) -> uint256:
                    """
                    @notice Redeems an amount of shares of `owners` shares sending funds to `receiver`.
                    @dev The default behavior is to allow losses to be realized.
                    @param shares The amount of shares to burn.
                    @param receiver The address to receive the assets.
                    @param owner The address who's shares are being burnt.
                    @param max_loss Optional amount of acceptable loss in Basis Points.
                    @param strategies Optional array of strategies to withdraw from.
                    @return The amount of assets actually withdrawn.
                    """
                    assets: uint256 = self._convert_to_assets(shares, Rounding.ROUND_DOWN)
                    # Always return the actual amount of assets withdrawn.
                    return self._redeem(msg.sender, receiver, owner, assets, shares, max_loss, strategies)
                
                
                @external
                def approve(spender: address, amount: uint256) -> bool:
                    """
                    @notice Approve an address to spend the vault's shares.
                    @param spender The address to approve.
                    @param amount The amount of shares to approve.
                    @return True if the approval was successful.
                    """
                    return self._approve(msg.sender, spender, amount)
                
                @external
                def transfer(receiver: address, amount: uint256) -> bool:
                    """
                    @notice Transfer shares to a receiver.
                    @param receiver The address to transfer shares to.
                    @param amount The amount of shares to transfer.
                    @return True if the transfer was successful.
                    """
                    assert receiver not in [self, empty(address)]
                    self._transfer(msg.sender, receiver, amount)
                    return True
                
                @external
                def transferFrom(sender: address, receiver: address, amount: uint256) -> bool:
                    """
                    @notice Transfer shares from a sender to a receiver.
                    @param sender The address to transfer shares from.
                    @param receiver The address to transfer shares to.
                    @param amount The amount of shares to transfer.
                    @return True if the transfer was successful.
                    """
                    assert receiver not in [self, empty(address)]
                    return self._transfer_from(sender, receiver, amount)
                
                ## ERC20+4626 compatibility
                @external
                def permit(
                    owner: address, 
                    spender: address, 
                    amount: uint256, 
                    deadline: uint256, 
                    v: uint8, 
                    r: bytes32, 
                    s: bytes32
                ) -> bool:
                    """
                    @notice Approve an address to spend the vault's shares.
                    @param owner The address to approve.
                    @param spender The address to approve.
                    @param amount The amount of shares to approve.
                    @param deadline The deadline for the permit.
                    @param v The v component of the signature.
                    @param r The r component of the signature.
                    @param s The s component of the signature.
                    @return True if the approval was successful.
                    """
                    return self._permit(owner, spender, amount, deadline, v, r, s)
                
                @view
                @external
                def balanceOf(addr: address) -> uint256:
                    """
                    @notice Get the balance of a user.
                    @param addr The address to get the balance of.
                    @return The balance of the user.
                    """
                    if(addr == self):
                        # If the address is the vault, account for locked shares.
                        return self.balance_of[addr] - self._unlocked_shares()
                
                    return self.balance_of[addr]
                
                @view
                @external
                def totalSupply() -> uint256:
                    """
                    @notice Get the total supply of shares.
                    @return The total supply of shares.
                    """
                    return self._total_supply()
                
                @view
                @external
                def totalAssets() -> uint256:
                    """
                    @notice Get the total assets held by the vault.
                    @return The total assets held by the vault.
                    """
                    return self._total_assets()
                
                @view
                @external
                def totalIdle() -> uint256:
                    """
                    @notice Get the amount of loose `asset` the vault holds.
                    @return The current total idle.
                    """
                    return self.total_idle
                
                @view
                @external
                def totalDebt() -> uint256:
                    """
                    @notice Get the the total amount of funds invested
                    across all strategies.
                    @return The current total debt.
                    """
                    return self.total_debt
                
                @view
                @external
                def convertToShares(assets: uint256) -> uint256:
                    """
                    @notice Convert an amount of assets to shares.
                    @param assets The amount of assets to convert.
                    @return The amount of shares.
                    """
                    return self._convert_to_shares(assets, Rounding.ROUND_DOWN)
                
                @view
                @external
                def previewDeposit(assets: uint256) -> uint256:
                    """
                    @notice Preview the amount of shares that would be minted for a deposit.
                    @param assets The amount of assets to deposit.
                    @return The amount of shares that would be minted.
                    """
                    return self._convert_to_shares(assets, Rounding.ROUND_DOWN)
                
                @view
                @external
                def previewMint(shares: uint256) -> uint256:
                    """
                    @notice Preview the amount of assets that would be deposited for a mint.
                    @param shares The amount of shares to mint.
                    @return The amount of assets that would be deposited.
                    """
                    return self._convert_to_assets(shares, Rounding.ROUND_UP)
                
                @view
                @external
                def convertToAssets(shares: uint256) -> uint256:
                    """
                    @notice Convert an amount of shares to assets.
                    @param shares The amount of shares to convert.
                    @return The amount of assets.
                    """
                    return self._convert_to_assets(shares, Rounding.ROUND_DOWN)
                
                @view
                @external
                def maxDeposit(receiver: address) -> uint256:
                    """
                    @notice Get the maximum amount of assets that can be deposited.
                    @param receiver The address that will receive the shares.
                    @return The maximum amount of assets that can be deposited.
                    """
                    return self._max_deposit(receiver)
                
                @view
                @external
                def maxMint(receiver: address) -> uint256:
                    """
                    @notice Get the maximum amount of shares that can be minted.
                    @param receiver The address that will receive the shares.
                    @return The maximum amount of shares that can be minted.
                    """
                    max_deposit: uint256 = self._max_deposit(receiver)
                    return self._convert_to_shares(max_deposit, Rounding.ROUND_DOWN)
                
                @view
                @external
                def maxWithdraw(
                    owner: address,
                    max_loss: uint256 = 0,
                    strategies: DynArray[address, MAX_QUEUE] = []
                ) -> uint256:
                    """
                    @notice Get the maximum amount of assets that can be withdrawn.
                    @dev Complies to normal 4626 interface and takes custom params.
                    NOTE: Passing in a incorrectly ordered queue may result in
                     incorrect returns values.
                    @param owner The address that owns the shares.
                    @param max_loss Custom max_loss if any.
                    @param strategies Custom strategies queue if any.
                    @return The maximum amount of assets that can be withdrawn.
                    """
                    return self._max_withdraw(owner, max_loss, strategies)
                
                @view
                @external
                def maxRedeem(
                    owner: address,
                    max_loss: uint256 = MAX_BPS,
                    strategies: DynArray[address, MAX_QUEUE] = []
                ) -> uint256:
                    """
                    @notice Get the maximum amount of shares that can be redeemed.
                    @dev Complies to normal 4626 interface and takes custom params.
                    NOTE: Passing in a incorrectly ordered queue may result in
                     incorrect returns values.
                    @param owner The address that owns the shares.
                    @param max_loss Custom max_loss if any.
                    @param strategies Custom strategies queue if any.
                    @return The maximum amount of shares that can be redeemed.
                    """
                    return min(
                        # Min of the shares equivalent of max_withdraw or the full balance
                        self._convert_to_shares(self._max_withdraw(owner, max_loss, strategies), Rounding.ROUND_DOWN),
                        self.balance_of[owner]
                    )
                
                @view
                @external
                def previewWithdraw(assets: uint256) -> uint256:
                    """
                    @notice Preview the amount of shares that would be redeemed for a withdraw.
                    @param assets The amount of assets to withdraw.
                    @return The amount of shares that would be redeemed.
                    """
                    return self._convert_to_shares(assets, Rounding.ROUND_UP)
                
                @view
                @external
                def previewRedeem(shares: uint256) -> uint256:
                    """
                    @notice Preview the amount of assets that would be withdrawn for a redeem.
                    @param shares The amount of shares to redeem.
                    @return The amount of assets that would be withdrawn.
                    """
                    return self._convert_to_assets(shares, Rounding.ROUND_DOWN)
                
                @view
                @external
                def FACTORY() -> address:
                    """
                    @notice Address of the factory that deployed the vault.
                    @dev Is used to retrieve the protocol fees.
                    @return Address of the vault factory.
                    """
                    return self.factory
                
                @view
                @external
                def apiVersion() -> String[28]:
                    """
                    @notice Get the API version of the vault.
                    @return The API version of the vault.
                    """
                    return API_VERSION
                
                @view
                @external
                def assess_share_of_unrealised_losses(strategy: address, assets_needed: uint256) -> uint256:
                    """
                    @notice Assess the share of unrealised losses that a strategy has.
                    @param strategy The address of the strategy.
                    @param assets_needed The amount of assets needed to be withdrawn.
                    @return The share of unrealised losses that the strategy has.
                    """
                    assert self.strategies[strategy].current_debt >= assets_needed
                
                    return self._assess_share_of_unrealised_losses(strategy, assets_needed)
                
                ## Profit locking getter functions ##
                
                @view
                @external
                def profitMaxUnlockTime() -> uint256:
                    """
                    @notice Gets the current time profits are set to unlock over.
                    @return The current profit max unlock time.
                    """
                    return self.profit_max_unlock_time
                
                @view
                @external
                def fullProfitUnlockDate() -> uint256:
                    """
                    @notice Gets the timestamp at which all profits will be unlocked.
                    @return The full profit unlocking timestamp
                    """
                    return self.full_profit_unlock_date
                
                @view
                @external
                def profitUnlockingRate() -> uint256:
                    """
                    @notice The per second rate at which profits are unlocking.
                    @dev This is denominated in EXTENDED_BPS decimals.
                    @return The current profit unlocking rate.
                    """
                    return self.profit_unlocking_rate
                
                
                @view
                @external
                def lastProfitUpdate() -> uint256:
                    """
                    @notice The timestamp of the last time shares were locked.
                    @return The last profit update.
                    """
                    return self.last_profit_update
                
                # eip-1344
                @view
                @internal
                def domain_separator() -> bytes32:
                    return keccak256(
                        concat(
                            DOMAIN_TYPE_HASH,
                            keccak256(convert("Yearn Vault", Bytes[11])),
                            keccak256(convert(API_VERSION, Bytes[28])),
                            convert(chain.id, bytes32),
                            convert(self, bytes32)
                        )
                    )
                
                @view
                @external
                def DOMAIN_SEPARATOR() -> bytes32:
                    """
                    @notice Get the domain separator.
                    @return The domain separator.
                    """
                    return self.domain_separator()