ETH Price: $1,975.71 (+0.36%)

Transaction Decoder

Block:
24463162 at Feb-15-2026 03:40:35 PM +UTC
Transaction Fee:
0.000013330512097742 ETH $0.03
Gas Used:
208,537 Gas / 0.063923966 Gwei

Emitted Events:

340 Yearn V3 Vault.Transfer( sender=0xD5AAdCD054a00Eff71BCB922CDdc70752FA2bc68, receiver=0x0000000000000000000000000000000000000000, value=8792398 )
341 FiatTokenProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000be53a109b494e5c9f97b9cd39fe969be68bf6204, 0x000000000000000000000000d5aadcd054a00eff71bcb922cddc70752fa2bc68, 0000000000000000000000000000000000000000000000000000000000933b99 )
342 Yearn V3 Vault.Withdraw( sender=0xD5AAdCD054a00Eff71BCB922CDdc70752FA2bc68, receiver=0xD5AAdCD054a00Eff71BCB922CDdc70752FA2bc68, owner=0xD5AAdCD054a00Eff71BCB922CDdc70752FA2bc68, assets=9649049, shares=8792398 )
343 FiatTokenProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000d5aadcd054a00eff71bcb922cddc70752fa2bc68, 0x000000000000000000000000a354f35829ae975e850e23e9615b11da1b3dc4de, 0000000000000000000000000000000000000000000000000000000000933b99 )
344 Vyper_contract.Transfer( sender=[Sender] 0x99b17456ed9c3eb3e2f4f292bcb515f8bb9f4640, receiver=0x0000000000000000000000000000000000000000, value=8260971 )
345 FiatTokenProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000a354f35829ae975e850e23e9615b11da1b3dc4de, 0x00000000000000000000000099b17456ed9c3eb3e2f4f292bcb515f8bb9f4640, 0000000000000000000000000000000000000000000000000000000000933b99 )

Account State Difference:

  Address   Before After State Difference Code
(Titan Builder)
16.435540718516783055 Eth16.435546713955533055 Eth0.00000599543875
0x99b17456...8bb9F4640
0.002698609390584032 Eth
Nonce: 718
0.00268527887848629 Eth
Nonce: 719
0.000013330512097742
0xA0b86991...E3606eB48
0xa354F358...a1B3dC4DE
0xBe53A109...E68BF6204

Execution Trace

Vyper_contract.CALL( )
  • Vyper_contract.DELEGATECALL( )
    • FiatTokenProxy.70a08231( )
      • FiatTokenV2_2.balanceOf( account=0xa354F35829Ae975e850e23e9615b11Da1B3dC4DE ) => ( 0 )
      • FiatTokenProxy.70a08231( )
        • FiatTokenV2_2.balanceOf( account=0xa354F35829Ae975e850e23e9615b11Da1B3dC4DE ) => ( 0 )
        • FiatTokenProxy.70a08231( )
          • FiatTokenV2_2.balanceOf( account=0xa354F35829Ae975e850e23e9615b11Da1B3dC4DE ) => ( 0 )
          • 0xd5aadcd054a00eff71bcb922cddc70752fa2bc68.2e1a7d4d( )
            • 0xa7496763f035e73ec6b56f4c86312cc1874a01e3.2e1a7d4d( )
              • FiatTokenProxy.70a08231( )
                • FiatTokenV2_2.balanceOf( account=0xD5AAdCD054a00Eff71BCB922CDdc70752FA2bc68 ) => ( 0 )
                • V3.previewWithdraw( assets=9649049 ) => ( 8792398 )
                  • V3.previewWithdraw( assets=9649049 ) => ( 8792398 )
                  • V3.balanceOf( addr=0xD5AAdCD054a00Eff71BCB922CDdc70752FA2bc68 ) => ( 6116913431932 )
                    • V3.balanceOf( addr=0xD5AAdCD054a00Eff71BCB922CDdc70752FA2bc68 ) => ( 6116913431932 )
                    • V3.redeem( shares=8792398, receiver=0xD5AAdCD054a00Eff71BCB922CDdc70752FA2bc68, owner=0xD5AAdCD054a00Eff71BCB922CDdc70752FA2bc68, max_loss=0 ) => ( 9649049 )
                      • V3.redeem( shares=8792398, receiver=0xD5AAdCD054a00Eff71BCB922CDdc70752FA2bc68, owner=0xD5AAdCD054a00Eff71BCB922CDdc70752FA2bc68, max_loss=0 ) => ( 9649049 )
                        • Null: 0x000...004.STATICCALL( )
                        • FiatTokenProxy.a9059cbb( )
                        • FiatTokenProxy.70a08231( )
                          • FiatTokenV2_2.balanceOf( account=0xD5AAdCD054a00Eff71BCB922CDdc70752FA2bc68 ) => ( 9649049 )
                          • FiatTokenProxy.a9059cbb( )
                            • FiatTokenV2_2.transfer( to=0xa354F35829Ae975e850e23e9615b11Da1B3dC4DE, value=9649049 ) => ( True )
                            • FiatTokenProxy.70a08231( )
                              • FiatTokenV2_2.balanceOf( account=0xa354F35829Ae975e850e23e9615b11Da1B3dC4DE ) => ( 9649049 )
                              • FiatTokenProxy.70a08231( )
                                • FiatTokenV2_2.balanceOf( account=0xa354F35829Ae975e850e23e9615b11Da1B3dC4DE ) => ( 9649049 )
                                • Null: 0x000...004.CALL( )
                                • Null: 0x000...004.00000000( )
                                • FiatTokenProxy.a9059cbb( )
                                  • FiatTokenV2_2.transfer( to=0x99b17456eD9C3EB3E2F4f292bCB515F8bb9F4640, value=9649049 ) => ( True )
                                  • Null: 0x000...004.00000000( )
                                    File 1 of 6: Vyper_contract
                                    # @version 0.2.12
                                    """
                                    @title Yearn Token Vault
                                    @license GNU AGPLv3
                                    @author yearn.finance
                                    @notice
                                        Yearn Token Vault. Holds an underlying token, and allows users to interact
                                        with the Yearn ecosystem through Strategies connected to the Vault.
                                        Vaults are not limited to a single Strategy, they can have as many Strategies
                                        as can be designed (however the withdrawal queue is capped at 20.)
                                    
                                        Deposited funds are moved into the most impactful strategy that has not
                                        already reached its limit for assets under management, regardless of which
                                        Strategy a user's funds end up in, they receive their portion of yields
                                        generated across all Strategies.
                                    
                                        When a user withdraws, if there are no funds sitting undeployed in the
                                        Vault, the Vault withdraws funds from Strategies in the order of least
                                        impact. (Funds are taken from the Strategy that will disturb everyone's
                                        gains the least, then the next least, etc.) In order to achieve this, the
                                        withdrawal queue's order must be properly set and managed by the community
                                        (through governance).
                                    
                                        Vault Strategies are parameterized to pursue the highest risk-adjusted yield.
                                    
                                        There is an "Emergency Shutdown" mode. When the Vault is put into emergency
                                        shutdown, assets will be recalled from the Strategies as quickly as is
                                        practical (given on-chain conditions), minimizing loss. Deposits are
                                        halted, new Strategies may not be added, and each Strategy exits with the
                                        minimum possible damage to position, while opening up deposits to be
                                        withdrawn by users. There are no restrictions on withdrawals above what is
                                        expected under Normal Operation.
                                    
                                        For further details, please refer to the specification:
                                        https://github.com/iearn-finance/yearn-vaults/blob/main/SPECIFICATION.md
                                    """
                                    
                                    API_VERSION: constant(String[28]) = "0.4.3"
                                    
                                    from vyper.interfaces import ERC20
                                    
                                    implements: ERC20
                                    
                                    
                                    interface DetailedERC20:
                                        def name() -> String[42]: view
                                        def symbol() -> String[20]: view
                                        def decimals() -> uint256: view
                                    
                                    
                                    interface Strategy:
                                        def want() -> address: view
                                        def vault() -> address: view
                                        def isActive() -> bool: view
                                        def delegatedAssets() -> uint256: view
                                        def estimatedTotalAssets() -> uint256: view
                                        def withdraw(_amount: uint256) -> uint256: nonpayable
                                        def migrate(_newStrategy: address): nonpayable
                                    
                                    
                                    event Transfer:
                                        sender: indexed(address)
                                        receiver: indexed(address)
                                        value: uint256
                                    
                                    
                                    event Approval:
                                        owner: indexed(address)
                                        spender: indexed(address)
                                        value: uint256
                                    
                                    
                                    name: public(String[64])
                                    symbol: public(String[32])
                                    decimals: public(uint256)
                                    
                                    balanceOf: public(HashMap[address, uint256])
                                    allowance: public(HashMap[address, HashMap[address, uint256]])
                                    totalSupply: public(uint256)
                                    
                                    token: public(ERC20)
                                    governance: public(address)
                                    management: public(address)
                                    guardian: public(address)
                                    pendingGovernance: address
                                    
                                    struct StrategyParams:
                                        performanceFee: uint256  # Strategist's fee (basis points)
                                        activation: uint256  # Activation block.timestamp
                                        debtRatio: uint256  # Maximum borrow amount (in BPS of total assets)
                                        minDebtPerHarvest: uint256  # Lower limit on the increase of debt since last harvest
                                        maxDebtPerHarvest: uint256  # Upper limit on the increase of debt since last harvest
                                        lastReport: uint256  # block.timestamp of the last time a report occured
                                        totalDebt: uint256  # Total outstanding debt that Strategy has
                                        totalGain: uint256  # Total returns that Strategy has realized for Vault
                                        totalLoss: uint256  # Total losses that Strategy has realized for Vault
                                    
                                    
                                    event StrategyAdded:
                                        strategy: indexed(address)
                                        debtRatio: uint256  # Maximum borrow amount (in BPS of total assets)
                                        minDebtPerHarvest: uint256  # Lower limit on the increase of debt since last harvest
                                        maxDebtPerHarvest: uint256  # Upper limit on the increase of debt since last harvest
                                        performanceFee: uint256  # Strategist's fee (basis points)
                                    
                                    
                                    event StrategyReported:
                                        strategy: indexed(address)
                                        gain: uint256
                                        loss: uint256
                                        debtPaid: uint256
                                        totalGain: uint256
                                        totalLoss: uint256
                                        totalDebt: uint256
                                        debtAdded: uint256
                                        debtRatio: uint256
                                    
                                    
                                    event UpdateGovernance:
                                        governance: address # New active governance
                                    
                                    
                                    event UpdateManagement:
                                        management: address # New active manager
                                    
                                    event UpdateRewards:
                                        rewards: address # New active rewards recipient
                                    
                                    
                                    event UpdateDepositLimit:
                                        depositLimit: uint256 # New active deposit limit
                                    
                                    
                                    event UpdatePerformanceFee:
                                        performanceFee: uint256 # New active performance fee
                                    
                                    
                                    event UpdateManagementFee:
                                        managementFee: uint256 # New active management fee
                                    
                                    
                                    event UpdateGuardian:
                                        guardian: address # Address of the active guardian
                                    
                                    
                                    event EmergencyShutdown:
                                        active: bool # New emergency shutdown state (if false, normal operation enabled)
                                    
                                    
                                    event UpdateWithdrawalQueue:
                                        queue: address[MAXIMUM_STRATEGIES] # New active withdrawal queue
                                    
                                    
                                    event StrategyUpdateDebtRatio:
                                        strategy: indexed(address) # Address of the strategy for the debt ratio adjustment
                                        debtRatio: uint256 # The new debt limit for the strategy (in BPS of total assets)
                                    
                                    
                                    event StrategyUpdateMinDebtPerHarvest:
                                        strategy: indexed(address) # Address of the strategy for the rate limit adjustment
                                        minDebtPerHarvest: uint256  # Lower limit on the increase of debt since last harvest
                                    
                                    
                                    event StrategyUpdateMaxDebtPerHarvest:
                                        strategy: indexed(address) # Address of the strategy for the rate limit adjustment
                                        maxDebtPerHarvest: uint256  # Upper limit on the increase of debt since last harvest
                                    
                                    
                                    event StrategyUpdatePerformanceFee:
                                        strategy: indexed(address) # Address of the strategy for the performance fee adjustment
                                        performanceFee: uint256 # The new performance fee for the strategy
                                    
                                    
                                    event StrategyMigrated:
                                        oldVersion: indexed(address) # Old version of the strategy to be migrated
                                        newVersion: indexed(address) # New version of the strategy
                                    
                                    
                                    event StrategyRevoked:
                                        strategy: indexed(address) # Address of the strategy that is revoked
                                    
                                    
                                    event StrategyRemovedFromQueue:
                                        strategy: indexed(address) # Address of the strategy that is removed from the withdrawal queue
                                    
                                    
                                    event StrategyAddedToQueue:
                                        strategy: indexed(address) # Address of the strategy that is added to the withdrawal queue
                                    
                                    
                                    # NOTE: Track the total for overhead targeting purposes
                                    strategies: public(HashMap[address, StrategyParams])
                                    MAXIMUM_STRATEGIES: constant(uint256) = 20
                                    DEGRADATION_COEFFICIENT: constant(uint256) = 10 ** 18
                                    
                                    # Ordering that `withdraw` uses to determine which strategies to pull funds from
                                    # NOTE: Does *NOT* have to match the ordering of all the current strategies that
                                    #       exist, but it is recommended that it does or else withdrawal depth is
                                    #       limited to only those inside the queue.
                                    # NOTE: Ordering is determined by governance, and should be balanced according
                                    #       to risk, slippage, and/or volatility. Can also be ordered to increase the
                                    #       withdrawal speed of a particular Strategy.
                                    # NOTE: The first time a ZERO_ADDRESS is encountered, it stops withdrawing
                                    withdrawalQueue: public(address[MAXIMUM_STRATEGIES])
                                    
                                    emergencyShutdown: public(bool)
                                    
                                    depositLimit: public(uint256)  # Limit for totalAssets the Vault can hold
                                    debtRatio: public(uint256)  # Debt ratio for the Vault across all strategies (in BPS, <= 10k)
                                    totalDebt: public(uint256)  # Amount of tokens that all strategies have borrowed
                                    lastReport: public(uint256)  # block.timestamp of last report
                                    activation: public(uint256)  # block.timestamp of contract deployment
                                    lockedProfit: public(uint256) # how much profit is locked and cant be withdrawn
                                    lockedProfitDegradation: public(uint256) # rate per block of degradation. DEGRADATION_COEFFICIENT is 100% per block
                                    rewards: public(address)  # Rewards contract where Governance fees are sent to
                                    # Governance Fee for management of Vault (given to `rewards`)
                                    managementFee: public(uint256)
                                    # Governance Fee for performance of Vault (given to `rewards`)
                                    performanceFee: public(uint256)
                                    MAX_BPS: constant(uint256) = 10_000  # 100%, or 10k basis points
                                    # NOTE: A four-century period will be missing 3 of its 100 Julian leap years, leaving 97.
                                    #       So the average year has 365 + 97/400 = 365.2425 days
                                    #       ERROR(Julian): -0.0078
                                    #       ERROR(Gregorian): -0.0003
                                    #       A day = 24 * 60 * 60 sec = 86400 sec
                                    #       365.2425 * 86400 = 31556952.0
                                    SECS_PER_YEAR: constant(uint256) = 31_556_952  # 365.2425 days
                                    # `nonces` track `permit` approvals with signature.
                                    nonces: public(HashMap[address, uint256])
                                    DOMAIN_SEPARATOR: public(bytes32)
                                    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)")
                                    
                                    
                                    @external
                                    def initialize(
                                        token: address,
                                        governance: address,
                                        rewards: address,
                                        nameOverride: String[64],
                                        symbolOverride: String[32],
                                        guardian: address = msg.sender,
                                        management: address =  msg.sender,
                                    ):
                                        """
                                        @notice
                                            Initializes the Vault, this is called only once, when the contract is
                                            deployed.
                                            The performance fee is set to 10% of yield, per Strategy.
                                            The management fee is set to 2%, per year.
                                            The initial deposit limit is set to 0 (deposits disabled); it must be
                                            updated after initialization.
                                        @dev
                                            If `nameOverride` is not specified, the name will be 'yearn'
                                            combined with the name of `token`.
                                    
                                            If `symbolOverride` is not specified, the symbol will be 'yv'
                                            combined with the symbol of `token`.
                                    
                                            The token used by the vault should not change balances outside transfers and 
                                            it must transfer the exact amount requested. Fee on transfer and rebasing are not supported.
                                        @param token The token that may be deposited into this Vault.
                                        @param governance The address authorized for governance interactions.
                                        @param rewards The address to distribute rewards to.
                                        @param management The address of the vault manager.
                                        @param nameOverride Specify a custom Vault name. Leave empty for default choice.
                                        @param symbolOverride Specify a custom Vault symbol name. Leave empty for default choice.
                                        @param guardian The address authorized for guardian interactions. Defaults to caller.
                                        """
                                        assert self.activation == 0  # dev: no devops199
                                        self.token = ERC20(token)
                                        if nameOverride == "":
                                            self.name = concat(DetailedERC20(token).symbol(), " yVault")
                                        else:
                                            self.name = nameOverride
                                        if symbolOverride == "":
                                            self.symbol = concat("yv", DetailedERC20(token).symbol())
                                        else:
                                            self.symbol = symbolOverride
                                        decimals: uint256 = DetailedERC20(token).decimals()
                                        self.decimals = decimals
                                        assert decimals < 256 # dev: see VVE-2020-0001
                                    
                                        self.governance = governance
                                        log UpdateGovernance(governance)
                                        self.management = management
                                        log UpdateManagement(management)
                                        self.rewards = rewards
                                        log UpdateRewards(rewards)
                                        self.guardian = guardian
                                        log UpdateGuardian(guardian)
                                        self.performanceFee = 1000  # 10% of yield (per Strategy)
                                        log UpdatePerformanceFee(convert(1000, uint256))
                                        self.managementFee = 200  # 2% per year
                                        log UpdateManagementFee(convert(200, uint256))
                                        self.lastReport = block.timestamp
                                        self.activation = block.timestamp
                                        self.lockedProfitDegradation = convert(DEGRADATION_COEFFICIENT * 46 / 10 ** 6 , uint256) # 6 hours in blocks
                                        # EIP-712
                                        self.DOMAIN_SEPARATOR = keccak256(
                                            concat(
                                                DOMAIN_TYPE_HASH,
                                                keccak256(convert("Yearn Vault", Bytes[11])),
                                                keccak256(convert(API_VERSION, Bytes[28])),
                                                convert(chain.id, bytes32),
                                                convert(self, bytes32)
                                            )
                                        )
                                    
                                    
                                    @pure
                                    @external
                                    def apiVersion() -> String[28]:
                                        """
                                        @notice
                                            Used to track the deployed version of this contract. In practice you
                                            can use this version number to compare with Yearn's GitHub and
                                            determine which version of the source matches this deployed contract.
                                        @dev
                                            All strategies must have an `apiVersion()` that matches the Vault's
                                            `API_VERSION`.
                                        @return API_VERSION which holds the current version of this contract.
                                        """
                                        return API_VERSION
                                    
                                    
                                    @external
                                    def setName(name: String[42]):
                                        """
                                        @notice
                                            Used to change the value of `name`.
                                    
                                            This may only be called by governance.
                                        @param name The new name to use.
                                        """
                                        assert msg.sender == self.governance
                                        self.name = name
                                    
                                    
                                    @external
                                    def setSymbol(symbol: String[20]):
                                        """
                                        @notice
                                            Used to change the value of `symbol`.
                                    
                                            This may only be called by governance.
                                        @param symbol The new symbol to use.
                                        """
                                        assert msg.sender == self.governance
                                        self.symbol = symbol
                                    
                                    
                                    # 2-phase commit for a change in governance
                                    @external
                                    def setGovernance(governance: address):
                                        """
                                        @notice
                                            Nominate a new address to use as governance.
                                    
                                            The change does not go into effect immediately. This function sets a
                                            pending change, and the governance address is not updated until
                                            the proposed governance address has accepted the responsibility.
                                    
                                            This may only be called by the current governance address.
                                        @param governance The address requested to take over Vault governance.
                                        """
                                        assert msg.sender == self.governance
                                        self.pendingGovernance = governance
                                    
                                    
                                    @external
                                    def acceptGovernance():
                                        """
                                        @notice
                                            Once a new governance address has been proposed using setGovernance(),
                                            this function may be called by the proposed address to accept the
                                            responsibility of taking over governance for this contract.
                                    
                                            This may only be called by the proposed governance address.
                                        @dev
                                            setGovernance() should be called by the existing governance address,
                                            prior to calling this function.
                                        """
                                        assert msg.sender == self.pendingGovernance
                                        self.governance = msg.sender
                                        log UpdateGovernance(msg.sender)
                                    
                                    
                                    @external
                                    def setManagement(management: address):
                                        """
                                        @notice
                                            Changes the management address.
                                            Management is able to make some investment decisions adjusting parameters.
                                    
                                            This may only be called by governance.
                                        @param management The address to use for managing.
                                        """
                                        assert msg.sender == self.governance
                                        self.management = management
                                        log UpdateManagement(management)
                                    
                                    
                                    @external
                                    def setRewards(rewards: address):
                                        """
                                        @notice
                                            Changes the rewards address. Any distributed rewards
                                            will cease flowing to the old address and begin flowing
                                            to this address once the change is in effect.
                                    
                                            This will not change any Strategy reports in progress, only
                                            new reports made after this change goes into effect.
                                    
                                            This may only be called by governance.
                                        @param rewards The address to use for collecting rewards.
                                        """
                                        assert msg.sender == self.governance
                                        assert not (rewards in [self, ZERO_ADDRESS])
                                        self.rewards = rewards
                                        log UpdateRewards(rewards)
                                    
                                    
                                    @external
                                    def setLockedProfitDegradation(degradation: uint256):
                                        """
                                        @notice
                                            Changes the locked profit degradation.
                                        @param degradation The rate of degradation in percent per second scaled to 1e18.
                                        """
                                        assert msg.sender == self.governance
                                        # Since "degradation" is of type uint256 it can never be less than zero
                                        assert degradation <= DEGRADATION_COEFFICIENT
                                        self.lockedProfitDegradation = degradation
                                    
                                    
                                    @external
                                    def setDepositLimit(limit: uint256):
                                        """
                                        @notice
                                            Changes the maximum amount of tokens that can be deposited in this Vault.
                                    
                                            Note, this is not how much may be deposited by a single depositor,
                                            but the maximum amount that may be deposited across all depositors.
                                    
                                            This may only be called by governance.
                                        @param limit The new deposit limit to use.
                                        """
                                        assert msg.sender == self.governance
                                        self.depositLimit = limit
                                        log UpdateDepositLimit(limit)
                                    
                                    
                                    @external
                                    def setPerformanceFee(fee: uint256):
                                        """
                                        @notice
                                            Used to change the value of `performanceFee`.
                                    
                                            Should set this value below the maximum strategist performance fee.
                                    
                                            This may only be called by governance.
                                        @param fee The new performance fee to use.
                                        """
                                        assert msg.sender == self.governance
                                        assert fee <= MAX_BPS / 2
                                        self.performanceFee = fee
                                        log UpdatePerformanceFee(fee)
                                    
                                    
                                    @external
                                    def setManagementFee(fee: uint256):
                                        """
                                        @notice
                                            Used to change the value of `managementFee`.
                                    
                                            This may only be called by governance.
                                        @param fee The new management fee to use.
                                        """
                                        assert msg.sender == self.governance
                                        assert fee <= MAX_BPS
                                        self.managementFee = fee
                                        log UpdateManagementFee(fee)
                                    
                                    
                                    @external
                                    def setGuardian(guardian: address):
                                        """
                                        @notice
                                            Used to change the address of `guardian`.
                                    
                                            This may only be called by governance or the existing guardian.
                                        @param guardian The new guardian address to use.
                                        """
                                        assert msg.sender in [self.guardian, self.governance]
                                        self.guardian = guardian
                                        log UpdateGuardian(guardian)
                                    
                                    
                                    @external
                                    def setEmergencyShutdown(active: bool):
                                        """
                                        @notice
                                            Activates or deactivates Vault mode where all Strategies go into full
                                            withdrawal.
                                    
                                            During Emergency Shutdown:
                                            1. No Users may deposit into the Vault (but may withdraw as usual.)
                                            2. Governance may not add new Strategies.
                                            3. Each Strategy must pay back their debt as quickly as reasonable to
                                                minimally affect their position.
                                            4. Only Governance may undo Emergency Shutdown.
                                    
                                            See contract level note for further details.
                                    
                                            This may only be called by governance or the guardian.
                                        @param active
                                            If true, the Vault goes into Emergency Shutdown. If false, the Vault
                                            goes back into Normal Operation.
                                        """
                                        if active:
                                            assert msg.sender in [self.guardian, self.governance]
                                        else:
                                            assert msg.sender == self.governance
                                        self.emergencyShutdown = active
                                        log EmergencyShutdown(active)
                                    
                                    
                                    @external
                                    def setWithdrawalQueue(queue: address[MAXIMUM_STRATEGIES]):
                                        """
                                        @notice
                                            Updates the withdrawalQueue to match the addresses and order specified
                                            by `queue`.
                                    
                                            There can be fewer strategies than the maximum, as well as fewer than
                                            the total number of strategies active in the vault. `withdrawalQueue`
                                            will be updated in a gas-efficient manner, assuming the input is well-
                                            ordered with 0x0 only at the end.
                                    
                                            This may only be called by governance or management.
                                        @dev
                                            This is order sensitive, specify the addresses in the order in which
                                            funds should be withdrawn (so `queue`[0] is the first Strategy withdrawn
                                            from, `queue`[1] is the second, etc.)
                                    
                                            This means that the least impactful Strategy (the Strategy that will have
                                            its core positions impacted the least by having funds removed) should be
                                            at `queue`[0], then the next least impactful at `queue`[1], and so on.
                                        @param queue
                                            The array of addresses to use as the new withdrawal queue. This is
                                            order sensitive.
                                        """
                                        assert msg.sender in [self.management, self.governance]
                                    
                                        # HACK: Temporary until Vyper adds support for Dynamic arrays
                                        old_queue: address[MAXIMUM_STRATEGIES] = empty(address[MAXIMUM_STRATEGIES])
                                        for i in range(MAXIMUM_STRATEGIES):
                                            old_queue[i] = self.withdrawalQueue[i] 
                                            if queue[i] == ZERO_ADDRESS:
                                                # NOTE: Cannot use this method to remove entries from the queue
                                                assert old_queue[i] == ZERO_ADDRESS
                                                break
                                            # NOTE: Cannot use this method to add more entries to the queue
                                            assert old_queue[i] != ZERO_ADDRESS
                                    
                                            assert self.strategies[queue[i]].activation > 0
                                    
                                            existsInOldQueue: bool = False
                                            for j in range(MAXIMUM_STRATEGIES):
                                                if queue[j] == ZERO_ADDRESS:
                                                    existsInOldQueue = True
                                                    break
                                                if queue[i] == old_queue[j]:
                                                    # NOTE: Ensure that every entry in queue prior to reordering exists now
                                                    existsInOldQueue = True
                                    
                                                if j <= i:
                                                    # NOTE: This will only check for duplicate entries in queue after `i`
                                                    continue
                                                assert queue[i] != queue[j]  # dev: do not add duplicate strategies
                                    
                                            assert existsInOldQueue # dev: do not add new strategies
                                    
                                            self.withdrawalQueue[i] = queue[i]
                                        log UpdateWithdrawalQueue(queue)
                                    
                                    
                                    @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.
                                        # HACK: Used to handle non-compliant tokens like USDT
                                        response: Bytes[32] = raw_call(
                                            token,
                                            concat(
                                                method_id("transfer(address,uint256)"),
                                                convert(receiver, bytes32),
                                                convert(amount, bytes32),
                                            ),
                                            max_outsize=32,
                                        )
                                        if len(response) > 0:
                                            assert convert(response, bool), "Transfer failed!"
                                    
                                    
                                    @internal
                                    def erc20_safe_transferFrom(token: address, sender: address, receiver: address, amount: uint256):
                                        # Used only to send tokens that are not the type managed by this Vault.
                                        # HACK: Used to handle non-compliant tokens like USDT
                                        response: Bytes[32] = raw_call(
                                            token,
                                            concat(
                                                method_id("transferFrom(address,address,uint256)"),
                                                convert(sender, bytes32),
                                                convert(receiver, bytes32),
                                                convert(amount, bytes32),
                                            ),
                                            max_outsize=32,
                                        )
                                        if len(response) > 0:
                                            assert convert(response, bool), "Transfer failed!"
                                    
                                    
                                    @internal
                                    def _transfer(sender: address, receiver: address, amount: uint256):
                                        # See note on `transfer()`.
                                    
                                        # Protect people from accidentally sending their shares to bad places
                                        assert receiver not in [self, ZERO_ADDRESS]
                                        self.balanceOf[sender] -= amount
                                        self.balanceOf[receiver] += amount
                                        log Transfer(sender, receiver, amount)
                                    
                                    
                                    @external
                                    def transfer(receiver: address, amount: uint256) -> bool:
                                        """
                                        @notice
                                            Transfers shares from the caller's address to `receiver`. This function
                                            will always return true, unless the user is attempting to transfer
                                            shares to this contract's address, or to 0x0.
                                        @param receiver
                                            The address shares are being transferred to. Must not be this contract's
                                            address, must not be 0x0.
                                        @param amount The quantity of shares to transfer.
                                        @return
                                            True if transfer is sent to an address other than this contract's or
                                            0x0, otherwise the transaction will fail.
                                        """
                                        self._transfer(msg.sender, receiver, amount)
                                        return True
                                    
                                    
                                    @external
                                    def transferFrom(sender: address, receiver: address, amount: uint256) -> bool:
                                        """
                                        @notice
                                            Transfers `amount` shares from `sender` to `receiver`. This operation will
                                            always return true, unless the user is attempting to transfer shares
                                            to this contract's address, or to 0x0.
                                    
                                            Unless the caller has given this contract unlimited approval,
                                            transfering shares will decrement the caller's `allowance` by `amount`.
                                        @param sender The address shares are being transferred from.
                                        @param receiver
                                            The address shares are being transferred to. Must not be this contract's
                                            address, must not be 0x0.
                                        @param amount The quantity of shares to transfer.
                                        @return
                                            True if transfer is sent to an address other than this contract's or
                                            0x0, otherwise the transaction will fail.
                                        """
                                        # Unlimited approval (saves an SSTORE)
                                        if (self.allowance[sender][msg.sender] < MAX_UINT256):
                                            allowance: uint256 = self.allowance[sender][msg.sender] - amount
                                            self.allowance[sender][msg.sender] = allowance
                                            # NOTE: Allows log filters to have a full accounting of allowance changes
                                            log Approval(sender, msg.sender, allowance)
                                        self._transfer(sender, receiver, amount)
                                        return True
                                    
                                    
                                    @external
                                    def approve(spender: address, amount: uint256) -> bool:
                                        """
                                        @dev Approve the passed address to spend the specified amount of tokens on behalf of
                                             `msg.sender`. 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. See https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                                        @param spender The address which will spend the funds.
                                        @param amount The amount of tokens to be spent.
                                        """
                                        self.allowance[msg.sender][spender] = amount
                                        log Approval(msg.sender, spender, amount)
                                        return True
                                    
                                    
                                    @external
                                    def increaseAllowance(spender: address, amount: uint256) -> bool:
                                        """
                                        @dev Increase the allowance of the passed address to spend the total amount of tokens
                                             on behalf of msg.sender. This method mitigates the risk that someone may use both
                                             the old and the new allowance by unfortunate transaction ordering.
                                             See https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                                        @param spender The address which will spend the funds.
                                        @param amount The amount of tokens to increase the allowance by.
                                        """
                                        self.allowance[msg.sender][spender] += amount
                                        log Approval(msg.sender, spender, self.allowance[msg.sender][spender])
                                        return True
                                    
                                    
                                    @external
                                    def decreaseAllowance(spender: address, amount: uint256) -> bool:
                                        """
                                        @dev Decrease the allowance of the passed address to spend the total amount of tokens
                                             on behalf of msg.sender. This method mitigates the risk that someone may use both
                                             the old and the new allowance by unfortunate transaction ordering.
                                             See https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                                        @param spender The address which will spend the funds.
                                        @param amount The amount of tokens to decrease the allowance by.
                                        """
                                        self.allowance[msg.sender][spender] -= amount
                                        log Approval(msg.sender, spender, self.allowance[msg.sender][spender])
                                        return True
                                    
                                    
                                    @external
                                    def permit(owner: address, spender: address, amount: uint256, expiry: uint256, signature: Bytes[65]) -> bool:
                                        """
                                        @notice
                                            Approves spender by owner's signature to expend owner's tokens.
                                            See https://eips.ethereum.org/EIPS/eip-2612.
                                    
                                        @param owner The address which is a source of funds and has signed the Permit.
                                        @param spender The address which is allowed to spend the funds.
                                        @param amount The amount of tokens to be spent.
                                        @param expiry The timestamp after which the Permit is no longer valid.
                                        @param signature A valid secp256k1 signature of Permit by owner encoded as r, s, v.
                                        @return True, if transaction completes successfully
                                        """
                                        assert owner != ZERO_ADDRESS  # dev: invalid owner
                                        assert expiry == 0 or expiry >= block.timestamp  # dev: 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(expiry, bytes32),
                                                    )
                                                )
                                            )
                                        )
                                        # NOTE: signature is packed as r, s, v
                                        r: uint256 = convert(slice(signature, 0, 32), uint256)
                                        s: uint256 = convert(slice(signature, 32, 32), uint256)
                                        v: uint256 = convert(slice(signature, 64, 1), uint256)
                                        assert ecrecover(digest, v, r, s) == owner  # dev: invalid signature
                                        self.allowance[owner][spender] = amount
                                        self.nonces[owner] = nonce + 1
                                        log Approval(owner, spender, amount)
                                        return True
                                    
                                    
                                    @view
                                    @internal
                                    def _totalAssets() -> uint256:
                                        # See note on `totalAssets()`.
                                        return self.token.balanceOf(self) + self.totalDebt
                                    
                                    
                                    @view
                                    @external
                                    def totalAssets() -> uint256:
                                        """
                                        @notice
                                            Returns the total quantity of all assets under control of this
                                            Vault, whether they're loaned out to a Strategy, or currently held in
                                            the Vault.
                                        @return The total assets under control of this Vault.
                                        """
                                        return self._totalAssets()
                                    
                                    
                                    @view
                                    @internal
                                    def _calculateLockedProfit() -> uint256:
                                        lockedFundsRatio: uint256 = (block.timestamp - self.lastReport) * self.lockedProfitDegradation
                                    
                                        if(lockedFundsRatio < DEGRADATION_COEFFICIENT):
                                            lockedProfit: uint256 = self.lockedProfit
                                            return lockedProfit - (
                                                    lockedFundsRatio
                                                    * lockedProfit
                                                    / DEGRADATION_COEFFICIENT
                                                )
                                        else:        
                                            return 0
                                    
                                    @view
                                    @internal
                                    def _freeFunds() -> uint256:
                                        return self._totalAssets() - self._calculateLockedProfit()
                                    
                                    @internal
                                    def _issueSharesForAmount(to: address, amount: uint256) -> uint256:
                                        # Issues `amount` Vault shares to `to`.
                                        # Shares must be issued prior to taking on new collateral, or
                                        # calculation will be wrong. This means that only *trusted* tokens
                                        # (with no capability for exploitative behavior) can be used.
                                        shares: uint256 = 0
                                        # HACK: Saves 2 SLOADs (~200 gas, post-Berlin)
                                        totalSupply: uint256 = self.totalSupply
                                        if totalSupply > 0:
                                            # Mint amount of shares based on what the Vault is managing overall
                                            # NOTE: if sqrt(token.totalSupply()) > 1e39, this could potentially revert
                                            shares =  amount * totalSupply / self._freeFunds()  # dev: no free funds
                                        else:
                                            # No existing shares, so mint 1:1
                                            shares = amount
                                        assert shares != 0 # dev: division rounding resulted in zero
                                    
                                        # Mint new shares
                                        self.totalSupply = totalSupply + shares
                                        self.balanceOf[to] += shares
                                        log Transfer(ZERO_ADDRESS, to, shares)
                                    
                                        return shares
                                    
                                    
                                    @external
                                    @nonreentrant("withdraw")
                                    def deposit(_amount: uint256 = MAX_UINT256, recipient: address = msg.sender) -> uint256:
                                        """
                                        @notice
                                            Deposits `_amount` `token`, issuing shares to `recipient`. If the
                                            Vault is in Emergency Shutdown, deposits will not be accepted and this
                                            call will fail.
                                        @dev
                                            Measuring quantity of shares to issues is based on the total
                                            outstanding debt that this contract has ("expected value") instead
                                            of the total balance sheet it has ("estimated value") has important
                                            security considerations, and is done intentionally. If this value were
                                            measured against external systems, it could be purposely manipulated by
                                            an attacker to withdraw more assets than they otherwise should be able
                                            to claim by redeeming their shares.
                                    
                                            On deposit, this means that shares are issued against the total amount
                                            that the deposited capital can be given in service of the debt that
                                            Strategies assume. If that number were to be lower than the "expected
                                            value" at some future point, depositing shares via this method could
                                            entitle the depositor to *less* than the deposited value once the
                                            "realized value" is updated from further reports by the Strategies
                                            to the Vaults.
                                    
                                            Care should be taken by integrators to account for this discrepancy,
                                            by using the view-only methods of this contract (both off-chain and
                                            on-chain) to determine if depositing into the Vault is a "good idea".
                                        @param _amount The quantity of tokens to deposit, defaults to all.
                                        @param recipient
                                            The address to issue the shares in this Vault to. Defaults to the
                                            caller's address.
                                        @return The issued Vault shares.
                                        """
                                        assert not self.emergencyShutdown  # Deposits are locked out
                                        assert recipient not in [self, ZERO_ADDRESS]
                                    
                                        amount: uint256 = _amount
                                    
                                        # If _amount not specified, transfer the full token balance,
                                        # up to deposit limit
                                        if amount == MAX_UINT256:
                                            amount = min(
                                                self.depositLimit - self._totalAssets(),
                                                self.token.balanceOf(msg.sender),
                                            )
                                        else:
                                            # Ensure deposit limit is respected
                                            assert self._totalAssets() + amount <= self.depositLimit
                                    
                                        # Ensure we are depositing something
                                        assert amount > 0
                                    
                                        # Issue new shares (needs to be done before taking deposit to be accurate)
                                        # Shares are issued to recipient (may be different from msg.sender)
                                        # See @dev note, above.
                                        shares: uint256 = self._issueSharesForAmount(recipient, amount)
                                    
                                        # Tokens are transferred from msg.sender (may be different from _recipient)
                                        self.erc20_safe_transferFrom(self.token.address, msg.sender, self, amount)
                                    
                                        return shares  # Just in case someone wants them
                                    
                                    
                                    @view
                                    @internal
                                    def _shareValue(shares: uint256) -> uint256:
                                        # Returns price = 1:1 if vault is empty
                                        if self.totalSupply == 0:
                                            return shares
                                    
                                        # Determines the current value of `shares`.
                                        # NOTE: if sqrt(Vault.totalAssets()) >>> 1e39, this could potentially revert
                                    
                                        return (
                                            shares
                                            * self._freeFunds()
                                            / self.totalSupply
                                        )
                                    
                                    
                                    @view
                                    @internal
                                    def _sharesForAmount(amount: uint256) -> uint256:
                                        # Determines how many shares `amount` of token would receive.
                                        # See dev note on `deposit`.
                                        _freeFunds: uint256 = self._freeFunds()
                                        if _freeFunds > 0:
                                            # NOTE: if sqrt(token.totalSupply()) > 1e37, this could potentially revert
                                            return  (
                                                amount
                                                * self.totalSupply
                                                / _freeFunds 
                                            )
                                        else:
                                            return 0
                                    
                                    
                                    @view
                                    @external
                                    def maxAvailableShares() -> uint256:
                                        """
                                        @notice
                                            Determines the maximum quantity of shares this Vault can facilitate a
                                            withdrawal for, factoring in assets currently residing in the Vault,
                                            as well as those deployed to strategies on the Vault's balance sheet.
                                        @dev
                                            Regarding how shares are calculated, see dev note on `deposit`.
                                    
                                            If you want to calculated the maximum a user could withdraw up to,
                                            you want to use this function.
                                    
                                            Note that the amount provided by this function is the theoretical
                                            maximum possible from withdrawing, the real amount depends on the
                                            realized losses incurred during withdrawal.
                                        @return The total quantity of shares this Vault can provide.
                                        """
                                        shares: uint256 = self._sharesForAmount(self.token.balanceOf(self))
                                    
                                        for strategy in self.withdrawalQueue:
                                            if strategy == ZERO_ADDRESS:
                                                break
                                            shares += self._sharesForAmount(self.strategies[strategy].totalDebt)
                                    
                                        return shares
                                    
                                    
                                    @internal
                                    def _reportLoss(strategy: address, loss: uint256):
                                        # Loss can only be up the amount of debt issued to strategy
                                        totalDebt: uint256 = self.strategies[strategy].totalDebt
                                        assert totalDebt >= loss
                                    
                                        # Also, make sure we reduce our trust with the strategy by the amount of loss
                                        if self.debtRatio != 0: # if vault with single strategy that is set to EmergencyOne
                                            # NOTE: The context to this calculation is different than the calculation in `_reportLoss`,
                                            # this calculation intentionally approximates via `totalDebt` to avoid manipulatable results
                                            ratio_change: uint256 = min(
                                                # NOTE: This calculation isn't 100% precise, the adjustment is ~10%-20% more severe due to EVM math
                                                loss * self.debtRatio / self.totalDebt,
                                                self.strategies[strategy].debtRatio,
                                            )
                                            self.strategies[strategy].debtRatio -= ratio_change
                                            self.debtRatio -= ratio_change
                                        # Finally, adjust our strategy's parameters by the loss
                                        self.strategies[strategy].totalLoss += loss
                                        self.strategies[strategy].totalDebt = totalDebt - loss
                                        self.totalDebt -= loss
                                    
                                    
                                    @external
                                    @nonreentrant("withdraw")
                                    def withdraw(
                                        maxShares: uint256 = MAX_UINT256,
                                        recipient: address = msg.sender,
                                        maxLoss: uint256 = 1,  # 0.01% [BPS]
                                    ) -> uint256:
                                        """
                                        @notice
                                            Withdraws the calling account's tokens from this Vault, redeeming
                                            amount `_shares` for an appropriate amount of tokens.
                                    
                                            See note on `setWithdrawalQueue` for further details of withdrawal
                                            ordering and behavior.
                                        @dev
                                            Measuring the value of shares is based on the total outstanding debt
                                            that this contract has ("expected value") instead of the total balance
                                            sheet it has ("estimated value") has important security considerations,
                                            and is done intentionally. If this value were measured against external
                                            systems, it could be purposely manipulated by an attacker to withdraw
                                            more assets than they otherwise should be able to claim by redeeming
                                            their shares.
                                    
                                            On withdrawal, this means that shares are redeemed against the total
                                            amount that the deposited capital had "realized" since the point it
                                            was deposited, up until the point it was withdrawn. If that number
                                            were to be higher than the "expected value" at some future point,
                                            withdrawing shares via this method could entitle the depositor to
                                            *more* than the expected value once the "realized value" is updated
                                            from further reports by the Strategies to the Vaults.
                                    
                                            Under exceptional scenarios, this could cause earlier withdrawals to
                                            earn "more" of the underlying assets than Users might otherwise be
                                            entitled to, if the Vault's estimated value were otherwise measured
                                            through external means, accounting for whatever exceptional scenarios
                                            exist for the Vault (that aren't covered by the Vault's own design.)
                                    
                                            In the situation where a large withdrawal happens, it can empty the 
                                            vault balance and the strategies in the withdrawal queue. 
                                            Strategies not in the withdrawal queue will have to be harvested to 
                                            rebalance the funds and make the funds available again to withdraw.
                                        @param maxShares
                                            How many shares to try and redeem for tokens, defaults to all.
                                        @param recipient
                                            The address to issue the shares in this Vault to. Defaults to the
                                            caller's address.
                                        @param maxLoss
                                            The maximum acceptable loss to sustain on withdrawal. Defaults to 0.01%.
                                            If a loss is specified, up to that amount of shares may be burnt to cover losses on withdrawal.
                                        @return The quantity of tokens redeemed for `_shares`.
                                        """
                                        shares: uint256 = maxShares  # May reduce this number below
                                    
                                        # Max Loss is <=100%, revert otherwise
                                        assert maxLoss <= MAX_BPS
                                    
                                        # If _shares not specified, transfer full share balance
                                        if shares == MAX_UINT256:
                                            shares = self.balanceOf[msg.sender]
                                    
                                        # Limit to only the shares they own
                                        assert shares <= self.balanceOf[msg.sender]
                                    
                                        # Ensure we are withdrawing something
                                        assert shares > 0
                                    
                                        # See @dev note, above.
                                        value: uint256 = self._shareValue(shares)
                                    
                                        if value > self.token.balanceOf(self):
                                            totalLoss: uint256 = 0
                                            # We need to go get some from our strategies in the withdrawal queue
                                            # NOTE: This performs forced withdrawals from each Strategy. During
                                            #       forced withdrawal, a Strategy may realize a loss. That loss
                                            #       is reported back to the Vault, and the will affect the amount
                                            #       of tokens that the withdrawer receives for their shares. They
                                            #       can optionally specify the maximum acceptable loss (in BPS)
                                            #       to prevent excessive losses on their withdrawals (which may
                                            #       happen in certain edge cases where Strategies realize a loss)
                                            for strategy in self.withdrawalQueue:
                                                if strategy == ZERO_ADDRESS:
                                                    break  # We've exhausted the queue
                                    
                                                vault_balance: uint256 = self.token.balanceOf(self)
                                                if value <= vault_balance:
                                                    break  # We're done withdrawing
                                    
                                                amountNeeded: uint256 = value - vault_balance
                                    
                                                # NOTE: Don't withdraw more than the debt so that Strategy can still
                                                #       continue to work based on the profits it has
                                                # NOTE: This means that user will lose out on any profits that each
                                                #       Strategy in the queue would return on next harvest, benefiting others
                                                amountNeeded = min(amountNeeded, self.strategies[strategy].totalDebt)
                                                if amountNeeded == 0:
                                                    continue  # Nothing to withdraw from this Strategy, try the next one
                                    
                                                # Force withdraw amount from each Strategy in the order set by governance
                                                loss: uint256 = Strategy(strategy).withdraw(amountNeeded)
                                                withdrawn: uint256 = self.token.balanceOf(self) - vault_balance
                                    
                                                # NOTE: Withdrawer incurs any losses from liquidation
                                                if loss > 0:
                                                    value -= loss
                                                    totalLoss += loss
                                                    self._reportLoss(strategy, loss)
                                    
                                                # Reduce the Strategy's debt by the amount withdrawn ("realized returns")
                                                # NOTE: This doesn't add to returns as it's not earned by "normal means"
                                                self.strategies[strategy].totalDebt -= withdrawn
                                                self.totalDebt -= withdrawn
                                    
                                            # NOTE: We have withdrawn everything possible out of the withdrawal queue
                                            #       but we still don't have enough to fully pay them back, so adjust
                                            #       to the total amount we've freed up through forced withdrawals
                                            vault_balance: uint256 = self.token.balanceOf(self)
                                            if value > vault_balance:
                                                value = vault_balance
                                                # NOTE: Burn # of shares that corresponds to what Vault has on-hand,
                                                #       including the losses that were incurred above during withdrawals
                                                shares = self._sharesForAmount(value + totalLoss)
                                    
                                            # NOTE: This loss protection is put in place to revert if losses from
                                            #       withdrawing are more than what is considered acceptable.
                                            assert totalLoss <= maxLoss * (value + totalLoss) / MAX_BPS
                                    
                                        # Burn shares (full value of what is being withdrawn)
                                        self.totalSupply -= shares
                                        self.balanceOf[msg.sender] -= shares
                                        log Transfer(msg.sender, ZERO_ADDRESS, shares)
                                    
                                        # Withdraw remaining balance to _recipient (may be different to msg.sender) (minus fee)
                                        self.erc20_safe_transfer(self.token.address, recipient, value)
                                    
                                        return value
                                    
                                    
                                    @view
                                    @external
                                    def pricePerShare() -> uint256:
                                        """
                                        @notice Gives the price for a single Vault share.
                                        @dev See dev note on `withdraw`.
                                        @return The value of a single share.
                                        """
                                        return self._shareValue(10 ** self.decimals)
                                    
                                    
                                    @internal
                                    def _organizeWithdrawalQueue():
                                        # Reorganize `withdrawalQueue` based on premise that if there is an
                                        # empty value between two actual values, then the empty value should be
                                        # replaced by the later value.
                                        # NOTE: Relative ordering of non-zero values is maintained.
                                        offset: uint256 = 0
                                        for idx in range(MAXIMUM_STRATEGIES):
                                            strategy: address = self.withdrawalQueue[idx]
                                            if strategy == ZERO_ADDRESS:
                                                offset += 1  # how many values we need to shift, always `<= idx`
                                            elif offset > 0:
                                                self.withdrawalQueue[idx - offset] = strategy
                                                self.withdrawalQueue[idx] = ZERO_ADDRESS
                                    
                                    
                                    @external
                                    def addStrategy(
                                        strategy: address,
                                        debtRatio: uint256,
                                        minDebtPerHarvest: uint256,
                                        maxDebtPerHarvest: uint256,
                                        performanceFee: uint256,
                                    ):
                                        """
                                        @notice
                                            Add a Strategy to the Vault.
                                    
                                            This may only be called by governance.
                                        @dev
                                            The Strategy will be appended to `withdrawalQueue`, call
                                            `setWithdrawalQueue` to change the order.
                                        @param strategy The address of the Strategy to add.
                                        @param debtRatio
                                            The share of the total assets in the `vault that the `strategy` has access to.
                                        @param minDebtPerHarvest
                                            Lower limit on the increase of debt since last harvest
                                        @param maxDebtPerHarvest
                                            Upper limit on the increase of debt since last harvest
                                        @param performanceFee
                                            The fee the strategist will receive based on this Vault's performance.
                                        """
                                        # Check if queue is full
                                        assert self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] == ZERO_ADDRESS
                                    
                                        # Check calling conditions
                                        assert not self.emergencyShutdown
                                        assert msg.sender == self.governance
                                    
                                        # Check strategy configuration
                                        assert strategy != ZERO_ADDRESS
                                        assert self.strategies[strategy].activation == 0
                                        assert self == Strategy(strategy).vault()
                                        assert self.token.address == Strategy(strategy).want()
                                    
                                        # Check strategy parameters
                                        assert self.debtRatio + debtRatio <= MAX_BPS
                                        assert minDebtPerHarvest <= maxDebtPerHarvest
                                        assert performanceFee <= MAX_BPS / 2 
                                    
                                        # Add strategy to approved strategies
                                        self.strategies[strategy] = StrategyParams({
                                            performanceFee: performanceFee,
                                            activation: block.timestamp,
                                            debtRatio: debtRatio,
                                            minDebtPerHarvest: minDebtPerHarvest,
                                            maxDebtPerHarvest: maxDebtPerHarvest,
                                            lastReport: block.timestamp,
                                            totalDebt: 0,
                                            totalGain: 0,
                                            totalLoss: 0,
                                        })
                                        log StrategyAdded(strategy, debtRatio, minDebtPerHarvest, maxDebtPerHarvest, performanceFee)
                                    
                                        # Update Vault parameters
                                        self.debtRatio += debtRatio
                                    
                                        # Add strategy to the end of the withdrawal queue
                                        self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] = strategy
                                        self._organizeWithdrawalQueue()
                                    
                                    
                                    @external
                                    def updateStrategyDebtRatio(
                                        strategy: address,
                                        debtRatio: uint256,
                                    ):
                                        """
                                        @notice
                                            Change the quantity of assets `strategy` may manage.
                                    
                                            This may be called by governance or management.
                                        @param strategy The Strategy to update.
                                        @param debtRatio The quantity of assets `strategy` may now manage.
                                        """
                                        assert msg.sender in [self.management, self.governance]
                                        assert self.strategies[strategy].activation > 0
                                        self.debtRatio -= self.strategies[strategy].debtRatio
                                        self.strategies[strategy].debtRatio = debtRatio
                                        self.debtRatio += debtRatio
                                        assert self.debtRatio <= MAX_BPS
                                        log StrategyUpdateDebtRatio(strategy, debtRatio)
                                    
                                    
                                    @external
                                    def updateStrategyMinDebtPerHarvest(
                                        strategy: address,
                                        minDebtPerHarvest: uint256,
                                    ):
                                        """
                                        @notice
                                            Change the quantity assets per block this Vault may deposit to or
                                            withdraw from `strategy`.
                                    
                                            This may only be called by governance or management.
                                        @param strategy The Strategy to update.
                                        @param minDebtPerHarvest
                                            Lower limit on the increase of debt since last harvest
                                        """
                                        assert msg.sender in [self.management, self.governance]
                                        assert self.strategies[strategy].activation > 0
                                        assert self.strategies[strategy].maxDebtPerHarvest >= minDebtPerHarvest
                                        self.strategies[strategy].minDebtPerHarvest = minDebtPerHarvest
                                        log StrategyUpdateMinDebtPerHarvest(strategy, minDebtPerHarvest)
                                    
                                    
                                    @external
                                    def updateStrategyMaxDebtPerHarvest(
                                        strategy: address,
                                        maxDebtPerHarvest: uint256,
                                    ):
                                        """
                                        @notice
                                            Change the quantity assets per block this Vault may deposit to or
                                            withdraw from `strategy`.
                                    
                                            This may only be called by governance or management.
                                        @param strategy The Strategy to update.
                                        @param maxDebtPerHarvest
                                            Upper limit on the increase of debt since last harvest
                                        """
                                        assert msg.sender in [self.management, self.governance]
                                        assert self.strategies[strategy].activation > 0
                                        assert self.strategies[strategy].minDebtPerHarvest <= maxDebtPerHarvest
                                        self.strategies[strategy].maxDebtPerHarvest = maxDebtPerHarvest
                                        log StrategyUpdateMaxDebtPerHarvest(strategy, maxDebtPerHarvest)
                                    
                                    
                                    @external
                                    def updateStrategyPerformanceFee(
                                        strategy: address,
                                        performanceFee: uint256,
                                    ):
                                        """
                                        @notice
                                            Change the fee the strategist will receive based on this Vault's
                                            performance.
                                    
                                            This may only be called by governance.
                                        @param strategy The Strategy to update.
                                        @param performanceFee The new fee the strategist will receive.
                                        """
                                        assert msg.sender == self.governance
                                        assert performanceFee <= MAX_BPS / 2
                                        assert self.strategies[strategy].activation > 0
                                        self.strategies[strategy].performanceFee = performanceFee
                                        log StrategyUpdatePerformanceFee(strategy, performanceFee)
                                    
                                    
                                    @internal
                                    def _revokeStrategy(strategy: address):
                                        self.debtRatio -= self.strategies[strategy].debtRatio
                                        self.strategies[strategy].debtRatio = 0
                                        log StrategyRevoked(strategy)
                                    
                                    
                                    @external
                                    def migrateStrategy(oldVersion: address, newVersion: address):
                                        """
                                        @notice
                                            Migrates a Strategy, including all assets from `oldVersion` to
                                            `newVersion`.
                                    
                                            This may only be called by governance.
                                        @dev
                                            Strategy must successfully migrate all capital and positions to new
                                            Strategy, or else this will upset the balance of the Vault.
                                    
                                            The new Strategy should be "empty" e.g. have no prior commitments to
                                            this Vault, otherwise it could have issues.
                                        @param oldVersion The existing Strategy to migrate from.
                                        @param newVersion The new Strategy to migrate to.
                                        """
                                        assert msg.sender == self.governance
                                        assert newVersion != ZERO_ADDRESS
                                        assert self.strategies[oldVersion].activation > 0
                                        assert self.strategies[newVersion].activation == 0
                                    
                                        strategy: StrategyParams = self.strategies[oldVersion]
                                    
                                        self._revokeStrategy(oldVersion)
                                        # _revokeStrategy will lower the debtRatio
                                        self.debtRatio += strategy.debtRatio
                                        # Debt is migrated to new strategy
                                        self.strategies[oldVersion].totalDebt = 0
                                    
                                        self.strategies[newVersion] = StrategyParams({
                                            performanceFee: strategy.performanceFee,
                                            # NOTE: use last report for activation time, so E[R] calc works
                                            activation: strategy.lastReport,
                                            debtRatio: strategy.debtRatio,
                                            minDebtPerHarvest: strategy.minDebtPerHarvest,
                                            maxDebtPerHarvest: strategy.maxDebtPerHarvest,
                                            lastReport: strategy.lastReport,
                                            totalDebt: strategy.totalDebt,
                                            totalGain: 0,
                                            totalLoss: 0,
                                        })
                                    
                                        Strategy(oldVersion).migrate(newVersion)
                                        log StrategyMigrated(oldVersion, newVersion)
                                    
                                        for idx in range(MAXIMUM_STRATEGIES):
                                            if self.withdrawalQueue[idx] == oldVersion:
                                                self.withdrawalQueue[idx] = newVersion
                                                return  # Don't need to reorder anything because we swapped
                                    
                                    
                                    @external
                                    def revokeStrategy(strategy: address = msg.sender):
                                        """
                                        @notice
                                            Revoke a Strategy, setting its debt limit to 0 and preventing any
                                            future deposits.
                                    
                                            This function should only be used in the scenario where the Strategy is
                                            being retired but no migration of the positions are possible, or in the
                                            extreme scenario that the Strategy needs to be put into "Emergency Exit"
                                            mode in order for it to exit as quickly as possible. The latter scenario
                                            could be for any reason that is considered "critical" that the Strategy
                                            exits its position as fast as possible, such as a sudden change in market
                                            conditions leading to losses, or an imminent failure in an external
                                            dependency.
                                    
                                            This may only be called by governance, the guardian, or the Strategy
                                            itself. Note that a Strategy will only revoke itself during emergency
                                            shutdown.
                                        @param strategy The Strategy to revoke.
                                        """
                                        assert msg.sender in [strategy, self.governance, self.guardian]
                                        assert self.strategies[strategy].debtRatio != 0 # dev: already zero
                                    
                                        self._revokeStrategy(strategy)
                                    
                                    
                                    @external
                                    def addStrategyToQueue(strategy: address):
                                        """
                                        @notice
                                            Adds `strategy` to `withdrawalQueue`.
                                    
                                            This may only be called by governance or management.
                                        @dev
                                            The Strategy will be appended to `withdrawalQueue`, call
                                            `setWithdrawalQueue` to change the order.
                                        @param strategy The Strategy to add.
                                        """
                                        assert msg.sender in [self.management, self.governance]
                                        # Must be a current Strategy
                                        assert self.strategies[strategy].activation > 0
                                        # Can't already be in the queue
                                        last_idx: uint256 = 0
                                        for s in self.withdrawalQueue:
                                            if s == ZERO_ADDRESS:
                                                break
                                            assert s != strategy
                                            last_idx += 1
                                        # Check if queue is full
                                        assert last_idx < MAXIMUM_STRATEGIES
                                    
                                        self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] = strategy
                                        self._organizeWithdrawalQueue()
                                        log StrategyAddedToQueue(strategy)
                                    
                                    
                                    @external
                                    def removeStrategyFromQueue(strategy: address):
                                        """
                                        @notice
                                            Remove `strategy` from `withdrawalQueue`.
                                    
                                            This may only be called by governance or management.
                                        @dev
                                            We don't do this with revokeStrategy because it should still
                                            be possible to withdraw from the Strategy if it's unwinding.
                                        @param strategy The Strategy to remove.
                                        """
                                        assert msg.sender in [self.management, self.governance]
                                        for idx in range(MAXIMUM_STRATEGIES):
                                            if self.withdrawalQueue[idx] == strategy:
                                                self.withdrawalQueue[idx] = ZERO_ADDRESS
                                                self._organizeWithdrawalQueue()
                                                log StrategyRemovedFromQueue(strategy)
                                                return  # We found the right location and cleared it
                                        raise  # We didn't find the Strategy in the queue
                                    
                                    
                                    @view
                                    @internal
                                    def _debtOutstanding(strategy: address) -> uint256:
                                        # See note on `debtOutstanding()`.
                                        if self.debtRatio == 0:
                                            return self.strategies[strategy].totalDebt
                                    
                                        strategy_debtLimit: uint256 = (
                                            self.strategies[strategy].debtRatio
                                            * self._totalAssets()
                                            / MAX_BPS
                                        )
                                        strategy_totalDebt: uint256 = self.strategies[strategy].totalDebt
                                    
                                        if self.emergencyShutdown:
                                            return strategy_totalDebt
                                        elif strategy_totalDebt <= strategy_debtLimit:
                                            return 0
                                        else:
                                            return strategy_totalDebt - strategy_debtLimit
                                    
                                    
                                    @view
                                    @external
                                    def debtOutstanding(strategy: address = msg.sender) -> uint256:
                                        """
                                        @notice
                                            Determines if `strategy` is past its debt limit and if any tokens
                                            should be withdrawn to the Vault.
                                        @param strategy The Strategy to check. Defaults to the caller.
                                        @return The quantity of tokens to withdraw.
                                        """
                                        return self._debtOutstanding(strategy)
                                    
                                    
                                    @view
                                    @internal
                                    def _creditAvailable(strategy: address) -> uint256:
                                        # See note on `creditAvailable()`.
                                        if self.emergencyShutdown:
                                            return 0
                                        vault_totalAssets: uint256 = self._totalAssets()
                                        vault_debtLimit: uint256 =  self.debtRatio * vault_totalAssets / MAX_BPS 
                                        vault_totalDebt: uint256 = self.totalDebt
                                        strategy_debtLimit: uint256 = self.strategies[strategy].debtRatio * vault_totalAssets / MAX_BPS
                                        strategy_totalDebt: uint256 = self.strategies[strategy].totalDebt
                                        strategy_minDebtPerHarvest: uint256 = self.strategies[strategy].minDebtPerHarvest
                                        strategy_maxDebtPerHarvest: uint256 = self.strategies[strategy].maxDebtPerHarvest
                                    
                                        # Exhausted credit line
                                        if strategy_debtLimit <= strategy_totalDebt or vault_debtLimit <= vault_totalDebt:
                                            return 0
                                    
                                        # Start with debt limit left for the Strategy
                                        available: uint256 = strategy_debtLimit - strategy_totalDebt
                                    
                                        # Adjust by the global debt limit left
                                        available = min(available, vault_debtLimit - vault_totalDebt)
                                    
                                        # Can only borrow up to what the contract has in reserve
                                        # NOTE: Running near 100% is discouraged
                                        available = min(available, self.token.balanceOf(self))
                                    
                                        # Adjust by min and max borrow limits (per harvest)
                                        # NOTE: min increase can be used to ensure that if a strategy has a minimum
                                        #       amount of capital needed to purchase a position, it's not given capital
                                        #       it can't make use of yet.
                                        # NOTE: max increase is used to make sure each harvest isn't bigger than what
                                        #       is authorized. This combined with adjusting min and max periods in
                                        #       `BaseStrategy` can be used to effect a "rate limit" on capital increase.
                                        if available < strategy_minDebtPerHarvest:
                                            return 0
                                        else:
                                            return min(available, strategy_maxDebtPerHarvest)
                                    
                                    @view
                                    @external
                                    def creditAvailable(strategy: address = msg.sender) -> uint256:
                                        """
                                        @notice
                                            Amount of tokens in Vault a Strategy has access to as a credit line.
                                    
                                            This will check the Strategy's debt limit, as well as the tokens
                                            available in the Vault, and determine the maximum amount of tokens
                                            (if any) the Strategy may draw on.
                                    
                                            In the rare case the Vault is in emergency shutdown this will return 0.
                                        @param strategy The Strategy to check. Defaults to caller.
                                        @return The quantity of tokens available for the Strategy to draw on.
                                        """
                                        return self._creditAvailable(strategy)
                                    
                                    
                                    @view
                                    @internal
                                    def _expectedReturn(strategy: address) -> uint256:
                                        # See note on `expectedReturn()`.
                                        strategy_lastReport: uint256 = self.strategies[strategy].lastReport
                                        timeSinceLastHarvest: uint256 = block.timestamp - strategy_lastReport
                                        totalHarvestTime: uint256 = strategy_lastReport - self.strategies[strategy].activation
                                    
                                        # NOTE: If either `timeSinceLastHarvest` or `totalHarvestTime` is 0, we can short-circuit to `0`
                                        if timeSinceLastHarvest > 0 and totalHarvestTime > 0 and Strategy(strategy).isActive():
                                            # NOTE: Unlikely to throw unless strategy accumalates >1e68 returns
                                            # NOTE: Calculate average over period of time where harvests have occured in the past
                                            return (
                                                self.strategies[strategy].totalGain
                                                * timeSinceLastHarvest
                                                / totalHarvestTime
                                            )
                                        else:
                                            return 0  # Covers the scenario when block.timestamp == activation
                                    
                                    
                                    @view
                                    @external
                                    def availableDepositLimit() -> uint256:
                                        if self.depositLimit > self._totalAssets():
                                            return self.depositLimit - self._totalAssets()
                                        else:
                                            return 0
                                    
                                    
                                    @view
                                    @external
                                    def expectedReturn(strategy: address = msg.sender) -> uint256:
                                        """
                                        @notice
                                            Provide an accurate expected value for the return this `strategy`
                                            would provide to the Vault the next time `report()` is called
                                            (since the last time it was called).
                                        @param strategy The Strategy to determine the expected return for. Defaults to caller.
                                        @return
                                            The anticipated amount `strategy` should make on its investment
                                            since its last report.
                                        """
                                        return self._expectedReturn(strategy)
                                    
                                    
                                    @internal
                                    def _assessFees(strategy: address, gain: uint256) -> uint256:
                                        # Issue new shares to cover fees
                                        # NOTE: In effect, this reduces overall share price by the combined fee
                                        # NOTE: may throw if Vault.totalAssets() > 1e64, or not called for more than a year
                                        duration: uint256 = block.timestamp - self.strategies[strategy].lastReport
                                        assert duration != 0 # can't assessFees twice within the same block
                                    
                                        if gain == 0:
                                            # NOTE: The fees are not charged if there hasn't been any gains reported
                                            return 0
                                    
                                        management_fee: uint256 = (
                                            (
                                                (self.strategies[strategy].totalDebt - Strategy(strategy).delegatedAssets())
                                                * duration 
                                                * self.managementFee
                                            )
                                            / MAX_BPS
                                            / SECS_PER_YEAR
                                        )
                                    
                                        # NOTE: Applies if Strategy is not shutting down, or it is but all debt paid off
                                        # NOTE: No fee is taken when a Strategy is unwinding it's position, until all debt is paid
                                        strategist_fee: uint256 = (
                                            gain
                                            * self.strategies[strategy].performanceFee
                                            / MAX_BPS
                                        )
                                        # NOTE: Unlikely to throw unless strategy reports >1e72 harvest profit
                                        performance_fee: uint256 = gain * self.performanceFee / MAX_BPS
                                    
                                        # NOTE: This must be called prior to taking new collateral,
                                        #       or the calculation will be wrong!
                                        # NOTE: This must be done at the same time, to ensure the relative
                                        #       ratio of governance_fee : strategist_fee is kept intact
                                        total_fee: uint256 = performance_fee + strategist_fee + management_fee
                                        # ensure total_fee is not more than gain
                                        if total_fee > gain:
                                            total_fee = gain
                                        if total_fee > 0:  # NOTE: If mgmt fee is 0% and no gains were realized, skip
                                            reward: uint256 = self._issueSharesForAmount(self, total_fee)
                                    
                                            # Send the rewards out as new shares in this Vault
                                            if strategist_fee > 0:  # NOTE: Guard against DIV/0 fault
                                                # NOTE: Unlikely to throw unless sqrt(reward) >>> 1e39
                                                strategist_reward: uint256 = (
                                                    strategist_fee
                                                    * reward
                                                    / total_fee
                                                )
                                                self._transfer(self, strategy, strategist_reward)
                                                # NOTE: Strategy distributes rewards at the end of harvest()
                                            # NOTE: Governance earns any dust leftover from flooring math above
                                            if self.balanceOf[self] > 0:
                                                self._transfer(self, self.rewards, self.balanceOf[self])
                                        return total_fee
                                    
                                    
                                    @external
                                    def report(gain: uint256, loss: uint256, _debtPayment: uint256) -> uint256:
                                        """
                                        @notice
                                            Reports the amount of assets the calling Strategy has free (usually in
                                            terms of ROI).
                                    
                                            The performance fee is determined here, off of the strategy's profits
                                            (if any), and sent to governance.
                                    
                                            The strategist's fee is also determined here (off of profits), to be
                                            handled according to the strategist on the next harvest.
                                    
                                            This may only be called by a Strategy managed by this Vault.
                                        @dev
                                            For approved strategies, this is the most efficient behavior.
                                            The Strategy reports back what it has free, then Vault "decides"
                                            whether to take some back or give it more. Note that the most it can
                                            take is `gain + _debtPayment`, and the most it can give is all of the
                                            remaining reserves. Anything outside of those bounds is abnormal behavior.
                                    
                                            All approved strategies must have increased diligence around
                                            calling this function, as abnormal behavior could become catastrophic.
                                        @param gain
                                            Amount Strategy has realized as a gain on it's investment since its
                                            last report, and is free to be given back to Vault as earnings
                                        @param loss
                                            Amount Strategy has realized as a loss on it's investment since its
                                            last report, and should be accounted for on the Vault's balance sheet.
                                            The loss will reduce the debtRatio. The next time the strategy will harvest,
                                            it will pay back the debt in an attempt to adjust to the new debt limit.
                                        @param _debtPayment
                                            Amount Strategy has made available to cover outstanding debt
                                        @return Amount of debt outstanding (if totalDebt > debtLimit or emergency shutdown).
                                        """
                                    
                                        # Only approved strategies can call this function
                                        assert self.strategies[msg.sender].activation > 0
                                        # No lying about total available to withdraw!
                                        assert self.token.balanceOf(msg.sender) >= gain + _debtPayment
                                    
                                        # We have a loss to report, do it before the rest of the calculations
                                        if loss > 0:
                                            self._reportLoss(msg.sender, loss)
                                    
                                        # Assess both management fee and performance fee, and issue both as shares of the vault
                                        totalFees: uint256 = self._assessFees(msg.sender, gain)
                                    
                                        # Returns are always "realized gains"
                                        self.strategies[msg.sender].totalGain += gain
                                    
                                        # Compute the line of credit the Vault is able to offer the Strategy (if any)
                                        credit: uint256 = self._creditAvailable(msg.sender)
                                    
                                        # Outstanding debt the Strategy wants to take back from the Vault (if any)
                                        # NOTE: debtOutstanding <= StrategyParams.totalDebt
                                        debt: uint256 = self._debtOutstanding(msg.sender)
                                        debtPayment: uint256 = min(_debtPayment, debt)
                                    
                                        if debtPayment > 0:
                                            self.strategies[msg.sender].totalDebt -= debtPayment
                                            self.totalDebt -= debtPayment
                                            debt -= debtPayment
                                            # NOTE: `debt` is being tracked for later
                                    
                                        # Update the actual debt based on the full credit we are extending to the Strategy
                                        # or the returns if we are taking funds back
                                        # NOTE: credit + self.strategies[msg.sender].totalDebt is always < self.debtLimit
                                        # NOTE: At least one of `credit` or `debt` is always 0 (both can be 0)
                                        if credit > 0:
                                            self.strategies[msg.sender].totalDebt += credit
                                            self.totalDebt += credit
                                    
                                        # Give/take balance to Strategy, based on the difference between the reported gains
                                        # (if any), the debt payment (if any), the credit increase we are offering (if any),
                                        # and the debt needed to be paid off (if any)
                                        # NOTE: This is just used to adjust the balance of tokens between the Strategy and
                                        #       the Vault based on the Strategy's debt limit (as well as the Vault's).
                                        totalAvail: uint256 = gain + debtPayment
                                        if totalAvail < credit:  # credit surplus, give to Strategy
                                            self.erc20_safe_transfer(self.token.address, msg.sender, credit - totalAvail)
                                        elif totalAvail > credit:  # credit deficit, take from Strategy
                                            self.erc20_safe_transferFrom(self.token.address, msg.sender, self, totalAvail - credit)
                                        # else, don't do anything because it is balanced
                                    
                                        # Profit is locked and gradually released per block
                                        # NOTE: compute current locked profit and replace with sum of current and new
                                        lockedProfitBeforeLoss: uint256 = self._calculateLockedProfit() + gain - totalFees
                                        if lockedProfitBeforeLoss > loss: 
                                            self.lockedProfit = lockedProfitBeforeLoss - loss
                                        else:
                                            self.lockedProfit = 0
                                    
                                        # Update reporting time
                                        self.strategies[msg.sender].lastReport = block.timestamp
                                        self.lastReport = block.timestamp
                                    
                                        log StrategyReported(
                                            msg.sender,
                                            gain,
                                            loss,
                                            debtPayment,
                                            self.strategies[msg.sender].totalGain,
                                            self.strategies[msg.sender].totalLoss,
                                            self.strategies[msg.sender].totalDebt,
                                            credit,
                                            self.strategies[msg.sender].debtRatio,
                                        )
                                    
                                        if self.strategies[msg.sender].debtRatio == 0 or self.emergencyShutdown:
                                            # Take every last penny the Strategy has (Emergency Exit/revokeStrategy)
                                            # NOTE: This is different than `debt` in order to extract *all* of the returns
                                            return Strategy(msg.sender).estimatedTotalAssets()
                                        else:
                                            # Otherwise, just return what we have as debt outstanding
                                            return debt
                                    
                                    
                                    @external
                                    def sweep(token: address, amount: uint256 = MAX_UINT256):
                                        """
                                        @notice
                                            Removes tokens from this Vault that are not the type of token managed
                                            by this Vault. This may be used in case of accidentally sending the
                                            wrong kind of token to this Vault.
                                    
                                            Tokens will be sent to `governance`.
                                    
                                            This will fail if an attempt is made to sweep the tokens that this
                                            Vault manages.
                                    
                                            This may only be called by governance.
                                        @param token The token to transfer out of this vault.
                                        @param amount The quantity or tokenId to transfer out.
                                        """
                                        assert msg.sender == self.governance
                                        # Can't be used to steal what this Vault is protecting
                                        assert token != self.token.address
                                        value: uint256 = amount
                                        if value == MAX_UINT256:
                                            value = ERC20(token).balanceOf(self)
                                        self.erc20_safe_transfer(token, self.governance, value)

                                    File 2 of 6: 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 3 of 6: FiatTokenProxy
                                    pragma solidity ^0.4.24;
                                    
                                    // File: zos-lib/contracts/upgradeability/Proxy.sol
                                    
                                    /**
                                     * @title Proxy
                                     * @dev Implements delegation of calls to other contracts, with proper
                                     * forwarding of return values and bubbling of failures.
                                     * It defines a fallback function that delegates all calls to the address
                                     * returned by the abstract _implementation() internal function.
                                     */
                                    contract Proxy {
                                      /**
                                       * @dev Fallback function.
                                       * Implemented entirely in `_fallback`.
                                       */
                                      function () payable external {
                                        _fallback();
                                      }
                                    
                                      /**
                                       * @return The Address of the implementation.
                                       */
                                      function _implementation() internal view returns (address);
                                    
                                      /**
                                       * @dev Delegates execution to an implementation contract.
                                       * This is a low level function that doesn't return to its internal call site.
                                       * It will return to the external caller whatever the implementation returns.
                                       * @param implementation Address to delegate.
                                       */
                                      function _delegate(address implementation) internal {
                                        assembly {
                                          // Copy msg.data. We take full control of memory in this inline assembly
                                          // block because it will not return to Solidity code. We overwrite the
                                          // Solidity scratch pad at memory position 0.
                                          calldatacopy(0, 0, calldatasize)
                                    
                                          // Call the implementation.
                                          // out and outsize are 0 because we don't know the size yet.
                                          let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)
                                    
                                          // Copy the returned data.
                                          returndatacopy(0, 0, returndatasize)
                                    
                                          switch result
                                          // delegatecall returns 0 on error.
                                          case 0 { revert(0, returndatasize) }
                                          default { return(0, returndatasize) }
                                        }
                                      }
                                    
                                      /**
                                       * @dev Function that is run as the first thing in the fallback function.
                                       * Can be redefined in derived contracts to add functionality.
                                       * Redefinitions must call super._willFallback().
                                       */
                                      function _willFallback() internal {
                                      }
                                    
                                      /**
                                       * @dev fallback implementation.
                                       * Extracted to enable manual triggering.
                                       */
                                      function _fallback() internal {
                                        _willFallback();
                                        _delegate(_implementation());
                                      }
                                    }
                                    
                                    // File: openzeppelin-solidity/contracts/AddressUtils.sol
                                    
                                    /**
                                     * Utility library of inline functions on addresses
                                     */
                                    library AddressUtils {
                                    
                                      /**
                                       * Returns whether the target address is a contract
                                       * @dev This function will return false if invoked during the constructor of a contract,
                                       * as the code is not actually created until after the constructor finishes.
                                       * @param addr address to check
                                       * @return whether the target address is a contract
                                       */
                                      function isContract(address addr) internal view returns (bool) {
                                        uint256 size;
                                        // XXX Currently there is no better way to check if there is a contract in an address
                                        // than to check the size of the code at that address.
                                        // See https://ethereum.stackexchange.com/a/14016/36603
                                        // for more details about how this works.
                                        // TODO Check this again before the Serenity release, because all addresses will be
                                        // contracts then.
                                        // solium-disable-next-line security/no-inline-assembly
                                        assembly { size := extcodesize(addr) }
                                        return size > 0;
                                      }
                                    
                                    }
                                    
                                    // File: zos-lib/contracts/upgradeability/UpgradeabilityProxy.sol
                                    
                                    /**
                                     * @title UpgradeabilityProxy
                                     * @dev This contract implements a proxy that allows to change the
                                     * implementation address to which it will delegate.
                                     * Such a change is called an implementation upgrade.
                                     */
                                    contract UpgradeabilityProxy is Proxy {
                                      /**
                                       * @dev Emitted when the implementation is upgraded.
                                       * @param implementation Address of the new implementation.
                                       */
                                      event Upgraded(address implementation);
                                    
                                      /**
                                       * @dev Storage slot with the address of the current implementation.
                                       * This is the keccak-256 hash of "org.zeppelinos.proxy.implementation", and is
                                       * validated in the constructor.
                                       */
                                      bytes32 private constant IMPLEMENTATION_SLOT = 0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3;
                                    
                                      /**
                                       * @dev Contract constructor.
                                       * @param _implementation Address of the initial implementation.
                                       */
                                      constructor(address _implementation) public {
                                        assert(IMPLEMENTATION_SLOT == keccak256("org.zeppelinos.proxy.implementation"));
                                    
                                        _setImplementation(_implementation);
                                      }
                                    
                                      /**
                                       * @dev Returns the current implementation.
                                       * @return Address of the current implementation
                                       */
                                      function _implementation() internal view returns (address impl) {
                                        bytes32 slot = IMPLEMENTATION_SLOT;
                                        assembly {
                                          impl := sload(slot)
                                        }
                                      }
                                    
                                      /**
                                       * @dev Upgrades the proxy to a new implementation.
                                       * @param newImplementation Address of the new implementation.
                                       */
                                      function _upgradeTo(address newImplementation) internal {
                                        _setImplementation(newImplementation);
                                        emit Upgraded(newImplementation);
                                      }
                                    
                                      /**
                                       * @dev Sets the implementation address of the proxy.
                                       * @param newImplementation Address of the new implementation.
                                       */
                                      function _setImplementation(address newImplementation) private {
                                        require(AddressUtils.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
                                    
                                        bytes32 slot = IMPLEMENTATION_SLOT;
                                    
                                        assembly {
                                          sstore(slot, newImplementation)
                                        }
                                      }
                                    }
                                    
                                    // File: zos-lib/contracts/upgradeability/AdminUpgradeabilityProxy.sol
                                    
                                    /**
                                     * @title AdminUpgradeabilityProxy
                                     * @dev This contract combines an upgradeability proxy with an authorization
                                     * mechanism for administrative tasks.
                                     * All external functions in this contract must be guarded by the
                                     * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
                                     * feature proposal that would enable this to be done automatically.
                                     */
                                    contract AdminUpgradeabilityProxy is UpgradeabilityProxy {
                                      /**
                                       * @dev Emitted when the administration has been transferred.
                                       * @param previousAdmin Address of the previous admin.
                                       * @param newAdmin Address of the new admin.
                                       */
                                      event AdminChanged(address previousAdmin, address newAdmin);
                                    
                                      /**
                                       * @dev Storage slot with the admin of the contract.
                                       * This is the keccak-256 hash of "org.zeppelinos.proxy.admin", and is
                                       * validated in the constructor.
                                       */
                                      bytes32 private constant ADMIN_SLOT = 0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b;
                                    
                                      /**
                                       * @dev Modifier to check whether the `msg.sender` is the admin.
                                       * If it is, it will run the function. Otherwise, it will delegate the call
                                       * to the implementation.
                                       */
                                      modifier ifAdmin() {
                                        if (msg.sender == _admin()) {
                                          _;
                                        } else {
                                          _fallback();
                                        }
                                      }
                                    
                                      /**
                                       * Contract constructor.
                                       * It sets the `msg.sender` as the proxy administrator.
                                       * @param _implementation address of the initial implementation.
                                       */
                                      constructor(address _implementation) UpgradeabilityProxy(_implementation) public {
                                        assert(ADMIN_SLOT == keccak256("org.zeppelinos.proxy.admin"));
                                    
                                        _setAdmin(msg.sender);
                                      }
                                    
                                      /**
                                       * @return The address of the proxy admin.
                                       */
                                      function admin() external view ifAdmin returns (address) {
                                        return _admin();
                                      }
                                    
                                      /**
                                       * @return The address of the implementation.
                                       */
                                      function implementation() external view ifAdmin returns (address) {
                                        return _implementation();
                                      }
                                    
                                      /**
                                       * @dev Changes the admin of the proxy.
                                       * Only the current admin can call this function.
                                       * @param newAdmin Address to transfer proxy administration to.
                                       */
                                      function changeAdmin(address newAdmin) external ifAdmin {
                                        require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
                                        emit AdminChanged(_admin(), newAdmin);
                                        _setAdmin(newAdmin);
                                      }
                                    
                                      /**
                                       * @dev Upgrade the backing implementation of the proxy.
                                       * Only the admin can call this function.
                                       * @param newImplementation Address of the new implementation.
                                       */
                                      function upgradeTo(address newImplementation) external ifAdmin {
                                        _upgradeTo(newImplementation);
                                      }
                                    
                                      /**
                                       * @dev Upgrade the backing implementation of the proxy and call a function
                                       * on the new implementation.
                                       * This is useful to initialize the proxied contract.
                                       * @param newImplementation Address of the new implementation.
                                       * @param data Data to send as msg.data in the low level call.
                                       * It should include the signature and the parameters of the function to be
                                       * called, as described in
                                       * https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding.
                                       */
                                      function upgradeToAndCall(address newImplementation, bytes data) payable external ifAdmin {
                                        _upgradeTo(newImplementation);
                                        require(address(this).call.value(msg.value)(data));
                                      }
                                    
                                      /**
                                       * @return The admin slot.
                                       */
                                      function _admin() internal view returns (address adm) {
                                        bytes32 slot = ADMIN_SLOT;
                                        assembly {
                                          adm := sload(slot)
                                        }
                                      }
                                    
                                      /**
                                       * @dev Sets the address of the proxy admin.
                                       * @param newAdmin Address of the new proxy admin.
                                       */
                                      function _setAdmin(address newAdmin) internal {
                                        bytes32 slot = ADMIN_SLOT;
                                    
                                        assembly {
                                          sstore(slot, newAdmin)
                                        }
                                      }
                                    
                                      /**
                                       * @dev Only fall back when the sender is not the admin.
                                       */
                                      function _willFallback() internal {
                                        require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
                                        super._willFallback();
                                      }
                                    }
                                    
                                    // File: contracts/FiatTokenProxy.sol
                                    
                                    /**
                                    * Copyright CENTRE SECZ 2018
                                    *
                                    * Permission is hereby granted, free of charge, to any person obtaining a copy 
                                    * of this software and associated documentation files (the "Software"), to deal 
                                    * in the Software without restriction, including without limitation the rights 
                                    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
                                    * copies of the Software, and to permit persons to whom the Software is furnished to 
                                    * do so, subject to the following conditions:
                                    *
                                    * The above copyright notice and this permission notice shall be included in all 
                                    * copies or substantial portions of the Software.
                                    *
                                    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
                                    * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
                                    * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
                                    * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
                                    * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
                                    * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
                                    */
                                    
                                    pragma solidity ^0.4.24;
                                    
                                    
                                    /**
                                     * @title FiatTokenProxy
                                     * @dev This contract proxies FiatToken calls and enables FiatToken upgrades
                                    */ 
                                    contract FiatTokenProxy is AdminUpgradeabilityProxy {
                                        constructor(address _implementation) public AdminUpgradeabilityProxy(_implementation) {
                                        }
                                    }

                                    File 4 of 6: Vyper_contract
                                    # @version 0.2.12
                                    """
                                    @title Yearn Token Vault
                                    @license GNU AGPLv3
                                    @author yearn.finance
                                    @notice
                                        Yearn Token Vault. Holds an underlying token, and allows users to interact
                                        with the Yearn ecosystem through Strategies connected to the Vault.
                                        Vaults are not limited to a single Strategy, they can have as many Strategies
                                        as can be designed (however the withdrawal queue is capped at 20.)
                                    
                                        Deposited funds are moved into the most impactful strategy that has not
                                        already reached its limit for assets under management, regardless of which
                                        Strategy a user's funds end up in, they receive their portion of yields
                                        generated across all Strategies.
                                    
                                        When a user withdraws, if there are no funds sitting undeployed in the
                                        Vault, the Vault withdraws funds from Strategies in the order of least
                                        impact. (Funds are taken from the Strategy that will disturb everyone's
                                        gains the least, then the next least, etc.) In order to achieve this, the
                                        withdrawal queue's order must be properly set and managed by the community
                                        (through governance).
                                    
                                        Vault Strategies are parameterized to pursue the highest risk-adjusted yield.
                                    
                                        There is an "Emergency Shutdown" mode. When the Vault is put into emergency
                                        shutdown, assets will be recalled from the Strategies as quickly as is
                                        practical (given on-chain conditions), minimizing loss. Deposits are
                                        halted, new Strategies may not be added, and each Strategy exits with the
                                        minimum possible damage to position, while opening up deposits to be
                                        withdrawn by users. There are no restrictions on withdrawals above what is
                                        expected under Normal Operation.
                                    
                                        For further details, please refer to the specification:
                                        https://github.com/iearn-finance/yearn-vaults/blob/main/SPECIFICATION.md
                                    """
                                    
                                    API_VERSION: constant(String[28]) = "0.4.3"
                                    
                                    from vyper.interfaces import ERC20
                                    
                                    implements: ERC20
                                    
                                    
                                    interface DetailedERC20:
                                        def name() -> String[42]: view
                                        def symbol() -> String[20]: view
                                        def decimals() -> uint256: view
                                    
                                    
                                    interface Strategy:
                                        def want() -> address: view
                                        def vault() -> address: view
                                        def isActive() -> bool: view
                                        def delegatedAssets() -> uint256: view
                                        def estimatedTotalAssets() -> uint256: view
                                        def withdraw(_amount: uint256) -> uint256: nonpayable
                                        def migrate(_newStrategy: address): nonpayable
                                    
                                    
                                    event Transfer:
                                        sender: indexed(address)
                                        receiver: indexed(address)
                                        value: uint256
                                    
                                    
                                    event Approval:
                                        owner: indexed(address)
                                        spender: indexed(address)
                                        value: uint256
                                    
                                    
                                    name: public(String[64])
                                    symbol: public(String[32])
                                    decimals: public(uint256)
                                    
                                    balanceOf: public(HashMap[address, uint256])
                                    allowance: public(HashMap[address, HashMap[address, uint256]])
                                    totalSupply: public(uint256)
                                    
                                    token: public(ERC20)
                                    governance: public(address)
                                    management: public(address)
                                    guardian: public(address)
                                    pendingGovernance: address
                                    
                                    struct StrategyParams:
                                        performanceFee: uint256  # Strategist's fee (basis points)
                                        activation: uint256  # Activation block.timestamp
                                        debtRatio: uint256  # Maximum borrow amount (in BPS of total assets)
                                        minDebtPerHarvest: uint256  # Lower limit on the increase of debt since last harvest
                                        maxDebtPerHarvest: uint256  # Upper limit on the increase of debt since last harvest
                                        lastReport: uint256  # block.timestamp of the last time a report occured
                                        totalDebt: uint256  # Total outstanding debt that Strategy has
                                        totalGain: uint256  # Total returns that Strategy has realized for Vault
                                        totalLoss: uint256  # Total losses that Strategy has realized for Vault
                                    
                                    
                                    event StrategyAdded:
                                        strategy: indexed(address)
                                        debtRatio: uint256  # Maximum borrow amount (in BPS of total assets)
                                        minDebtPerHarvest: uint256  # Lower limit on the increase of debt since last harvest
                                        maxDebtPerHarvest: uint256  # Upper limit on the increase of debt since last harvest
                                        performanceFee: uint256  # Strategist's fee (basis points)
                                    
                                    
                                    event StrategyReported:
                                        strategy: indexed(address)
                                        gain: uint256
                                        loss: uint256
                                        debtPaid: uint256
                                        totalGain: uint256
                                        totalLoss: uint256
                                        totalDebt: uint256
                                        debtAdded: uint256
                                        debtRatio: uint256
                                    
                                    
                                    event UpdateGovernance:
                                        governance: address # New active governance
                                    
                                    
                                    event UpdateManagement:
                                        management: address # New active manager
                                    
                                    event UpdateRewards:
                                        rewards: address # New active rewards recipient
                                    
                                    
                                    event UpdateDepositLimit:
                                        depositLimit: uint256 # New active deposit limit
                                    
                                    
                                    event UpdatePerformanceFee:
                                        performanceFee: uint256 # New active performance fee
                                    
                                    
                                    event UpdateManagementFee:
                                        managementFee: uint256 # New active management fee
                                    
                                    
                                    event UpdateGuardian:
                                        guardian: address # Address of the active guardian
                                    
                                    
                                    event EmergencyShutdown:
                                        active: bool # New emergency shutdown state (if false, normal operation enabled)
                                    
                                    
                                    event UpdateWithdrawalQueue:
                                        queue: address[MAXIMUM_STRATEGIES] # New active withdrawal queue
                                    
                                    
                                    event StrategyUpdateDebtRatio:
                                        strategy: indexed(address) # Address of the strategy for the debt ratio adjustment
                                        debtRatio: uint256 # The new debt limit for the strategy (in BPS of total assets)
                                    
                                    
                                    event StrategyUpdateMinDebtPerHarvest:
                                        strategy: indexed(address) # Address of the strategy for the rate limit adjustment
                                        minDebtPerHarvest: uint256  # Lower limit on the increase of debt since last harvest
                                    
                                    
                                    event StrategyUpdateMaxDebtPerHarvest:
                                        strategy: indexed(address) # Address of the strategy for the rate limit adjustment
                                        maxDebtPerHarvest: uint256  # Upper limit on the increase of debt since last harvest
                                    
                                    
                                    event StrategyUpdatePerformanceFee:
                                        strategy: indexed(address) # Address of the strategy for the performance fee adjustment
                                        performanceFee: uint256 # The new performance fee for the strategy
                                    
                                    
                                    event StrategyMigrated:
                                        oldVersion: indexed(address) # Old version of the strategy to be migrated
                                        newVersion: indexed(address) # New version of the strategy
                                    
                                    
                                    event StrategyRevoked:
                                        strategy: indexed(address) # Address of the strategy that is revoked
                                    
                                    
                                    event StrategyRemovedFromQueue:
                                        strategy: indexed(address) # Address of the strategy that is removed from the withdrawal queue
                                    
                                    
                                    event StrategyAddedToQueue:
                                        strategy: indexed(address) # Address of the strategy that is added to the withdrawal queue
                                    
                                    
                                    # NOTE: Track the total for overhead targeting purposes
                                    strategies: public(HashMap[address, StrategyParams])
                                    MAXIMUM_STRATEGIES: constant(uint256) = 20
                                    DEGRADATION_COEFFICIENT: constant(uint256) = 10 ** 18
                                    
                                    # Ordering that `withdraw` uses to determine which strategies to pull funds from
                                    # NOTE: Does *NOT* have to match the ordering of all the current strategies that
                                    #       exist, but it is recommended that it does or else withdrawal depth is
                                    #       limited to only those inside the queue.
                                    # NOTE: Ordering is determined by governance, and should be balanced according
                                    #       to risk, slippage, and/or volatility. Can also be ordered to increase the
                                    #       withdrawal speed of a particular Strategy.
                                    # NOTE: The first time a ZERO_ADDRESS is encountered, it stops withdrawing
                                    withdrawalQueue: public(address[MAXIMUM_STRATEGIES])
                                    
                                    emergencyShutdown: public(bool)
                                    
                                    depositLimit: public(uint256)  # Limit for totalAssets the Vault can hold
                                    debtRatio: public(uint256)  # Debt ratio for the Vault across all strategies (in BPS, <= 10k)
                                    totalDebt: public(uint256)  # Amount of tokens that all strategies have borrowed
                                    lastReport: public(uint256)  # block.timestamp of last report
                                    activation: public(uint256)  # block.timestamp of contract deployment
                                    lockedProfit: public(uint256) # how much profit is locked and cant be withdrawn
                                    lockedProfitDegradation: public(uint256) # rate per block of degradation. DEGRADATION_COEFFICIENT is 100% per block
                                    rewards: public(address)  # Rewards contract where Governance fees are sent to
                                    # Governance Fee for management of Vault (given to `rewards`)
                                    managementFee: public(uint256)
                                    # Governance Fee for performance of Vault (given to `rewards`)
                                    performanceFee: public(uint256)
                                    MAX_BPS: constant(uint256) = 10_000  # 100%, or 10k basis points
                                    # NOTE: A four-century period will be missing 3 of its 100 Julian leap years, leaving 97.
                                    #       So the average year has 365 + 97/400 = 365.2425 days
                                    #       ERROR(Julian): -0.0078
                                    #       ERROR(Gregorian): -0.0003
                                    #       A day = 24 * 60 * 60 sec = 86400 sec
                                    #       365.2425 * 86400 = 31556952.0
                                    SECS_PER_YEAR: constant(uint256) = 31_556_952  # 365.2425 days
                                    # `nonces` track `permit` approvals with signature.
                                    nonces: public(HashMap[address, uint256])
                                    DOMAIN_SEPARATOR: public(bytes32)
                                    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)")
                                    
                                    
                                    @external
                                    def initialize(
                                        token: address,
                                        governance: address,
                                        rewards: address,
                                        nameOverride: String[64],
                                        symbolOverride: String[32],
                                        guardian: address = msg.sender,
                                        management: address =  msg.sender,
                                    ):
                                        """
                                        @notice
                                            Initializes the Vault, this is called only once, when the contract is
                                            deployed.
                                            The performance fee is set to 10% of yield, per Strategy.
                                            The management fee is set to 2%, per year.
                                            The initial deposit limit is set to 0 (deposits disabled); it must be
                                            updated after initialization.
                                        @dev
                                            If `nameOverride` is not specified, the name will be 'yearn'
                                            combined with the name of `token`.
                                    
                                            If `symbolOverride` is not specified, the symbol will be 'yv'
                                            combined with the symbol of `token`.
                                    
                                            The token used by the vault should not change balances outside transfers and 
                                            it must transfer the exact amount requested. Fee on transfer and rebasing are not supported.
                                        @param token The token that may be deposited into this Vault.
                                        @param governance The address authorized for governance interactions.
                                        @param rewards The address to distribute rewards to.
                                        @param management The address of the vault manager.
                                        @param nameOverride Specify a custom Vault name. Leave empty for default choice.
                                        @param symbolOverride Specify a custom Vault symbol name. Leave empty for default choice.
                                        @param guardian The address authorized for guardian interactions. Defaults to caller.
                                        """
                                        assert self.activation == 0  # dev: no devops199
                                        self.token = ERC20(token)
                                        if nameOverride == "":
                                            self.name = concat(DetailedERC20(token).symbol(), " yVault")
                                        else:
                                            self.name = nameOverride
                                        if symbolOverride == "":
                                            self.symbol = concat("yv", DetailedERC20(token).symbol())
                                        else:
                                            self.symbol = symbolOverride
                                        decimals: uint256 = DetailedERC20(token).decimals()
                                        self.decimals = decimals
                                        assert decimals < 256 # dev: see VVE-2020-0001
                                    
                                        self.governance = governance
                                        log UpdateGovernance(governance)
                                        self.management = management
                                        log UpdateManagement(management)
                                        self.rewards = rewards
                                        log UpdateRewards(rewards)
                                        self.guardian = guardian
                                        log UpdateGuardian(guardian)
                                        self.performanceFee = 1000  # 10% of yield (per Strategy)
                                        log UpdatePerformanceFee(convert(1000, uint256))
                                        self.managementFee = 200  # 2% per year
                                        log UpdateManagementFee(convert(200, uint256))
                                        self.lastReport = block.timestamp
                                        self.activation = block.timestamp
                                        self.lockedProfitDegradation = convert(DEGRADATION_COEFFICIENT * 46 / 10 ** 6 , uint256) # 6 hours in blocks
                                        # EIP-712
                                        self.DOMAIN_SEPARATOR = keccak256(
                                            concat(
                                                DOMAIN_TYPE_HASH,
                                                keccak256(convert("Yearn Vault", Bytes[11])),
                                                keccak256(convert(API_VERSION, Bytes[28])),
                                                convert(chain.id, bytes32),
                                                convert(self, bytes32)
                                            )
                                        )
                                    
                                    
                                    @pure
                                    @external
                                    def apiVersion() -> String[28]:
                                        """
                                        @notice
                                            Used to track the deployed version of this contract. In practice you
                                            can use this version number to compare with Yearn's GitHub and
                                            determine which version of the source matches this deployed contract.
                                        @dev
                                            All strategies must have an `apiVersion()` that matches the Vault's
                                            `API_VERSION`.
                                        @return API_VERSION which holds the current version of this contract.
                                        """
                                        return API_VERSION
                                    
                                    
                                    @external
                                    def setName(name: String[42]):
                                        """
                                        @notice
                                            Used to change the value of `name`.
                                    
                                            This may only be called by governance.
                                        @param name The new name to use.
                                        """
                                        assert msg.sender == self.governance
                                        self.name = name
                                    
                                    
                                    @external
                                    def setSymbol(symbol: String[20]):
                                        """
                                        @notice
                                            Used to change the value of `symbol`.
                                    
                                            This may only be called by governance.
                                        @param symbol The new symbol to use.
                                        """
                                        assert msg.sender == self.governance
                                        self.symbol = symbol
                                    
                                    
                                    # 2-phase commit for a change in governance
                                    @external
                                    def setGovernance(governance: address):
                                        """
                                        @notice
                                            Nominate a new address to use as governance.
                                    
                                            The change does not go into effect immediately. This function sets a
                                            pending change, and the governance address is not updated until
                                            the proposed governance address has accepted the responsibility.
                                    
                                            This may only be called by the current governance address.
                                        @param governance The address requested to take over Vault governance.
                                        """
                                        assert msg.sender == self.governance
                                        self.pendingGovernance = governance
                                    
                                    
                                    @external
                                    def acceptGovernance():
                                        """
                                        @notice
                                            Once a new governance address has been proposed using setGovernance(),
                                            this function may be called by the proposed address to accept the
                                            responsibility of taking over governance for this contract.
                                    
                                            This may only be called by the proposed governance address.
                                        @dev
                                            setGovernance() should be called by the existing governance address,
                                            prior to calling this function.
                                        """
                                        assert msg.sender == self.pendingGovernance
                                        self.governance = msg.sender
                                        log UpdateGovernance(msg.sender)
                                    
                                    
                                    @external
                                    def setManagement(management: address):
                                        """
                                        @notice
                                            Changes the management address.
                                            Management is able to make some investment decisions adjusting parameters.
                                    
                                            This may only be called by governance.
                                        @param management The address to use for managing.
                                        """
                                        assert msg.sender == self.governance
                                        self.management = management
                                        log UpdateManagement(management)
                                    
                                    
                                    @external
                                    def setRewards(rewards: address):
                                        """
                                        @notice
                                            Changes the rewards address. Any distributed rewards
                                            will cease flowing to the old address and begin flowing
                                            to this address once the change is in effect.
                                    
                                            This will not change any Strategy reports in progress, only
                                            new reports made after this change goes into effect.
                                    
                                            This may only be called by governance.
                                        @param rewards The address to use for collecting rewards.
                                        """
                                        assert msg.sender == self.governance
                                        assert not (rewards in [self, ZERO_ADDRESS])
                                        self.rewards = rewards
                                        log UpdateRewards(rewards)
                                    
                                    
                                    @external
                                    def setLockedProfitDegradation(degradation: uint256):
                                        """
                                        @notice
                                            Changes the locked profit degradation.
                                        @param degradation The rate of degradation in percent per second scaled to 1e18.
                                        """
                                        assert msg.sender == self.governance
                                        # Since "degradation" is of type uint256 it can never be less than zero
                                        assert degradation <= DEGRADATION_COEFFICIENT
                                        self.lockedProfitDegradation = degradation
                                    
                                    
                                    @external
                                    def setDepositLimit(limit: uint256):
                                        """
                                        @notice
                                            Changes the maximum amount of tokens that can be deposited in this Vault.
                                    
                                            Note, this is not how much may be deposited by a single depositor,
                                            but the maximum amount that may be deposited across all depositors.
                                    
                                            This may only be called by governance.
                                        @param limit The new deposit limit to use.
                                        """
                                        assert msg.sender == self.governance
                                        self.depositLimit = limit
                                        log UpdateDepositLimit(limit)
                                    
                                    
                                    @external
                                    def setPerformanceFee(fee: uint256):
                                        """
                                        @notice
                                            Used to change the value of `performanceFee`.
                                    
                                            Should set this value below the maximum strategist performance fee.
                                    
                                            This may only be called by governance.
                                        @param fee The new performance fee to use.
                                        """
                                        assert msg.sender == self.governance
                                        assert fee <= MAX_BPS / 2
                                        self.performanceFee = fee
                                        log UpdatePerformanceFee(fee)
                                    
                                    
                                    @external
                                    def setManagementFee(fee: uint256):
                                        """
                                        @notice
                                            Used to change the value of `managementFee`.
                                    
                                            This may only be called by governance.
                                        @param fee The new management fee to use.
                                        """
                                        assert msg.sender == self.governance
                                        assert fee <= MAX_BPS
                                        self.managementFee = fee
                                        log UpdateManagementFee(fee)
                                    
                                    
                                    @external
                                    def setGuardian(guardian: address):
                                        """
                                        @notice
                                            Used to change the address of `guardian`.
                                    
                                            This may only be called by governance or the existing guardian.
                                        @param guardian The new guardian address to use.
                                        """
                                        assert msg.sender in [self.guardian, self.governance]
                                        self.guardian = guardian
                                        log UpdateGuardian(guardian)
                                    
                                    
                                    @external
                                    def setEmergencyShutdown(active: bool):
                                        """
                                        @notice
                                            Activates or deactivates Vault mode where all Strategies go into full
                                            withdrawal.
                                    
                                            During Emergency Shutdown:
                                            1. No Users may deposit into the Vault (but may withdraw as usual.)
                                            2. Governance may not add new Strategies.
                                            3. Each Strategy must pay back their debt as quickly as reasonable to
                                                minimally affect their position.
                                            4. Only Governance may undo Emergency Shutdown.
                                    
                                            See contract level note for further details.
                                    
                                            This may only be called by governance or the guardian.
                                        @param active
                                            If true, the Vault goes into Emergency Shutdown. If false, the Vault
                                            goes back into Normal Operation.
                                        """
                                        if active:
                                            assert msg.sender in [self.guardian, self.governance]
                                        else:
                                            assert msg.sender == self.governance
                                        self.emergencyShutdown = active
                                        log EmergencyShutdown(active)
                                    
                                    
                                    @external
                                    def setWithdrawalQueue(queue: address[MAXIMUM_STRATEGIES]):
                                        """
                                        @notice
                                            Updates the withdrawalQueue to match the addresses and order specified
                                            by `queue`.
                                    
                                            There can be fewer strategies than the maximum, as well as fewer than
                                            the total number of strategies active in the vault. `withdrawalQueue`
                                            will be updated in a gas-efficient manner, assuming the input is well-
                                            ordered with 0x0 only at the end.
                                    
                                            This may only be called by governance or management.
                                        @dev
                                            This is order sensitive, specify the addresses in the order in which
                                            funds should be withdrawn (so `queue`[0] is the first Strategy withdrawn
                                            from, `queue`[1] is the second, etc.)
                                    
                                            This means that the least impactful Strategy (the Strategy that will have
                                            its core positions impacted the least by having funds removed) should be
                                            at `queue`[0], then the next least impactful at `queue`[1], and so on.
                                        @param queue
                                            The array of addresses to use as the new withdrawal queue. This is
                                            order sensitive.
                                        """
                                        assert msg.sender in [self.management, self.governance]
                                    
                                        # HACK: Temporary until Vyper adds support for Dynamic arrays
                                        old_queue: address[MAXIMUM_STRATEGIES] = empty(address[MAXIMUM_STRATEGIES])
                                        for i in range(MAXIMUM_STRATEGIES):
                                            old_queue[i] = self.withdrawalQueue[i] 
                                            if queue[i] == ZERO_ADDRESS:
                                                # NOTE: Cannot use this method to remove entries from the queue
                                                assert old_queue[i] == ZERO_ADDRESS
                                                break
                                            # NOTE: Cannot use this method to add more entries to the queue
                                            assert old_queue[i] != ZERO_ADDRESS
                                    
                                            assert self.strategies[queue[i]].activation > 0
                                    
                                            existsInOldQueue: bool = False
                                            for j in range(MAXIMUM_STRATEGIES):
                                                if queue[j] == ZERO_ADDRESS:
                                                    existsInOldQueue = True
                                                    break
                                                if queue[i] == old_queue[j]:
                                                    # NOTE: Ensure that every entry in queue prior to reordering exists now
                                                    existsInOldQueue = True
                                    
                                                if j <= i:
                                                    # NOTE: This will only check for duplicate entries in queue after `i`
                                                    continue
                                                assert queue[i] != queue[j]  # dev: do not add duplicate strategies
                                    
                                            assert existsInOldQueue # dev: do not add new strategies
                                    
                                            self.withdrawalQueue[i] = queue[i]
                                        log UpdateWithdrawalQueue(queue)
                                    
                                    
                                    @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.
                                        # HACK: Used to handle non-compliant tokens like USDT
                                        response: Bytes[32] = raw_call(
                                            token,
                                            concat(
                                                method_id("transfer(address,uint256)"),
                                                convert(receiver, bytes32),
                                                convert(amount, bytes32),
                                            ),
                                            max_outsize=32,
                                        )
                                        if len(response) > 0:
                                            assert convert(response, bool), "Transfer failed!"
                                    
                                    
                                    @internal
                                    def erc20_safe_transferFrom(token: address, sender: address, receiver: address, amount: uint256):
                                        # Used only to send tokens that are not the type managed by this Vault.
                                        # HACK: Used to handle non-compliant tokens like USDT
                                        response: Bytes[32] = raw_call(
                                            token,
                                            concat(
                                                method_id("transferFrom(address,address,uint256)"),
                                                convert(sender, bytes32),
                                                convert(receiver, bytes32),
                                                convert(amount, bytes32),
                                            ),
                                            max_outsize=32,
                                        )
                                        if len(response) > 0:
                                            assert convert(response, bool), "Transfer failed!"
                                    
                                    
                                    @internal
                                    def _transfer(sender: address, receiver: address, amount: uint256):
                                        # See note on `transfer()`.
                                    
                                        # Protect people from accidentally sending their shares to bad places
                                        assert receiver not in [self, ZERO_ADDRESS]
                                        self.balanceOf[sender] -= amount
                                        self.balanceOf[receiver] += amount
                                        log Transfer(sender, receiver, amount)
                                    
                                    
                                    @external
                                    def transfer(receiver: address, amount: uint256) -> bool:
                                        """
                                        @notice
                                            Transfers shares from the caller's address to `receiver`. This function
                                            will always return true, unless the user is attempting to transfer
                                            shares to this contract's address, or to 0x0.
                                        @param receiver
                                            The address shares are being transferred to. Must not be this contract's
                                            address, must not be 0x0.
                                        @param amount The quantity of shares to transfer.
                                        @return
                                            True if transfer is sent to an address other than this contract's or
                                            0x0, otherwise the transaction will fail.
                                        """
                                        self._transfer(msg.sender, receiver, amount)
                                        return True
                                    
                                    
                                    @external
                                    def transferFrom(sender: address, receiver: address, amount: uint256) -> bool:
                                        """
                                        @notice
                                            Transfers `amount` shares from `sender` to `receiver`. This operation will
                                            always return true, unless the user is attempting to transfer shares
                                            to this contract's address, or to 0x0.
                                    
                                            Unless the caller has given this contract unlimited approval,
                                            transfering shares will decrement the caller's `allowance` by `amount`.
                                        @param sender The address shares are being transferred from.
                                        @param receiver
                                            The address shares are being transferred to. Must not be this contract's
                                            address, must not be 0x0.
                                        @param amount The quantity of shares to transfer.
                                        @return
                                            True if transfer is sent to an address other than this contract's or
                                            0x0, otherwise the transaction will fail.
                                        """
                                        # Unlimited approval (saves an SSTORE)
                                        if (self.allowance[sender][msg.sender] < MAX_UINT256):
                                            allowance: uint256 = self.allowance[sender][msg.sender] - amount
                                            self.allowance[sender][msg.sender] = allowance
                                            # NOTE: Allows log filters to have a full accounting of allowance changes
                                            log Approval(sender, msg.sender, allowance)
                                        self._transfer(sender, receiver, amount)
                                        return True
                                    
                                    
                                    @external
                                    def approve(spender: address, amount: uint256) -> bool:
                                        """
                                        @dev Approve the passed address to spend the specified amount of tokens on behalf of
                                             `msg.sender`. 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. See https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                                        @param spender The address which will spend the funds.
                                        @param amount The amount of tokens to be spent.
                                        """
                                        self.allowance[msg.sender][spender] = amount
                                        log Approval(msg.sender, spender, amount)
                                        return True
                                    
                                    
                                    @external
                                    def increaseAllowance(spender: address, amount: uint256) -> bool:
                                        """
                                        @dev Increase the allowance of the passed address to spend the total amount of tokens
                                             on behalf of msg.sender. This method mitigates the risk that someone may use both
                                             the old and the new allowance by unfortunate transaction ordering.
                                             See https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                                        @param spender The address which will spend the funds.
                                        @param amount The amount of tokens to increase the allowance by.
                                        """
                                        self.allowance[msg.sender][spender] += amount
                                        log Approval(msg.sender, spender, self.allowance[msg.sender][spender])
                                        return True
                                    
                                    
                                    @external
                                    def decreaseAllowance(spender: address, amount: uint256) -> bool:
                                        """
                                        @dev Decrease the allowance of the passed address to spend the total amount of tokens
                                             on behalf of msg.sender. This method mitigates the risk that someone may use both
                                             the old and the new allowance by unfortunate transaction ordering.
                                             See https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                                        @param spender The address which will spend the funds.
                                        @param amount The amount of tokens to decrease the allowance by.
                                        """
                                        self.allowance[msg.sender][spender] -= amount
                                        log Approval(msg.sender, spender, self.allowance[msg.sender][spender])
                                        return True
                                    
                                    
                                    @external
                                    def permit(owner: address, spender: address, amount: uint256, expiry: uint256, signature: Bytes[65]) -> bool:
                                        """
                                        @notice
                                            Approves spender by owner's signature to expend owner's tokens.
                                            See https://eips.ethereum.org/EIPS/eip-2612.
                                    
                                        @param owner The address which is a source of funds and has signed the Permit.
                                        @param spender The address which is allowed to spend the funds.
                                        @param amount The amount of tokens to be spent.
                                        @param expiry The timestamp after which the Permit is no longer valid.
                                        @param signature A valid secp256k1 signature of Permit by owner encoded as r, s, v.
                                        @return True, if transaction completes successfully
                                        """
                                        assert owner != ZERO_ADDRESS  # dev: invalid owner
                                        assert expiry == 0 or expiry >= block.timestamp  # dev: 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(expiry, bytes32),
                                                    )
                                                )
                                            )
                                        )
                                        # NOTE: signature is packed as r, s, v
                                        r: uint256 = convert(slice(signature, 0, 32), uint256)
                                        s: uint256 = convert(slice(signature, 32, 32), uint256)
                                        v: uint256 = convert(slice(signature, 64, 1), uint256)
                                        assert ecrecover(digest, v, r, s) == owner  # dev: invalid signature
                                        self.allowance[owner][spender] = amount
                                        self.nonces[owner] = nonce + 1
                                        log Approval(owner, spender, amount)
                                        return True
                                    
                                    
                                    @view
                                    @internal
                                    def _totalAssets() -> uint256:
                                        # See note on `totalAssets()`.
                                        return self.token.balanceOf(self) + self.totalDebt
                                    
                                    
                                    @view
                                    @external
                                    def totalAssets() -> uint256:
                                        """
                                        @notice
                                            Returns the total quantity of all assets under control of this
                                            Vault, whether they're loaned out to a Strategy, or currently held in
                                            the Vault.
                                        @return The total assets under control of this Vault.
                                        """
                                        return self._totalAssets()
                                    
                                    
                                    @view
                                    @internal
                                    def _calculateLockedProfit() -> uint256:
                                        lockedFundsRatio: uint256 = (block.timestamp - self.lastReport) * self.lockedProfitDegradation
                                    
                                        if(lockedFundsRatio < DEGRADATION_COEFFICIENT):
                                            lockedProfit: uint256 = self.lockedProfit
                                            return lockedProfit - (
                                                    lockedFundsRatio
                                                    * lockedProfit
                                                    / DEGRADATION_COEFFICIENT
                                                )
                                        else:        
                                            return 0
                                    
                                    @view
                                    @internal
                                    def _freeFunds() -> uint256:
                                        return self._totalAssets() - self._calculateLockedProfit()
                                    
                                    @internal
                                    def _issueSharesForAmount(to: address, amount: uint256) -> uint256:
                                        # Issues `amount` Vault shares to `to`.
                                        # Shares must be issued prior to taking on new collateral, or
                                        # calculation will be wrong. This means that only *trusted* tokens
                                        # (with no capability for exploitative behavior) can be used.
                                        shares: uint256 = 0
                                        # HACK: Saves 2 SLOADs (~200 gas, post-Berlin)
                                        totalSupply: uint256 = self.totalSupply
                                        if totalSupply > 0:
                                            # Mint amount of shares based on what the Vault is managing overall
                                            # NOTE: if sqrt(token.totalSupply()) > 1e39, this could potentially revert
                                            shares =  amount * totalSupply / self._freeFunds()  # dev: no free funds
                                        else:
                                            # No existing shares, so mint 1:1
                                            shares = amount
                                        assert shares != 0 # dev: division rounding resulted in zero
                                    
                                        # Mint new shares
                                        self.totalSupply = totalSupply + shares
                                        self.balanceOf[to] += shares
                                        log Transfer(ZERO_ADDRESS, to, shares)
                                    
                                        return shares
                                    
                                    
                                    @external
                                    @nonreentrant("withdraw")
                                    def deposit(_amount: uint256 = MAX_UINT256, recipient: address = msg.sender) -> uint256:
                                        """
                                        @notice
                                            Deposits `_amount` `token`, issuing shares to `recipient`. If the
                                            Vault is in Emergency Shutdown, deposits will not be accepted and this
                                            call will fail.
                                        @dev
                                            Measuring quantity of shares to issues is based on the total
                                            outstanding debt that this contract has ("expected value") instead
                                            of the total balance sheet it has ("estimated value") has important
                                            security considerations, and is done intentionally. If this value were
                                            measured against external systems, it could be purposely manipulated by
                                            an attacker to withdraw more assets than they otherwise should be able
                                            to claim by redeeming their shares.
                                    
                                            On deposit, this means that shares are issued against the total amount
                                            that the deposited capital can be given in service of the debt that
                                            Strategies assume. If that number were to be lower than the "expected
                                            value" at some future point, depositing shares via this method could
                                            entitle the depositor to *less* than the deposited value once the
                                            "realized value" is updated from further reports by the Strategies
                                            to the Vaults.
                                    
                                            Care should be taken by integrators to account for this discrepancy,
                                            by using the view-only methods of this contract (both off-chain and
                                            on-chain) to determine if depositing into the Vault is a "good idea".
                                        @param _amount The quantity of tokens to deposit, defaults to all.
                                        @param recipient
                                            The address to issue the shares in this Vault to. Defaults to the
                                            caller's address.
                                        @return The issued Vault shares.
                                        """
                                        assert not self.emergencyShutdown  # Deposits are locked out
                                        assert recipient not in [self, ZERO_ADDRESS]
                                    
                                        amount: uint256 = _amount
                                    
                                        # If _amount not specified, transfer the full token balance,
                                        # up to deposit limit
                                        if amount == MAX_UINT256:
                                            amount = min(
                                                self.depositLimit - self._totalAssets(),
                                                self.token.balanceOf(msg.sender),
                                            )
                                        else:
                                            # Ensure deposit limit is respected
                                            assert self._totalAssets() + amount <= self.depositLimit
                                    
                                        # Ensure we are depositing something
                                        assert amount > 0
                                    
                                        # Issue new shares (needs to be done before taking deposit to be accurate)
                                        # Shares are issued to recipient (may be different from msg.sender)
                                        # See @dev note, above.
                                        shares: uint256 = self._issueSharesForAmount(recipient, amount)
                                    
                                        # Tokens are transferred from msg.sender (may be different from _recipient)
                                        self.erc20_safe_transferFrom(self.token.address, msg.sender, self, amount)
                                    
                                        return shares  # Just in case someone wants them
                                    
                                    
                                    @view
                                    @internal
                                    def _shareValue(shares: uint256) -> uint256:
                                        # Returns price = 1:1 if vault is empty
                                        if self.totalSupply == 0:
                                            return shares
                                    
                                        # Determines the current value of `shares`.
                                        # NOTE: if sqrt(Vault.totalAssets()) >>> 1e39, this could potentially revert
                                    
                                        return (
                                            shares
                                            * self._freeFunds()
                                            / self.totalSupply
                                        )
                                    
                                    
                                    @view
                                    @internal
                                    def _sharesForAmount(amount: uint256) -> uint256:
                                        # Determines how many shares `amount` of token would receive.
                                        # See dev note on `deposit`.
                                        _freeFunds: uint256 = self._freeFunds()
                                        if _freeFunds > 0:
                                            # NOTE: if sqrt(token.totalSupply()) > 1e37, this could potentially revert
                                            return  (
                                                amount
                                                * self.totalSupply
                                                / _freeFunds 
                                            )
                                        else:
                                            return 0
                                    
                                    
                                    @view
                                    @external
                                    def maxAvailableShares() -> uint256:
                                        """
                                        @notice
                                            Determines the maximum quantity of shares this Vault can facilitate a
                                            withdrawal for, factoring in assets currently residing in the Vault,
                                            as well as those deployed to strategies on the Vault's balance sheet.
                                        @dev
                                            Regarding how shares are calculated, see dev note on `deposit`.
                                    
                                            If you want to calculated the maximum a user could withdraw up to,
                                            you want to use this function.
                                    
                                            Note that the amount provided by this function is the theoretical
                                            maximum possible from withdrawing, the real amount depends on the
                                            realized losses incurred during withdrawal.
                                        @return The total quantity of shares this Vault can provide.
                                        """
                                        shares: uint256 = self._sharesForAmount(self.token.balanceOf(self))
                                    
                                        for strategy in self.withdrawalQueue:
                                            if strategy == ZERO_ADDRESS:
                                                break
                                            shares += self._sharesForAmount(self.strategies[strategy].totalDebt)
                                    
                                        return shares
                                    
                                    
                                    @internal
                                    def _reportLoss(strategy: address, loss: uint256):
                                        # Loss can only be up the amount of debt issued to strategy
                                        totalDebt: uint256 = self.strategies[strategy].totalDebt
                                        assert totalDebt >= loss
                                    
                                        # Also, make sure we reduce our trust with the strategy by the amount of loss
                                        if self.debtRatio != 0: # if vault with single strategy that is set to EmergencyOne
                                            # NOTE: The context to this calculation is different than the calculation in `_reportLoss`,
                                            # this calculation intentionally approximates via `totalDebt` to avoid manipulatable results
                                            ratio_change: uint256 = min(
                                                # NOTE: This calculation isn't 100% precise, the adjustment is ~10%-20% more severe due to EVM math
                                                loss * self.debtRatio / self.totalDebt,
                                                self.strategies[strategy].debtRatio,
                                            )
                                            self.strategies[strategy].debtRatio -= ratio_change
                                            self.debtRatio -= ratio_change
                                        # Finally, adjust our strategy's parameters by the loss
                                        self.strategies[strategy].totalLoss += loss
                                        self.strategies[strategy].totalDebt = totalDebt - loss
                                        self.totalDebt -= loss
                                    
                                    
                                    @external
                                    @nonreentrant("withdraw")
                                    def withdraw(
                                        maxShares: uint256 = MAX_UINT256,
                                        recipient: address = msg.sender,
                                        maxLoss: uint256 = 1,  # 0.01% [BPS]
                                    ) -> uint256:
                                        """
                                        @notice
                                            Withdraws the calling account's tokens from this Vault, redeeming
                                            amount `_shares` for an appropriate amount of tokens.
                                    
                                            See note on `setWithdrawalQueue` for further details of withdrawal
                                            ordering and behavior.
                                        @dev
                                            Measuring the value of shares is based on the total outstanding debt
                                            that this contract has ("expected value") instead of the total balance
                                            sheet it has ("estimated value") has important security considerations,
                                            and is done intentionally. If this value were measured against external
                                            systems, it could be purposely manipulated by an attacker to withdraw
                                            more assets than they otherwise should be able to claim by redeeming
                                            their shares.
                                    
                                            On withdrawal, this means that shares are redeemed against the total
                                            amount that the deposited capital had "realized" since the point it
                                            was deposited, up until the point it was withdrawn. If that number
                                            were to be higher than the "expected value" at some future point,
                                            withdrawing shares via this method could entitle the depositor to
                                            *more* than the expected value once the "realized value" is updated
                                            from further reports by the Strategies to the Vaults.
                                    
                                            Under exceptional scenarios, this could cause earlier withdrawals to
                                            earn "more" of the underlying assets than Users might otherwise be
                                            entitled to, if the Vault's estimated value were otherwise measured
                                            through external means, accounting for whatever exceptional scenarios
                                            exist for the Vault (that aren't covered by the Vault's own design.)
                                    
                                            In the situation where a large withdrawal happens, it can empty the 
                                            vault balance and the strategies in the withdrawal queue. 
                                            Strategies not in the withdrawal queue will have to be harvested to 
                                            rebalance the funds and make the funds available again to withdraw.
                                        @param maxShares
                                            How many shares to try and redeem for tokens, defaults to all.
                                        @param recipient
                                            The address to issue the shares in this Vault to. Defaults to the
                                            caller's address.
                                        @param maxLoss
                                            The maximum acceptable loss to sustain on withdrawal. Defaults to 0.01%.
                                            If a loss is specified, up to that amount of shares may be burnt to cover losses on withdrawal.
                                        @return The quantity of tokens redeemed for `_shares`.
                                        """
                                        shares: uint256 = maxShares  # May reduce this number below
                                    
                                        # Max Loss is <=100%, revert otherwise
                                        assert maxLoss <= MAX_BPS
                                    
                                        # If _shares not specified, transfer full share balance
                                        if shares == MAX_UINT256:
                                            shares = self.balanceOf[msg.sender]
                                    
                                        # Limit to only the shares they own
                                        assert shares <= self.balanceOf[msg.sender]
                                    
                                        # Ensure we are withdrawing something
                                        assert shares > 0
                                    
                                        # See @dev note, above.
                                        value: uint256 = self._shareValue(shares)
                                    
                                        if value > self.token.balanceOf(self):
                                            totalLoss: uint256 = 0
                                            # We need to go get some from our strategies in the withdrawal queue
                                            # NOTE: This performs forced withdrawals from each Strategy. During
                                            #       forced withdrawal, a Strategy may realize a loss. That loss
                                            #       is reported back to the Vault, and the will affect the amount
                                            #       of tokens that the withdrawer receives for their shares. They
                                            #       can optionally specify the maximum acceptable loss (in BPS)
                                            #       to prevent excessive losses on their withdrawals (which may
                                            #       happen in certain edge cases where Strategies realize a loss)
                                            for strategy in self.withdrawalQueue:
                                                if strategy == ZERO_ADDRESS:
                                                    break  # We've exhausted the queue
                                    
                                                vault_balance: uint256 = self.token.balanceOf(self)
                                                if value <= vault_balance:
                                                    break  # We're done withdrawing
                                    
                                                amountNeeded: uint256 = value - vault_balance
                                    
                                                # NOTE: Don't withdraw more than the debt so that Strategy can still
                                                #       continue to work based on the profits it has
                                                # NOTE: This means that user will lose out on any profits that each
                                                #       Strategy in the queue would return on next harvest, benefiting others
                                                amountNeeded = min(amountNeeded, self.strategies[strategy].totalDebt)
                                                if amountNeeded == 0:
                                                    continue  # Nothing to withdraw from this Strategy, try the next one
                                    
                                                # Force withdraw amount from each Strategy in the order set by governance
                                                loss: uint256 = Strategy(strategy).withdraw(amountNeeded)
                                                withdrawn: uint256 = self.token.balanceOf(self) - vault_balance
                                    
                                                # NOTE: Withdrawer incurs any losses from liquidation
                                                if loss > 0:
                                                    value -= loss
                                                    totalLoss += loss
                                                    self._reportLoss(strategy, loss)
                                    
                                                # Reduce the Strategy's debt by the amount withdrawn ("realized returns")
                                                # NOTE: This doesn't add to returns as it's not earned by "normal means"
                                                self.strategies[strategy].totalDebt -= withdrawn
                                                self.totalDebt -= withdrawn
                                    
                                            # NOTE: We have withdrawn everything possible out of the withdrawal queue
                                            #       but we still don't have enough to fully pay them back, so adjust
                                            #       to the total amount we've freed up through forced withdrawals
                                            vault_balance: uint256 = self.token.balanceOf(self)
                                            if value > vault_balance:
                                                value = vault_balance
                                                # NOTE: Burn # of shares that corresponds to what Vault has on-hand,
                                                #       including the losses that were incurred above during withdrawals
                                                shares = self._sharesForAmount(value + totalLoss)
                                    
                                            # NOTE: This loss protection is put in place to revert if losses from
                                            #       withdrawing are more than what is considered acceptable.
                                            assert totalLoss <= maxLoss * (value + totalLoss) / MAX_BPS
                                    
                                        # Burn shares (full value of what is being withdrawn)
                                        self.totalSupply -= shares
                                        self.balanceOf[msg.sender] -= shares
                                        log Transfer(msg.sender, ZERO_ADDRESS, shares)
                                    
                                        # Withdraw remaining balance to _recipient (may be different to msg.sender) (minus fee)
                                        self.erc20_safe_transfer(self.token.address, recipient, value)
                                    
                                        return value
                                    
                                    
                                    @view
                                    @external
                                    def pricePerShare() -> uint256:
                                        """
                                        @notice Gives the price for a single Vault share.
                                        @dev See dev note on `withdraw`.
                                        @return The value of a single share.
                                        """
                                        return self._shareValue(10 ** self.decimals)
                                    
                                    
                                    @internal
                                    def _organizeWithdrawalQueue():
                                        # Reorganize `withdrawalQueue` based on premise that if there is an
                                        # empty value between two actual values, then the empty value should be
                                        # replaced by the later value.
                                        # NOTE: Relative ordering of non-zero values is maintained.
                                        offset: uint256 = 0
                                        for idx in range(MAXIMUM_STRATEGIES):
                                            strategy: address = self.withdrawalQueue[idx]
                                            if strategy == ZERO_ADDRESS:
                                                offset += 1  # how many values we need to shift, always `<= idx`
                                            elif offset > 0:
                                                self.withdrawalQueue[idx - offset] = strategy
                                                self.withdrawalQueue[idx] = ZERO_ADDRESS
                                    
                                    
                                    @external
                                    def addStrategy(
                                        strategy: address,
                                        debtRatio: uint256,
                                        minDebtPerHarvest: uint256,
                                        maxDebtPerHarvest: uint256,
                                        performanceFee: uint256,
                                    ):
                                        """
                                        @notice
                                            Add a Strategy to the Vault.
                                    
                                            This may only be called by governance.
                                        @dev
                                            The Strategy will be appended to `withdrawalQueue`, call
                                            `setWithdrawalQueue` to change the order.
                                        @param strategy The address of the Strategy to add.
                                        @param debtRatio
                                            The share of the total assets in the `vault that the `strategy` has access to.
                                        @param minDebtPerHarvest
                                            Lower limit on the increase of debt since last harvest
                                        @param maxDebtPerHarvest
                                            Upper limit on the increase of debt since last harvest
                                        @param performanceFee
                                            The fee the strategist will receive based on this Vault's performance.
                                        """
                                        # Check if queue is full
                                        assert self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] == ZERO_ADDRESS
                                    
                                        # Check calling conditions
                                        assert not self.emergencyShutdown
                                        assert msg.sender == self.governance
                                    
                                        # Check strategy configuration
                                        assert strategy != ZERO_ADDRESS
                                        assert self.strategies[strategy].activation == 0
                                        assert self == Strategy(strategy).vault()
                                        assert self.token.address == Strategy(strategy).want()
                                    
                                        # Check strategy parameters
                                        assert self.debtRatio + debtRatio <= MAX_BPS
                                        assert minDebtPerHarvest <= maxDebtPerHarvest
                                        assert performanceFee <= MAX_BPS / 2 
                                    
                                        # Add strategy to approved strategies
                                        self.strategies[strategy] = StrategyParams({
                                            performanceFee: performanceFee,
                                            activation: block.timestamp,
                                            debtRatio: debtRatio,
                                            minDebtPerHarvest: minDebtPerHarvest,
                                            maxDebtPerHarvest: maxDebtPerHarvest,
                                            lastReport: block.timestamp,
                                            totalDebt: 0,
                                            totalGain: 0,
                                            totalLoss: 0,
                                        })
                                        log StrategyAdded(strategy, debtRatio, minDebtPerHarvest, maxDebtPerHarvest, performanceFee)
                                    
                                        # Update Vault parameters
                                        self.debtRatio += debtRatio
                                    
                                        # Add strategy to the end of the withdrawal queue
                                        self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] = strategy
                                        self._organizeWithdrawalQueue()
                                    
                                    
                                    @external
                                    def updateStrategyDebtRatio(
                                        strategy: address,
                                        debtRatio: uint256,
                                    ):
                                        """
                                        @notice
                                            Change the quantity of assets `strategy` may manage.
                                    
                                            This may be called by governance or management.
                                        @param strategy The Strategy to update.
                                        @param debtRatio The quantity of assets `strategy` may now manage.
                                        """
                                        assert msg.sender in [self.management, self.governance]
                                        assert self.strategies[strategy].activation > 0
                                        self.debtRatio -= self.strategies[strategy].debtRatio
                                        self.strategies[strategy].debtRatio = debtRatio
                                        self.debtRatio += debtRatio
                                        assert self.debtRatio <= MAX_BPS
                                        log StrategyUpdateDebtRatio(strategy, debtRatio)
                                    
                                    
                                    @external
                                    def updateStrategyMinDebtPerHarvest(
                                        strategy: address,
                                        minDebtPerHarvest: uint256,
                                    ):
                                        """
                                        @notice
                                            Change the quantity assets per block this Vault may deposit to or
                                            withdraw from `strategy`.
                                    
                                            This may only be called by governance or management.
                                        @param strategy The Strategy to update.
                                        @param minDebtPerHarvest
                                            Lower limit on the increase of debt since last harvest
                                        """
                                        assert msg.sender in [self.management, self.governance]
                                        assert self.strategies[strategy].activation > 0
                                        assert self.strategies[strategy].maxDebtPerHarvest >= minDebtPerHarvest
                                        self.strategies[strategy].minDebtPerHarvest = minDebtPerHarvest
                                        log StrategyUpdateMinDebtPerHarvest(strategy, minDebtPerHarvest)
                                    
                                    
                                    @external
                                    def updateStrategyMaxDebtPerHarvest(
                                        strategy: address,
                                        maxDebtPerHarvest: uint256,
                                    ):
                                        """
                                        @notice
                                            Change the quantity assets per block this Vault may deposit to or
                                            withdraw from `strategy`.
                                    
                                            This may only be called by governance or management.
                                        @param strategy The Strategy to update.
                                        @param maxDebtPerHarvest
                                            Upper limit on the increase of debt since last harvest
                                        """
                                        assert msg.sender in [self.management, self.governance]
                                        assert self.strategies[strategy].activation > 0
                                        assert self.strategies[strategy].minDebtPerHarvest <= maxDebtPerHarvest
                                        self.strategies[strategy].maxDebtPerHarvest = maxDebtPerHarvest
                                        log StrategyUpdateMaxDebtPerHarvest(strategy, maxDebtPerHarvest)
                                    
                                    
                                    @external
                                    def updateStrategyPerformanceFee(
                                        strategy: address,
                                        performanceFee: uint256,
                                    ):
                                        """
                                        @notice
                                            Change the fee the strategist will receive based on this Vault's
                                            performance.
                                    
                                            This may only be called by governance.
                                        @param strategy The Strategy to update.
                                        @param performanceFee The new fee the strategist will receive.
                                        """
                                        assert msg.sender == self.governance
                                        assert performanceFee <= MAX_BPS / 2
                                        assert self.strategies[strategy].activation > 0
                                        self.strategies[strategy].performanceFee = performanceFee
                                        log StrategyUpdatePerformanceFee(strategy, performanceFee)
                                    
                                    
                                    @internal
                                    def _revokeStrategy(strategy: address):
                                        self.debtRatio -= self.strategies[strategy].debtRatio
                                        self.strategies[strategy].debtRatio = 0
                                        log StrategyRevoked(strategy)
                                    
                                    
                                    @external
                                    def migrateStrategy(oldVersion: address, newVersion: address):
                                        """
                                        @notice
                                            Migrates a Strategy, including all assets from `oldVersion` to
                                            `newVersion`.
                                    
                                            This may only be called by governance.
                                        @dev
                                            Strategy must successfully migrate all capital and positions to new
                                            Strategy, or else this will upset the balance of the Vault.
                                    
                                            The new Strategy should be "empty" e.g. have no prior commitments to
                                            this Vault, otherwise it could have issues.
                                        @param oldVersion The existing Strategy to migrate from.
                                        @param newVersion The new Strategy to migrate to.
                                        """
                                        assert msg.sender == self.governance
                                        assert newVersion != ZERO_ADDRESS
                                        assert self.strategies[oldVersion].activation > 0
                                        assert self.strategies[newVersion].activation == 0
                                    
                                        strategy: StrategyParams = self.strategies[oldVersion]
                                    
                                        self._revokeStrategy(oldVersion)
                                        # _revokeStrategy will lower the debtRatio
                                        self.debtRatio += strategy.debtRatio
                                        # Debt is migrated to new strategy
                                        self.strategies[oldVersion].totalDebt = 0
                                    
                                        self.strategies[newVersion] = StrategyParams({
                                            performanceFee: strategy.performanceFee,
                                            # NOTE: use last report for activation time, so E[R] calc works
                                            activation: strategy.lastReport,
                                            debtRatio: strategy.debtRatio,
                                            minDebtPerHarvest: strategy.minDebtPerHarvest,
                                            maxDebtPerHarvest: strategy.maxDebtPerHarvest,
                                            lastReport: strategy.lastReport,
                                            totalDebt: strategy.totalDebt,
                                            totalGain: 0,
                                            totalLoss: 0,
                                        })
                                    
                                        Strategy(oldVersion).migrate(newVersion)
                                        log StrategyMigrated(oldVersion, newVersion)
                                    
                                        for idx in range(MAXIMUM_STRATEGIES):
                                            if self.withdrawalQueue[idx] == oldVersion:
                                                self.withdrawalQueue[idx] = newVersion
                                                return  # Don't need to reorder anything because we swapped
                                    
                                    
                                    @external
                                    def revokeStrategy(strategy: address = msg.sender):
                                        """
                                        @notice
                                            Revoke a Strategy, setting its debt limit to 0 and preventing any
                                            future deposits.
                                    
                                            This function should only be used in the scenario where the Strategy is
                                            being retired but no migration of the positions are possible, or in the
                                            extreme scenario that the Strategy needs to be put into "Emergency Exit"
                                            mode in order for it to exit as quickly as possible. The latter scenario
                                            could be for any reason that is considered "critical" that the Strategy
                                            exits its position as fast as possible, such as a sudden change in market
                                            conditions leading to losses, or an imminent failure in an external
                                            dependency.
                                    
                                            This may only be called by governance, the guardian, or the Strategy
                                            itself. Note that a Strategy will only revoke itself during emergency
                                            shutdown.
                                        @param strategy The Strategy to revoke.
                                        """
                                        assert msg.sender in [strategy, self.governance, self.guardian]
                                        assert self.strategies[strategy].debtRatio != 0 # dev: already zero
                                    
                                        self._revokeStrategy(strategy)
                                    
                                    
                                    @external
                                    def addStrategyToQueue(strategy: address):
                                        """
                                        @notice
                                            Adds `strategy` to `withdrawalQueue`.
                                    
                                            This may only be called by governance or management.
                                        @dev
                                            The Strategy will be appended to `withdrawalQueue`, call
                                            `setWithdrawalQueue` to change the order.
                                        @param strategy The Strategy to add.
                                        """
                                        assert msg.sender in [self.management, self.governance]
                                        # Must be a current Strategy
                                        assert self.strategies[strategy].activation > 0
                                        # Can't already be in the queue
                                        last_idx: uint256 = 0
                                        for s in self.withdrawalQueue:
                                            if s == ZERO_ADDRESS:
                                                break
                                            assert s != strategy
                                            last_idx += 1
                                        # Check if queue is full
                                        assert last_idx < MAXIMUM_STRATEGIES
                                    
                                        self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] = strategy
                                        self._organizeWithdrawalQueue()
                                        log StrategyAddedToQueue(strategy)
                                    
                                    
                                    @external
                                    def removeStrategyFromQueue(strategy: address):
                                        """
                                        @notice
                                            Remove `strategy` from `withdrawalQueue`.
                                    
                                            This may only be called by governance or management.
                                        @dev
                                            We don't do this with revokeStrategy because it should still
                                            be possible to withdraw from the Strategy if it's unwinding.
                                        @param strategy The Strategy to remove.
                                        """
                                        assert msg.sender in [self.management, self.governance]
                                        for idx in range(MAXIMUM_STRATEGIES):
                                            if self.withdrawalQueue[idx] == strategy:
                                                self.withdrawalQueue[idx] = ZERO_ADDRESS
                                                self._organizeWithdrawalQueue()
                                                log StrategyRemovedFromQueue(strategy)
                                                return  # We found the right location and cleared it
                                        raise  # We didn't find the Strategy in the queue
                                    
                                    
                                    @view
                                    @internal
                                    def _debtOutstanding(strategy: address) -> uint256:
                                        # See note on `debtOutstanding()`.
                                        if self.debtRatio == 0:
                                            return self.strategies[strategy].totalDebt
                                    
                                        strategy_debtLimit: uint256 = (
                                            self.strategies[strategy].debtRatio
                                            * self._totalAssets()
                                            / MAX_BPS
                                        )
                                        strategy_totalDebt: uint256 = self.strategies[strategy].totalDebt
                                    
                                        if self.emergencyShutdown:
                                            return strategy_totalDebt
                                        elif strategy_totalDebt <= strategy_debtLimit:
                                            return 0
                                        else:
                                            return strategy_totalDebt - strategy_debtLimit
                                    
                                    
                                    @view
                                    @external
                                    def debtOutstanding(strategy: address = msg.sender) -> uint256:
                                        """
                                        @notice
                                            Determines if `strategy` is past its debt limit and if any tokens
                                            should be withdrawn to the Vault.
                                        @param strategy The Strategy to check. Defaults to the caller.
                                        @return The quantity of tokens to withdraw.
                                        """
                                        return self._debtOutstanding(strategy)
                                    
                                    
                                    @view
                                    @internal
                                    def _creditAvailable(strategy: address) -> uint256:
                                        # See note on `creditAvailable()`.
                                        if self.emergencyShutdown:
                                            return 0
                                        vault_totalAssets: uint256 = self._totalAssets()
                                        vault_debtLimit: uint256 =  self.debtRatio * vault_totalAssets / MAX_BPS 
                                        vault_totalDebt: uint256 = self.totalDebt
                                        strategy_debtLimit: uint256 = self.strategies[strategy].debtRatio * vault_totalAssets / MAX_BPS
                                        strategy_totalDebt: uint256 = self.strategies[strategy].totalDebt
                                        strategy_minDebtPerHarvest: uint256 = self.strategies[strategy].minDebtPerHarvest
                                        strategy_maxDebtPerHarvest: uint256 = self.strategies[strategy].maxDebtPerHarvest
                                    
                                        # Exhausted credit line
                                        if strategy_debtLimit <= strategy_totalDebt or vault_debtLimit <= vault_totalDebt:
                                            return 0
                                    
                                        # Start with debt limit left for the Strategy
                                        available: uint256 = strategy_debtLimit - strategy_totalDebt
                                    
                                        # Adjust by the global debt limit left
                                        available = min(available, vault_debtLimit - vault_totalDebt)
                                    
                                        # Can only borrow up to what the contract has in reserve
                                        # NOTE: Running near 100% is discouraged
                                        available = min(available, self.token.balanceOf(self))
                                    
                                        # Adjust by min and max borrow limits (per harvest)
                                        # NOTE: min increase can be used to ensure that if a strategy has a minimum
                                        #       amount of capital needed to purchase a position, it's not given capital
                                        #       it can't make use of yet.
                                        # NOTE: max increase is used to make sure each harvest isn't bigger than what
                                        #       is authorized. This combined with adjusting min and max periods in
                                        #       `BaseStrategy` can be used to effect a "rate limit" on capital increase.
                                        if available < strategy_minDebtPerHarvest:
                                            return 0
                                        else:
                                            return min(available, strategy_maxDebtPerHarvest)
                                    
                                    @view
                                    @external
                                    def creditAvailable(strategy: address = msg.sender) -> uint256:
                                        """
                                        @notice
                                            Amount of tokens in Vault a Strategy has access to as a credit line.
                                    
                                            This will check the Strategy's debt limit, as well as the tokens
                                            available in the Vault, and determine the maximum amount of tokens
                                            (if any) the Strategy may draw on.
                                    
                                            In the rare case the Vault is in emergency shutdown this will return 0.
                                        @param strategy The Strategy to check. Defaults to caller.
                                        @return The quantity of tokens available for the Strategy to draw on.
                                        """
                                        return self._creditAvailable(strategy)
                                    
                                    
                                    @view
                                    @internal
                                    def _expectedReturn(strategy: address) -> uint256:
                                        # See note on `expectedReturn()`.
                                        strategy_lastReport: uint256 = self.strategies[strategy].lastReport
                                        timeSinceLastHarvest: uint256 = block.timestamp - strategy_lastReport
                                        totalHarvestTime: uint256 = strategy_lastReport - self.strategies[strategy].activation
                                    
                                        # NOTE: If either `timeSinceLastHarvest` or `totalHarvestTime` is 0, we can short-circuit to `0`
                                        if timeSinceLastHarvest > 0 and totalHarvestTime > 0 and Strategy(strategy).isActive():
                                            # NOTE: Unlikely to throw unless strategy accumalates >1e68 returns
                                            # NOTE: Calculate average over period of time where harvests have occured in the past
                                            return (
                                                self.strategies[strategy].totalGain
                                                * timeSinceLastHarvest
                                                / totalHarvestTime
                                            )
                                        else:
                                            return 0  # Covers the scenario when block.timestamp == activation
                                    
                                    
                                    @view
                                    @external
                                    def availableDepositLimit() -> uint256:
                                        if self.depositLimit > self._totalAssets():
                                            return self.depositLimit - self._totalAssets()
                                        else:
                                            return 0
                                    
                                    
                                    @view
                                    @external
                                    def expectedReturn(strategy: address = msg.sender) -> uint256:
                                        """
                                        @notice
                                            Provide an accurate expected value for the return this `strategy`
                                            would provide to the Vault the next time `report()` is called
                                            (since the last time it was called).
                                        @param strategy The Strategy to determine the expected return for. Defaults to caller.
                                        @return
                                            The anticipated amount `strategy` should make on its investment
                                            since its last report.
                                        """
                                        return self._expectedReturn(strategy)
                                    
                                    
                                    @internal
                                    def _assessFees(strategy: address, gain: uint256) -> uint256:
                                        # Issue new shares to cover fees
                                        # NOTE: In effect, this reduces overall share price by the combined fee
                                        # NOTE: may throw if Vault.totalAssets() > 1e64, or not called for more than a year
                                        duration: uint256 = block.timestamp - self.strategies[strategy].lastReport
                                        assert duration != 0 # can't assessFees twice within the same block
                                    
                                        if gain == 0:
                                            # NOTE: The fees are not charged if there hasn't been any gains reported
                                            return 0
                                    
                                        management_fee: uint256 = (
                                            (
                                                (self.strategies[strategy].totalDebt - Strategy(strategy).delegatedAssets())
                                                * duration 
                                                * self.managementFee
                                            )
                                            / MAX_BPS
                                            / SECS_PER_YEAR
                                        )
                                    
                                        # NOTE: Applies if Strategy is not shutting down, or it is but all debt paid off
                                        # NOTE: No fee is taken when a Strategy is unwinding it's position, until all debt is paid
                                        strategist_fee: uint256 = (
                                            gain
                                            * self.strategies[strategy].performanceFee
                                            / MAX_BPS
                                        )
                                        # NOTE: Unlikely to throw unless strategy reports >1e72 harvest profit
                                        performance_fee: uint256 = gain * self.performanceFee / MAX_BPS
                                    
                                        # NOTE: This must be called prior to taking new collateral,
                                        #       or the calculation will be wrong!
                                        # NOTE: This must be done at the same time, to ensure the relative
                                        #       ratio of governance_fee : strategist_fee is kept intact
                                        total_fee: uint256 = performance_fee + strategist_fee + management_fee
                                        # ensure total_fee is not more than gain
                                        if total_fee > gain:
                                            total_fee = gain
                                        if total_fee > 0:  # NOTE: If mgmt fee is 0% and no gains were realized, skip
                                            reward: uint256 = self._issueSharesForAmount(self, total_fee)
                                    
                                            # Send the rewards out as new shares in this Vault
                                            if strategist_fee > 0:  # NOTE: Guard against DIV/0 fault
                                                # NOTE: Unlikely to throw unless sqrt(reward) >>> 1e39
                                                strategist_reward: uint256 = (
                                                    strategist_fee
                                                    * reward
                                                    / total_fee
                                                )
                                                self._transfer(self, strategy, strategist_reward)
                                                # NOTE: Strategy distributes rewards at the end of harvest()
                                            # NOTE: Governance earns any dust leftover from flooring math above
                                            if self.balanceOf[self] > 0:
                                                self._transfer(self, self.rewards, self.balanceOf[self])
                                        return total_fee
                                    
                                    
                                    @external
                                    def report(gain: uint256, loss: uint256, _debtPayment: uint256) -> uint256:
                                        """
                                        @notice
                                            Reports the amount of assets the calling Strategy has free (usually in
                                            terms of ROI).
                                    
                                            The performance fee is determined here, off of the strategy's profits
                                            (if any), and sent to governance.
                                    
                                            The strategist's fee is also determined here (off of profits), to be
                                            handled according to the strategist on the next harvest.
                                    
                                            This may only be called by a Strategy managed by this Vault.
                                        @dev
                                            For approved strategies, this is the most efficient behavior.
                                            The Strategy reports back what it has free, then Vault "decides"
                                            whether to take some back or give it more. Note that the most it can
                                            take is `gain + _debtPayment`, and the most it can give is all of the
                                            remaining reserves. Anything outside of those bounds is abnormal behavior.
                                    
                                            All approved strategies must have increased diligence around
                                            calling this function, as abnormal behavior could become catastrophic.
                                        @param gain
                                            Amount Strategy has realized as a gain on it's investment since its
                                            last report, and is free to be given back to Vault as earnings
                                        @param loss
                                            Amount Strategy has realized as a loss on it's investment since its
                                            last report, and should be accounted for on the Vault's balance sheet.
                                            The loss will reduce the debtRatio. The next time the strategy will harvest,
                                            it will pay back the debt in an attempt to adjust to the new debt limit.
                                        @param _debtPayment
                                            Amount Strategy has made available to cover outstanding debt
                                        @return Amount of debt outstanding (if totalDebt > debtLimit or emergency shutdown).
                                        """
                                    
                                        # Only approved strategies can call this function
                                        assert self.strategies[msg.sender].activation > 0
                                        # No lying about total available to withdraw!
                                        assert self.token.balanceOf(msg.sender) >= gain + _debtPayment
                                    
                                        # We have a loss to report, do it before the rest of the calculations
                                        if loss > 0:
                                            self._reportLoss(msg.sender, loss)
                                    
                                        # Assess both management fee and performance fee, and issue both as shares of the vault
                                        totalFees: uint256 = self._assessFees(msg.sender, gain)
                                    
                                        # Returns are always "realized gains"
                                        self.strategies[msg.sender].totalGain += gain
                                    
                                        # Compute the line of credit the Vault is able to offer the Strategy (if any)
                                        credit: uint256 = self._creditAvailable(msg.sender)
                                    
                                        # Outstanding debt the Strategy wants to take back from the Vault (if any)
                                        # NOTE: debtOutstanding <= StrategyParams.totalDebt
                                        debt: uint256 = self._debtOutstanding(msg.sender)
                                        debtPayment: uint256 = min(_debtPayment, debt)
                                    
                                        if debtPayment > 0:
                                            self.strategies[msg.sender].totalDebt -= debtPayment
                                            self.totalDebt -= debtPayment
                                            debt -= debtPayment
                                            # NOTE: `debt` is being tracked for later
                                    
                                        # Update the actual debt based on the full credit we are extending to the Strategy
                                        # or the returns if we are taking funds back
                                        # NOTE: credit + self.strategies[msg.sender].totalDebt is always < self.debtLimit
                                        # NOTE: At least one of `credit` or `debt` is always 0 (both can be 0)
                                        if credit > 0:
                                            self.strategies[msg.sender].totalDebt += credit
                                            self.totalDebt += credit
                                    
                                        # Give/take balance to Strategy, based on the difference between the reported gains
                                        # (if any), the debt payment (if any), the credit increase we are offering (if any),
                                        # and the debt needed to be paid off (if any)
                                        # NOTE: This is just used to adjust the balance of tokens between the Strategy and
                                        #       the Vault based on the Strategy's debt limit (as well as the Vault's).
                                        totalAvail: uint256 = gain + debtPayment
                                        if totalAvail < credit:  # credit surplus, give to Strategy
                                            self.erc20_safe_transfer(self.token.address, msg.sender, credit - totalAvail)
                                        elif totalAvail > credit:  # credit deficit, take from Strategy
                                            self.erc20_safe_transferFrom(self.token.address, msg.sender, self, totalAvail - credit)
                                        # else, don't do anything because it is balanced
                                    
                                        # Profit is locked and gradually released per block
                                        # NOTE: compute current locked profit and replace with sum of current and new
                                        lockedProfitBeforeLoss: uint256 = self._calculateLockedProfit() + gain - totalFees
                                        if lockedProfitBeforeLoss > loss: 
                                            self.lockedProfit = lockedProfitBeforeLoss - loss
                                        else:
                                            self.lockedProfit = 0
                                    
                                        # Update reporting time
                                        self.strategies[msg.sender].lastReport = block.timestamp
                                        self.lastReport = block.timestamp
                                    
                                        log StrategyReported(
                                            msg.sender,
                                            gain,
                                            loss,
                                            debtPayment,
                                            self.strategies[msg.sender].totalGain,
                                            self.strategies[msg.sender].totalLoss,
                                            self.strategies[msg.sender].totalDebt,
                                            credit,
                                            self.strategies[msg.sender].debtRatio,
                                        )
                                    
                                        if self.strategies[msg.sender].debtRatio == 0 or self.emergencyShutdown:
                                            # Take every last penny the Strategy has (Emergency Exit/revokeStrategy)
                                            # NOTE: This is different than `debt` in order to extract *all* of the returns
                                            return Strategy(msg.sender).estimatedTotalAssets()
                                        else:
                                            # Otherwise, just return what we have as debt outstanding
                                            return debt
                                    
                                    
                                    @external
                                    def sweep(token: address, amount: uint256 = MAX_UINT256):
                                        """
                                        @notice
                                            Removes tokens from this Vault that are not the type of token managed
                                            by this Vault. This may be used in case of accidentally sending the
                                            wrong kind of token to this Vault.
                                    
                                            Tokens will be sent to `governance`.
                                    
                                            This will fail if an attempt is made to sweep the tokens that this
                                            Vault manages.
                                    
                                            This may only be called by governance.
                                        @param token The token to transfer out of this vault.
                                        @param amount The quantity or tokenId to transfer out.
                                        """
                                        assert msg.sender == self.governance
                                        # Can't be used to steal what this Vault is protecting
                                        assert token != self.token.address
                                        value: uint256 = amount
                                        if value == MAX_UINT256:
                                            value = ERC20(token).balanceOf(self)
                                        self.erc20_safe_transfer(token, self.governance, value)

                                    File 5 of 6: FiatTokenV2_2
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    import { EIP712Domain } from "./EIP712Domain.sol"; // solhint-disable-line no-unused-import
                                    import { Blacklistable } from "../v1/Blacklistable.sol"; // solhint-disable-line no-unused-import
                                    import { FiatTokenV1 } from "../v1/FiatTokenV1.sol"; // solhint-disable-line no-unused-import
                                    import { FiatTokenV2 } from "./FiatTokenV2.sol"; // solhint-disable-line no-unused-import
                                    import { FiatTokenV2_1 } from "./FiatTokenV2_1.sol";
                                    import { EIP712 } from "../util/EIP712.sol";
                                    // solhint-disable func-name-mixedcase
                                    /**
                                     * @title FiatToken V2.2
                                     * @notice ERC20 Token backed by fiat reserves, version 2.2
                                     */
                                    contract FiatTokenV2_2 is FiatTokenV2_1 {
                                        /**
                                         * @notice Initialize v2.2
                                         * @param accountsToBlacklist   A list of accounts to migrate from the old blacklist
                                         * @param newSymbol             New token symbol
                                         * data structure to the new blacklist data structure.
                                         */
                                        function initializeV2_2(
                                            address[] calldata accountsToBlacklist,
                                            string calldata newSymbol
                                        ) external {
                                            // solhint-disable-next-line reason-string
                                            require(_initializedVersion == 2);
                                            // Update fiat token symbol
                                            symbol = newSymbol;
                                            // Add previously blacklisted accounts to the new blacklist data structure
                                            // and remove them from the old blacklist data structure.
                                            for (uint256 i = 0; i < accountsToBlacklist.length; i++) {
                                                require(
                                                    _deprecatedBlacklisted[accountsToBlacklist[i]],
                                                    "FiatTokenV2_2: Blacklisting previously unblacklisted account!"
                                                );
                                                _blacklist(accountsToBlacklist[i]);
                                                delete _deprecatedBlacklisted[accountsToBlacklist[i]];
                                            }
                                            _blacklist(address(this));
                                            delete _deprecatedBlacklisted[address(this)];
                                            _initializedVersion = 3;
                                        }
                                        /**
                                         * @dev Internal function to get the current chain id.
                                         * @return The current chain id.
                                         */
                                        function _chainId() internal virtual view returns (uint256) {
                                            uint256 chainId;
                                            assembly {
                                                chainId := chainid()
                                            }
                                            return chainId;
                                        }
                                        /**
                                         * @inheritdoc EIP712Domain
                                         */
                                        function _domainSeparator() internal override view returns (bytes32) {
                                            return EIP712.makeDomainSeparator(name, "2", _chainId());
                                        }
                                        /**
                                         * @notice Update allowance with a signed permit
                                         * @dev EOA wallet signatures should be packed in the order of r, s, v.
                                         * @param owner       Token owner's address (Authorizer)
                                         * @param spender     Spender's address
                                         * @param value       Amount of allowance
                                         * @param deadline    The time at which the signature expires (unix time), or max uint256 value to signal no expiration
                                         * @param signature   Signature bytes signed by an EOA wallet or a contract wallet
                                         */
                                        function permit(
                                            address owner,
                                            address spender,
                                            uint256 value,
                                            uint256 deadline,
                                            bytes memory signature
                                        ) external whenNotPaused {
                                            _permit(owner, spender, value, deadline, signature);
                                        }
                                        /**
                                         * @notice Execute a transfer with a signed authorization
                                         * @dev EOA wallet signatures should be packed in the order of r, s, v.
                                         * @param from          Payer's address (Authorizer)
                                         * @param to            Payee's address
                                         * @param value         Amount to be transferred
                                         * @param validAfter    The time after which this is valid (unix time)
                                         * @param validBefore   The time before which this is valid (unix time)
                                         * @param nonce         Unique nonce
                                         * @param signature     Signature bytes signed by an EOA wallet or a contract wallet
                                         */
                                        function transferWithAuthorization(
                                            address from,
                                            address to,
                                            uint256 value,
                                            uint256 validAfter,
                                            uint256 validBefore,
                                            bytes32 nonce,
                                            bytes memory signature
                                        ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
                                            _transferWithAuthorization(
                                                from,
                                                to,
                                                value,
                                                validAfter,
                                                validBefore,
                                                nonce,
                                                signature
                                            );
                                        }
                                        /**
                                         * @notice Receive a transfer with a signed authorization from the payer
                                         * @dev This has an additional check to ensure that the payee's address
                                         * matches the caller of this function to prevent front-running attacks.
                                         * EOA wallet signatures should be packed in the order of r, s, v.
                                         * @param from          Payer's address (Authorizer)
                                         * @param to            Payee's address
                                         * @param value         Amount to be transferred
                                         * @param validAfter    The time after which this is valid (unix time)
                                         * @param validBefore   The time before which this is valid (unix time)
                                         * @param nonce         Unique nonce
                                         * @param signature     Signature bytes signed by an EOA wallet or a contract wallet
                                         */
                                        function receiveWithAuthorization(
                                            address from,
                                            address to,
                                            uint256 value,
                                            uint256 validAfter,
                                            uint256 validBefore,
                                            bytes32 nonce,
                                            bytes memory signature
                                        ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
                                            _receiveWithAuthorization(
                                                from,
                                                to,
                                                value,
                                                validAfter,
                                                validBefore,
                                                nonce,
                                                signature
                                            );
                                        }
                                        /**
                                         * @notice Attempt to cancel an authorization
                                         * @dev Works only if the authorization is not yet used.
                                         * EOA wallet signatures should be packed in the order of r, s, v.
                                         * @param authorizer    Authorizer's address
                                         * @param nonce         Nonce of the authorization
                                         * @param signature     Signature bytes signed by an EOA wallet or a contract wallet
                                         */
                                        function cancelAuthorization(
                                            address authorizer,
                                            bytes32 nonce,
                                            bytes memory signature
                                        ) external whenNotPaused {
                                            _cancelAuthorization(authorizer, nonce, signature);
                                        }
                                        /**
                                         * @dev Helper method that sets the blacklist state of an account on balanceAndBlacklistStates.
                                         * If _shouldBlacklist is true, we apply a (1 << 255) bitmask with an OR operation on the
                                         * account's balanceAndBlacklistState. This flips the high bit for the account to 1,
                                         * indicating that the account is blacklisted.
                                         *
                                         * If _shouldBlacklist if false, we reset the account's balanceAndBlacklistStates to their
                                         * balances. This clears the high bit for the account, indicating that the account is unblacklisted.
                                         * @param _account         The address of the account.
                                         * @param _shouldBlacklist True if the account should be blacklisted, false if the account should be unblacklisted.
                                         */
                                        function _setBlacklistState(address _account, bool _shouldBlacklist)
                                            internal
                                            override
                                        {
                                            balanceAndBlacklistStates[_account] = _shouldBlacklist
                                                ? balanceAndBlacklistStates[_account] | (1 << 255)
                                                : _balanceOf(_account);
                                        }
                                        /**
                                         * @dev Helper method that sets the balance of an account on balanceAndBlacklistStates.
                                         * Since balances are stored in the last 255 bits of the balanceAndBlacklistStates value,
                                         * we need to ensure that the updated balance does not exceed (2^255 - 1).
                                         * Since blacklisted accounts' balances cannot be updated, the method will also
                                         * revert if the account is blacklisted
                                         * @param _account The address of the account.
                                         * @param _balance The new fiat token balance of the account (max: (2^255 - 1)).
                                         */
                                        function _setBalance(address _account, uint256 _balance) internal override {
                                            require(
                                                _balance <= ((1 << 255) - 1),
                                                "FiatTokenV2_2: Balance exceeds (2^255 - 1)"
                                            );
                                            require(
                                                !_isBlacklisted(_account),
                                                "FiatTokenV2_2: Account is blacklisted"
                                            );
                                            balanceAndBlacklistStates[_account] = _balance;
                                        }
                                        /**
                                         * @inheritdoc Blacklistable
                                         */
                                        function _isBlacklisted(address _account)
                                            internal
                                            override
                                            view
                                            returns (bool)
                                        {
                                            return balanceAndBlacklistStates[_account] >> 255 == 1;
                                        }
                                        /**
                                         * @dev Helper method to obtain the balance of an account. Since balances
                                         * are stored in the last 255 bits of the balanceAndBlacklistStates value,
                                         * we apply a ((1 << 255) - 1) bit bitmask with an AND operation on the
                                         * balanceAndBlacklistState to obtain the balance.
                                         * @param _account  The address of the account.
                                         * @return          The fiat token balance of the account.
                                         */
                                        function _balanceOf(address _account)
                                            internal
                                            override
                                            view
                                            returns (uint256)
                                        {
                                            return balanceAndBlacklistStates[_account] & ((1 << 255) - 1);
                                        }
                                        /**
                                         * @inheritdoc FiatTokenV1
                                         */
                                        function approve(address spender, uint256 value)
                                            external
                                            override
                                            whenNotPaused
                                            returns (bool)
                                        {
                                            _approve(msg.sender, spender, value);
                                            return true;
                                        }
                                        /**
                                         * @inheritdoc FiatTokenV2
                                         */
                                        function permit(
                                            address owner,
                                            address spender,
                                            uint256 value,
                                            uint256 deadline,
                                            uint8 v,
                                            bytes32 r,
                                            bytes32 s
                                        ) external override whenNotPaused {
                                            _permit(owner, spender, value, deadline, v, r, s);
                                        }
                                        /**
                                         * @inheritdoc FiatTokenV2
                                         */
                                        function increaseAllowance(address spender, uint256 increment)
                                            external
                                            override
                                            whenNotPaused
                                            returns (bool)
                                        {
                                            _increaseAllowance(msg.sender, spender, increment);
                                            return true;
                                        }
                                        /**
                                         * @inheritdoc FiatTokenV2
                                         */
                                        function decreaseAllowance(address spender, uint256 decrement)
                                            external
                                            override
                                            whenNotPaused
                                            returns (bool)
                                        {
                                            _decreaseAllowance(msg.sender, spender, decrement);
                                            return true;
                                        }
                                    }
                                    // SPDX-License-Identifier: MIT
                                    pragma solidity >=0.6.2 <0.8.0;
                                    /**
                                     * @dev Collection of functions related to the address type
                                     */
                                    library Address {
                                        /**
                                         * @dev Returns true if `account` is a contract.
                                         *
                                         * [IMPORTANT]
                                         * ====
                                         * It is unsafe to assume that an address for which this function returns
                                         * false is an externally-owned account (EOA) and not a contract.
                                         *
                                         * Among others, `isContract` will return false for the following
                                         * types of addresses:
                                         *
                                         *  - an externally-owned account
                                         *  - a contract in construction
                                         *  - an address where a contract will be created
                                         *  - an address where a contract lived, but was destroyed
                                         * ====
                                         */
                                        function isContract(address account) internal view returns (bool) {
                                            // This method relies on extcodesize, which returns 0 for contracts in
                                            // construction, since the code is only stored at the end of the
                                            // constructor execution.
                                            uint256 size;
                                            // solhint-disable-next-line no-inline-assembly
                                            assembly { size := extcodesize(account) }
                                            return size > 0;
                                        }
                                        /**
                                         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                                         * `recipient`, forwarding all available gas and reverting on errors.
                                         *
                                         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                                         * of certain opcodes, possibly making contracts go over the 2300 gas limit
                                         * imposed by `transfer`, making them unable to receive funds via
                                         * `transfer`. {sendValue} removes this limitation.
                                         *
                                         * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                                         *
                                         * IMPORTANT: because control is transferred to `recipient`, care must be
                                         * taken to not create reentrancy vulnerabilities. Consider using
                                         * {ReentrancyGuard} or the
                                         * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                                         */
                                        function sendValue(address payable recipient, uint256 amount) internal {
                                            require(address(this).balance >= amount, "Address: insufficient balance");
                                            // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                                            (bool success, ) = recipient.call{ value: amount }("");
                                            require(success, "Address: unable to send value, recipient may have reverted");
                                        }
                                        /**
                                         * @dev Performs a Solidity function call using a low level `call`. A
                                         * plain`call` is an unsafe replacement for a function call: use this
                                         * function instead.
                                         *
                                         * If `target` reverts with a revert reason, it is bubbled up by this
                                         * function (like regular Solidity function calls).
                                         *
                                         * Returns the raw returned data. To convert to the expected return value,
                                         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                                         *
                                         * Requirements:
                                         *
                                         * - `target` must be a contract.
                                         * - calling `target` with `data` must not revert.
                                         *
                                         * _Available since v3.1._
                                         */
                                        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                                          return functionCall(target, data, "Address: low-level call failed");
                                        }
                                        /**
                                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                                         * `errorMessage` as a fallback revert reason when `target` reverts.
                                         *
                                         * _Available since v3.1._
                                         */
                                        function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                                            return functionCallWithValue(target, data, 0, errorMessage);
                                        }
                                        /**
                                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                         * but also transferring `value` wei to `target`.
                                         *
                                         * Requirements:
                                         *
                                         * - the calling contract must have an ETH balance of at least `value`.
                                         * - the called Solidity function must be `payable`.
                                         *
                                         * _Available since v3.1._
                                         */
                                        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                                            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                                        }
                                        /**
                                         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                                         * with `errorMessage` as a fallback revert reason when `target` reverts.
                                         *
                                         * _Available since v3.1._
                                         */
                                        function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
                                            require(address(this).balance >= value, "Address: insufficient balance for call");
                                            require(isContract(target), "Address: call to non-contract");
                                            // solhint-disable-next-line avoid-low-level-calls
                                            (bool success, bytes memory returndata) = target.call{ value: value }(data);
                                            return _verifyCallResult(success, returndata, errorMessage);
                                        }
                                        /**
                                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                         * but performing a static call.
                                         *
                                         * _Available since v3.3._
                                         */
                                        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                                            return functionStaticCall(target, data, "Address: low-level static call failed");
                                        }
                                        /**
                                         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                                         * but performing a static call.
                                         *
                                         * _Available since v3.3._
                                         */
                                        function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
                                            require(isContract(target), "Address: static call to non-contract");
                                            // solhint-disable-next-line avoid-low-level-calls
                                            (bool success, bytes memory returndata) = target.staticcall(data);
                                            return _verifyCallResult(success, returndata, errorMessage);
                                        }
                                        /**
                                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                         * but performing a delegate call.
                                         *
                                         * _Available since v3.4._
                                         */
                                        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                                            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                                        }
                                        /**
                                         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                                         * but performing a delegate call.
                                         *
                                         * _Available since v3.4._
                                         */
                                        function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                                            require(isContract(target), "Address: delegate call to non-contract");
                                            // solhint-disable-next-line avoid-low-level-calls
                                            (bool success, bytes memory returndata) = target.delegatecall(data);
                                            return _verifyCallResult(success, returndata, errorMessage);
                                        }
                                        function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
                                            if (success) {
                                                return returndata;
                                            } else {
                                                // Look for revert reason and bubble it up if present
                                                if (returndata.length > 0) {
                                                    // The easiest way to bubble the revert reason is using memory via assembly
                                                    // solhint-disable-next-line no-inline-assembly
                                                    assembly {
                                                        let returndata_size := mload(returndata)
                                                        revert(add(32, returndata), returndata_size)
                                                    }
                                                } else {
                                                    revert(errorMessage);
                                                }
                                            }
                                        }
                                    }
                                    // SPDX-License-Identifier: MIT
                                    pragma solidity >=0.6.0 <0.8.0;
                                    import "./IERC20.sol";
                                    import "../../math/SafeMath.sol";
                                    import "../../utils/Address.sol";
                                    /**
                                     * @title SafeERC20
                                     * @dev Wrappers around ERC20 operations that throw on failure (when the token
                                     * contract returns false). Tokens that return no value (and instead revert or
                                     * throw on failure) are also supported, non-reverting calls are assumed to be
                                     * successful.
                                     * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
                                     * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
                                     */
                                    library SafeERC20 {
                                        using SafeMath for uint256;
                                        using Address for address;
                                        function safeTransfer(IERC20 token, address to, uint256 value) internal {
                                            _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                                        }
                                        function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                                            _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                                        }
                                        /**
                                         * @dev Deprecated. This function has issues similar to the ones found in
                                         * {IERC20-approve}, and its usage is discouraged.
                                         *
                                         * Whenever possible, use {safeIncreaseAllowance} and
                                         * {safeDecreaseAllowance} instead.
                                         */
                                        function safeApprove(IERC20 token, address spender, uint256 value) internal {
                                            // safeApprove should only be called when setting an initial allowance,
                                            // or when resetting it to zero. To increase and decrease it, use
                                            // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                                            // solhint-disable-next-line max-line-length
                                            require((value == 0) || (token.allowance(address(this), spender) == 0),
                                                "SafeERC20: approve from non-zero to non-zero allowance"
                                            );
                                            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                                        }
                                        function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                                            uint256 newAllowance = token.allowance(address(this), spender).add(value);
                                            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                                        }
                                        function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                                            uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
                                            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                                        }
                                        /**
                                         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                                         * on the return value: the return value is optional (but if data is returned, it must not be false).
                                         * @param token The token targeted by the call.
                                         * @param data The call data (encoded using abi.encode or one of its variants).
                                         */
                                        function _callOptionalReturn(IERC20 token, bytes memory data) private {
                                            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                                            // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                                            // the target address contains contract code and also asserts for success in the low-level call.
                                            bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                                            if (returndata.length > 0) { // Return data is optional
                                                // solhint-disable-next-line max-line-length
                                                require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                                            }
                                        }
                                    }
                                    // SPDX-License-Identifier: MIT
                                    pragma solidity >=0.6.0 <0.8.0;
                                    /**
                                     * @dev Interface of the ERC20 standard as defined in the EIP.
                                     */
                                    interface IERC20 {
                                        /**
                                         * @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 `recipient`.
                                         *
                                         * Returns a boolean value indicating whether the operation succeeded.
                                         *
                                         * Emits a {Transfer} event.
                                         */
                                        function transfer(address recipient, 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 `sender` to `recipient` 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 sender, address recipient, uint256 amount) external returns (bool);
                                        /**
                                         * @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);
                                    }
                                    // SPDX-License-Identifier: MIT
                                    pragma solidity >=0.6.0 <0.8.0;
                                    /**
                                     * @dev Wrappers over Solidity's arithmetic operations with added overflow
                                     * checks.
                                     *
                                     * Arithmetic operations in Solidity wrap on overflow. This can easily result
                                     * in bugs, because programmers usually assume that an overflow raises an
                                     * error, which is the standard behavior in high level programming languages.
                                     * `SafeMath` restores this intuition by reverting the transaction when an
                                     * operation overflows.
                                     *
                                     * Using this library instead of the unchecked operations eliminates an entire
                                     * class of bugs, so it's recommended to use it always.
                                     */
                                    library SafeMath {
                                        /**
                                         * @dev Returns the addition of two unsigned integers, with an overflow flag.
                                         *
                                         * _Available since v3.4._
                                         */
                                        function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                                            uint256 c = a + b;
                                            if (c < a) return (false, 0);
                                            return (true, c);
                                        }
                                        /**
                                         * @dev Returns the substraction of two unsigned integers, with an overflow flag.
                                         *
                                         * _Available since v3.4._
                                         */
                                        function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                                            if (b > a) return (false, 0);
                                            return (true, a - b);
                                        }
                                        /**
                                         * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
                                         *
                                         * _Available since v3.4._
                                         */
                                        function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                                            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                                            // benefit is lost if 'b' is also tested.
                                            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                                            if (a == 0) return (true, 0);
                                            uint256 c = a * b;
                                            if (c / a != b) return (false, 0);
                                            return (true, c);
                                        }
                                        /**
                                         * @dev Returns the division of two unsigned integers, with a division by zero flag.
                                         *
                                         * _Available since v3.4._
                                         */
                                        function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                                            if (b == 0) return (false, 0);
                                            return (true, a / b);
                                        }
                                        /**
                                         * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
                                         *
                                         * _Available since v3.4._
                                         */
                                        function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                                            if (b == 0) return (false, 0);
                                            return (true, a % b);
                                        }
                                        /**
                                         * @dev Returns the addition of two unsigned integers, reverting on
                                         * overflow.
                                         *
                                         * Counterpart to Solidity's `+` operator.
                                         *
                                         * Requirements:
                                         *
                                         * - Addition cannot overflow.
                                         */
                                        function add(uint256 a, uint256 b) internal pure returns (uint256) {
                                            uint256 c = a + b;
                                            require(c >= a, "SafeMath: addition overflow");
                                            return c;
                                        }
                                        /**
                                         * @dev Returns the subtraction of two unsigned integers, reverting on
                                         * overflow (when the result is negative).
                                         *
                                         * Counterpart to Solidity's `-` operator.
                                         *
                                         * Requirements:
                                         *
                                         * - Subtraction cannot overflow.
                                         */
                                        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                                            require(b <= a, "SafeMath: subtraction overflow");
                                            return a - b;
                                        }
                                        /**
                                         * @dev Returns the multiplication of two unsigned integers, reverting on
                                         * overflow.
                                         *
                                         * Counterpart to Solidity's `*` operator.
                                         *
                                         * Requirements:
                                         *
                                         * - Multiplication cannot overflow.
                                         */
                                        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                                            if (a == 0) return 0;
                                            uint256 c = a * b;
                                            require(c / a == b, "SafeMath: multiplication overflow");
                                            return c;
                                        }
                                        /**
                                         * @dev Returns the integer division of two unsigned integers, reverting on
                                         * division by zero. The result is rounded towards zero.
                                         *
                                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                                         * uses an invalid opcode to revert (consuming all remaining gas).
                                         *
                                         * Requirements:
                                         *
                                         * - The divisor cannot be zero.
                                         */
                                        function div(uint256 a, uint256 b) internal pure returns (uint256) {
                                            require(b > 0, "SafeMath: division by zero");
                                            return a / b;
                                        }
                                        /**
                                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                                         * reverting when dividing by zero.
                                         *
                                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                                         * invalid opcode to revert (consuming all remaining gas).
                                         *
                                         * Requirements:
                                         *
                                         * - The divisor cannot be zero.
                                         */
                                        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                                            require(b > 0, "SafeMath: modulo by zero");
                                            return a % b;
                                        }
                                        /**
                                         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                                         * overflow (when the result is negative).
                                         *
                                         * CAUTION: This function is deprecated because it requires allocating memory for the error
                                         * message unnecessarily. For custom revert reasons use {trySub}.
                                         *
                                         * Counterpart to Solidity's `-` operator.
                                         *
                                         * Requirements:
                                         *
                                         * - Subtraction cannot overflow.
                                         */
                                        function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                            require(b <= a, errorMessage);
                                            return a - b;
                                        }
                                        /**
                                         * @dev Returns the integer division of two unsigned integers, reverting with custom message on
                                         * division by zero. The result is rounded towards zero.
                                         *
                                         * CAUTION: This function is deprecated because it requires allocating memory for the error
                                         * message unnecessarily. For custom revert reasons use {tryDiv}.
                                         *
                                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                                         * uses an invalid opcode to revert (consuming all remaining gas).
                                         *
                                         * Requirements:
                                         *
                                         * - The divisor cannot be zero.
                                         */
                                        function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                            require(b > 0, errorMessage);
                                            return a / b;
                                        }
                                        /**
                                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                                         * reverting with custom message when dividing by zero.
                                         *
                                         * CAUTION: This function is deprecated because it requires allocating memory for the error
                                         * message unnecessarily. For custom revert reasons use {tryMod}.
                                         *
                                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                                         * invalid opcode to revert (consuming all remaining gas).
                                         *
                                         * Requirements:
                                         *
                                         * - The divisor cannot be zero.
                                         */
                                        function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                            require(b > 0, errorMessage);
                                            return a % b;
                                        }
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    import { FiatTokenV2 } from "./FiatTokenV2.sol";
                                    // solhint-disable func-name-mixedcase
                                    /**
                                     * @title FiatToken V2.1
                                     * @notice ERC20 Token backed by fiat reserves, version 2.1
                                     */
                                    contract FiatTokenV2_1 is FiatTokenV2 {
                                        /**
                                         * @notice Initialize v2.1
                                         * @param lostAndFound  The address to which the locked funds are sent
                                         */
                                        function initializeV2_1(address lostAndFound) external {
                                            // solhint-disable-next-line reason-string
                                            require(_initializedVersion == 1);
                                            uint256 lockedAmount = _balanceOf(address(this));
                                            if (lockedAmount > 0) {
                                                _transfer(address(this), lostAndFound, lockedAmount);
                                            }
                                            _blacklist(address(this));
                                            _initializedVersion = 2;
                                        }
                                        /**
                                         * @notice Version string for the EIP712 domain separator
                                         * @return Version string
                                         */
                                        function version() external pure returns (string memory) {
                                            return "2";
                                        }
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    import { FiatTokenV1_1 } from "../v1.1/FiatTokenV1_1.sol";
                                    import { EIP712 } from "../util/EIP712.sol";
                                    import { EIP3009 } from "./EIP3009.sol";
                                    import { EIP2612 } from "./EIP2612.sol";
                                    /**
                                     * @title FiatToken V2
                                     * @notice ERC20 Token backed by fiat reserves, version 2
                                     */
                                    contract FiatTokenV2 is FiatTokenV1_1, EIP3009, EIP2612 {
                                        uint8 internal _initializedVersion;
                                        /**
                                         * @notice Initialize v2
                                         * @param newName   New token name
                                         */
                                        function initializeV2(string calldata newName) external {
                                            // solhint-disable-next-line reason-string
                                            require(initialized && _initializedVersion == 0);
                                            name = newName;
                                            _DEPRECATED_CACHED_DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(
                                                newName,
                                                "2"
                                            );
                                            _initializedVersion = 1;
                                        }
                                        /**
                                         * @notice Increase the allowance by a given increment
                                         * @param spender   Spender's address
                                         * @param increment Amount of increase in allowance
                                         * @return True if successful
                                         */
                                        function increaseAllowance(address spender, uint256 increment)
                                            external
                                            virtual
                                            whenNotPaused
                                            notBlacklisted(msg.sender)
                                            notBlacklisted(spender)
                                            returns (bool)
                                        {
                                            _increaseAllowance(msg.sender, spender, increment);
                                            return true;
                                        }
                                        /**
                                         * @notice Decrease the allowance by a given decrement
                                         * @param spender   Spender's address
                                         * @param decrement Amount of decrease in allowance
                                         * @return True if successful
                                         */
                                        function decreaseAllowance(address spender, uint256 decrement)
                                            external
                                            virtual
                                            whenNotPaused
                                            notBlacklisted(msg.sender)
                                            notBlacklisted(spender)
                                            returns (bool)
                                        {
                                            _decreaseAllowance(msg.sender, spender, decrement);
                                            return true;
                                        }
                                        /**
                                         * @notice Execute a transfer with a signed authorization
                                         * @param from          Payer's address (Authorizer)
                                         * @param to            Payee's address
                                         * @param value         Amount to be transferred
                                         * @param validAfter    The time after which this is valid (unix time)
                                         * @param validBefore   The time before which this is valid (unix time)
                                         * @param nonce         Unique nonce
                                         * @param v             v of the signature
                                         * @param r             r of the signature
                                         * @param s             s of the signature
                                         */
                                        function transferWithAuthorization(
                                            address from,
                                            address to,
                                            uint256 value,
                                            uint256 validAfter,
                                            uint256 validBefore,
                                            bytes32 nonce,
                                            uint8 v,
                                            bytes32 r,
                                            bytes32 s
                                        ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
                                            _transferWithAuthorization(
                                                from,
                                                to,
                                                value,
                                                validAfter,
                                                validBefore,
                                                nonce,
                                                v,
                                                r,
                                                s
                                            );
                                        }
                                        /**
                                         * @notice Receive a transfer with a signed authorization from the payer
                                         * @dev This has an additional check to ensure that the payee's address
                                         * matches the caller of this function to prevent front-running attacks.
                                         * @param from          Payer's address (Authorizer)
                                         * @param to            Payee's address
                                         * @param value         Amount to be transferred
                                         * @param validAfter    The time after which this is valid (unix time)
                                         * @param validBefore   The time before which this is valid (unix time)
                                         * @param nonce         Unique nonce
                                         * @param v             v of the signature
                                         * @param r             r of the signature
                                         * @param s             s of the signature
                                         */
                                        function receiveWithAuthorization(
                                            address from,
                                            address to,
                                            uint256 value,
                                            uint256 validAfter,
                                            uint256 validBefore,
                                            bytes32 nonce,
                                            uint8 v,
                                            bytes32 r,
                                            bytes32 s
                                        ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
                                            _receiveWithAuthorization(
                                                from,
                                                to,
                                                value,
                                                validAfter,
                                                validBefore,
                                                nonce,
                                                v,
                                                r,
                                                s
                                            );
                                        }
                                        /**
                                         * @notice Attempt to cancel an authorization
                                         * @dev Works only if the authorization is not yet used.
                                         * @param authorizer    Authorizer's address
                                         * @param nonce         Nonce of the authorization
                                         * @param v             v of the signature
                                         * @param r             r of the signature
                                         * @param s             s of the signature
                                         */
                                        function cancelAuthorization(
                                            address authorizer,
                                            bytes32 nonce,
                                            uint8 v,
                                            bytes32 r,
                                            bytes32 s
                                        ) external whenNotPaused {
                                            _cancelAuthorization(authorizer, nonce, v, r, s);
                                        }
                                        /**
                                         * @notice Update allowance with a signed permit
                                         * @param owner       Token owner's address (Authorizer)
                                         * @param spender     Spender's address
                                         * @param value       Amount of allowance
                                         * @param deadline    The time at which the signature expires (unix time), or max uint256 value to signal no expiration
                                         * @param v           v of the signature
                                         * @param r           r of the signature
                                         * @param s           s of the signature
                                         */
                                        function permit(
                                            address owner,
                                            address spender,
                                            uint256 value,
                                            uint256 deadline,
                                            uint8 v,
                                            bytes32 r,
                                            bytes32 s
                                        )
                                            external
                                            virtual
                                            whenNotPaused
                                            notBlacklisted(owner)
                                            notBlacklisted(spender)
                                        {
                                            _permit(owner, spender, value, deadline, v, r, s);
                                        }
                                        /**
                                         * @dev Internal function to increase the allowance by a given increment
                                         * @param owner     Token owner's address
                                         * @param spender   Spender's address
                                         * @param increment Amount of increase
                                         */
                                        function _increaseAllowance(
                                            address owner,
                                            address spender,
                                            uint256 increment
                                        ) internal override {
                                            _approve(owner, spender, allowed[owner][spender].add(increment));
                                        }
                                        /**
                                         * @dev Internal function to decrease the allowance by a given decrement
                                         * @param owner     Token owner's address
                                         * @param spender   Spender's address
                                         * @param decrement Amount of decrease
                                         */
                                        function _decreaseAllowance(
                                            address owner,
                                            address spender,
                                            uint256 decrement
                                        ) internal override {
                                            _approve(
                                                owner,
                                                spender,
                                                allowed[owner][spender].sub(
                                                    decrement,
                                                    "ERC20: decreased allowance below zero"
                                                )
                                            );
                                        }
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    // solhint-disable func-name-mixedcase
                                    /**
                                     * @title EIP712 Domain
                                     */
                                    contract EIP712Domain {
                                        // was originally DOMAIN_SEPARATOR
                                        // but that has been moved to a method so we can override it in V2_2+
                                        bytes32 internal _DEPRECATED_CACHED_DOMAIN_SEPARATOR;
                                        /**
                                         * @notice Get the EIP712 Domain Separator.
                                         * @return The bytes32 EIP712 domain separator.
                                         */
                                        function DOMAIN_SEPARATOR() external view returns (bytes32) {
                                            return _domainSeparator();
                                        }
                                        /**
                                         * @dev Internal method to get the EIP712 Domain Separator.
                                         * @return The bytes32 EIP712 domain separator.
                                         */
                                        function _domainSeparator() internal virtual view returns (bytes32) {
                                            return _DEPRECATED_CACHED_DOMAIN_SEPARATOR;
                                        }
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    import { AbstractFiatTokenV2 } from "./AbstractFiatTokenV2.sol";
                                    import { EIP712Domain } from "./EIP712Domain.sol";
                                    import { SignatureChecker } from "../util/SignatureChecker.sol";
                                    import { MessageHashUtils } from "../util/MessageHashUtils.sol";
                                    /**
                                     * @title EIP-3009
                                     * @notice Provide internal implementation for gas-abstracted transfers
                                     * @dev Contracts that inherit from this must wrap these with publicly
                                     * accessible functions, optionally adding modifiers where necessary
                                     */
                                    abstract contract EIP3009 is AbstractFiatTokenV2, EIP712Domain {
                                        // keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
                                        bytes32
                                            public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267;
                                        // keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
                                        bytes32
                                            public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8;
                                        // keccak256("CancelAuthorization(address authorizer,bytes32 nonce)")
                                        bytes32
                                            public constant CANCEL_AUTHORIZATION_TYPEHASH = 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429;
                                        /**
                                         * @dev authorizer address => nonce => bool (true if nonce is used)
                                         */
                                        mapping(address => mapping(bytes32 => bool)) private _authorizationStates;
                                        event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
                                        event AuthorizationCanceled(
                                            address indexed authorizer,
                                            bytes32 indexed nonce
                                        );
                                        /**
                                         * @notice Returns the state of an authorization
                                         * @dev Nonces are randomly generated 32-byte data unique to the
                                         * authorizer's address
                                         * @param authorizer    Authorizer's address
                                         * @param nonce         Nonce of the authorization
                                         * @return True if the nonce is used
                                         */
                                        function authorizationState(address authorizer, bytes32 nonce)
                                            external
                                            view
                                            returns (bool)
                                        {
                                            return _authorizationStates[authorizer][nonce];
                                        }
                                        /**
                                         * @notice Execute a transfer with a signed authorization
                                         * @param from          Payer's address (Authorizer)
                                         * @param to            Payee's address
                                         * @param value         Amount to be transferred
                                         * @param validAfter    The time after which this is valid (unix time)
                                         * @param validBefore   The time before which this is valid (unix time)
                                         * @param nonce         Unique nonce
                                         * @param v             v of the signature
                                         * @param r             r of the signature
                                         * @param s             s of the signature
                                         */
                                        function _transferWithAuthorization(
                                            address from,
                                            address to,
                                            uint256 value,
                                            uint256 validAfter,
                                            uint256 validBefore,
                                            bytes32 nonce,
                                            uint8 v,
                                            bytes32 r,
                                            bytes32 s
                                        ) internal {
                                            _transferWithAuthorization(
                                                from,
                                                to,
                                                value,
                                                validAfter,
                                                validBefore,
                                                nonce,
                                                abi.encodePacked(r, s, v)
                                            );
                                        }
                                        /**
                                         * @notice Execute a transfer with a signed authorization
                                         * @dev EOA wallet signatures should be packed in the order of r, s, v.
                                         * @param from          Payer's address (Authorizer)
                                         * @param to            Payee's address
                                         * @param value         Amount to be transferred
                                         * @param validAfter    The time after which this is valid (unix time)
                                         * @param validBefore   The time before which this is valid (unix time)
                                         * @param nonce         Unique nonce
                                         * @param signature     Signature byte array produced by an EOA wallet or a contract wallet
                                         */
                                        function _transferWithAuthorization(
                                            address from,
                                            address to,
                                            uint256 value,
                                            uint256 validAfter,
                                            uint256 validBefore,
                                            bytes32 nonce,
                                            bytes memory signature
                                        ) internal {
                                            _requireValidAuthorization(from, nonce, validAfter, validBefore);
                                            _requireValidSignature(
                                                from,
                                                keccak256(
                                                    abi.encode(
                                                        TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
                                                        from,
                                                        to,
                                                        value,
                                                        validAfter,
                                                        validBefore,
                                                        nonce
                                                    )
                                                ),
                                                signature
                                            );
                                            _markAuthorizationAsUsed(from, nonce);
                                            _transfer(from, to, value);
                                        }
                                        /**
                                         * @notice Receive a transfer with a signed authorization from the payer
                                         * @dev This has an additional check to ensure that the payee's address
                                         * matches the caller of this function to prevent front-running attacks.
                                         * @param from          Payer's address (Authorizer)
                                         * @param to            Payee's address
                                         * @param value         Amount to be transferred
                                         * @param validAfter    The time after which this is valid (unix time)
                                         * @param validBefore   The time before which this is valid (unix time)
                                         * @param nonce         Unique nonce
                                         * @param v             v of the signature
                                         * @param r             r of the signature
                                         * @param s             s of the signature
                                         */
                                        function _receiveWithAuthorization(
                                            address from,
                                            address to,
                                            uint256 value,
                                            uint256 validAfter,
                                            uint256 validBefore,
                                            bytes32 nonce,
                                            uint8 v,
                                            bytes32 r,
                                            bytes32 s
                                        ) internal {
                                            _receiveWithAuthorization(
                                                from,
                                                to,
                                                value,
                                                validAfter,
                                                validBefore,
                                                nonce,
                                                abi.encodePacked(r, s, v)
                                            );
                                        }
                                        /**
                                         * @notice Receive a transfer with a signed authorization from the payer
                                         * @dev This has an additional check to ensure that the payee's address
                                         * matches the caller of this function to prevent front-running attacks.
                                         * EOA wallet signatures should be packed in the order of r, s, v.
                                         * @param from          Payer's address (Authorizer)
                                         * @param to            Payee's address
                                         * @param value         Amount to be transferred
                                         * @param validAfter    The time after which this is valid (unix time)
                                         * @param validBefore   The time before which this is valid (unix time)
                                         * @param nonce         Unique nonce
                                         * @param signature     Signature byte array produced by an EOA wallet or a contract wallet
                                         */
                                        function _receiveWithAuthorization(
                                            address from,
                                            address to,
                                            uint256 value,
                                            uint256 validAfter,
                                            uint256 validBefore,
                                            bytes32 nonce,
                                            bytes memory signature
                                        ) internal {
                                            require(to == msg.sender, "FiatTokenV2: caller must be the payee");
                                            _requireValidAuthorization(from, nonce, validAfter, validBefore);
                                            _requireValidSignature(
                                                from,
                                                keccak256(
                                                    abi.encode(
                                                        RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
                                                        from,
                                                        to,
                                                        value,
                                                        validAfter,
                                                        validBefore,
                                                        nonce
                                                    )
                                                ),
                                                signature
                                            );
                                            _markAuthorizationAsUsed(from, nonce);
                                            _transfer(from, to, value);
                                        }
                                        /**
                                         * @notice Attempt to cancel an authorization
                                         * @param authorizer    Authorizer's address
                                         * @param nonce         Nonce of the authorization
                                         * @param v             v of the signature
                                         * @param r             r of the signature
                                         * @param s             s of the signature
                                         */
                                        function _cancelAuthorization(
                                            address authorizer,
                                            bytes32 nonce,
                                            uint8 v,
                                            bytes32 r,
                                            bytes32 s
                                        ) internal {
                                            _cancelAuthorization(authorizer, nonce, abi.encodePacked(r, s, v));
                                        }
                                        /**
                                         * @notice Attempt to cancel an authorization
                                         * @dev EOA wallet signatures should be packed in the order of r, s, v.
                                         * @param authorizer    Authorizer's address
                                         * @param nonce         Nonce of the authorization
                                         * @param signature     Signature byte array produced by an EOA wallet or a contract wallet
                                         */
                                        function _cancelAuthorization(
                                            address authorizer,
                                            bytes32 nonce,
                                            bytes memory signature
                                        ) internal {
                                            _requireUnusedAuthorization(authorizer, nonce);
                                            _requireValidSignature(
                                                authorizer,
                                                keccak256(
                                                    abi.encode(CANCEL_AUTHORIZATION_TYPEHASH, authorizer, nonce)
                                                ),
                                                signature
                                            );
                                            _authorizationStates[authorizer][nonce] = true;
                                            emit AuthorizationCanceled(authorizer, nonce);
                                        }
                                        /**
                                         * @notice Validates that signature against input data struct
                                         * @param signer        Signer's address
                                         * @param dataHash      Hash of encoded data struct
                                         * @param signature     Signature byte array produced by an EOA wallet or a contract wallet
                                         */
                                        function _requireValidSignature(
                                            address signer,
                                            bytes32 dataHash,
                                            bytes memory signature
                                        ) private view {
                                            require(
                                                SignatureChecker.isValidSignatureNow(
                                                    signer,
                                                    MessageHashUtils.toTypedDataHash(_domainSeparator(), dataHash),
                                                    signature
                                                ),
                                                "FiatTokenV2: invalid signature"
                                            );
                                        }
                                        /**
                                         * @notice Check that an authorization is unused
                                         * @param authorizer    Authorizer's address
                                         * @param nonce         Nonce of the authorization
                                         */
                                        function _requireUnusedAuthorization(address authorizer, bytes32 nonce)
                                            private
                                            view
                                        {
                                            require(
                                                !_authorizationStates[authorizer][nonce],
                                                "FiatTokenV2: authorization is used or canceled"
                                            );
                                        }
                                        /**
                                         * @notice Check that authorization is valid
                                         * @param authorizer    Authorizer's address
                                         * @param nonce         Nonce of the authorization
                                         * @param validAfter    The time after which this is valid (unix time)
                                         * @param validBefore   The time before which this is valid (unix time)
                                         */
                                        function _requireValidAuthorization(
                                            address authorizer,
                                            bytes32 nonce,
                                            uint256 validAfter,
                                            uint256 validBefore
                                        ) private view {
                                            require(
                                                now > validAfter,
                                                "FiatTokenV2: authorization is not yet valid"
                                            );
                                            require(now < validBefore, "FiatTokenV2: authorization is expired");
                                            _requireUnusedAuthorization(authorizer, nonce);
                                        }
                                        /**
                                         * @notice Mark an authorization as used
                                         * @param authorizer    Authorizer's address
                                         * @param nonce         Nonce of the authorization
                                         */
                                        function _markAuthorizationAsUsed(address authorizer, bytes32 nonce)
                                            private
                                        {
                                            _authorizationStates[authorizer][nonce] = true;
                                            emit AuthorizationUsed(authorizer, nonce);
                                        }
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    import { AbstractFiatTokenV2 } from "./AbstractFiatTokenV2.sol";
                                    import { EIP712Domain } from "./EIP712Domain.sol";
                                    import { MessageHashUtils } from "../util/MessageHashUtils.sol";
                                    import { SignatureChecker } from "../util/SignatureChecker.sol";
                                    /**
                                     * @title EIP-2612
                                     * @notice Provide internal implementation for gas-abstracted approvals
                                     */
                                    abstract contract EIP2612 is AbstractFiatTokenV2, EIP712Domain {
                                        // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
                                        bytes32
                                            public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
                                        mapping(address => uint256) private _permitNonces;
                                        /**
                                         * @notice Nonces for permit
                                         * @param owner Token owner's address (Authorizer)
                                         * @return Next nonce
                                         */
                                        function nonces(address owner) external view returns (uint256) {
                                            return _permitNonces[owner];
                                        }
                                        /**
                                         * @notice Verify a signed approval permit and execute if valid
                                         * @param owner     Token owner's address (Authorizer)
                                         * @param spender   Spender's address
                                         * @param value     Amount of allowance
                                         * @param deadline  The time at which the signature expires (unix time), or max uint256 value to signal no expiration
                                         * @param v         v of the signature
                                         * @param r         r of the signature
                                         * @param s         s of the signature
                                         */
                                        function _permit(
                                            address owner,
                                            address spender,
                                            uint256 value,
                                            uint256 deadline,
                                            uint8 v,
                                            bytes32 r,
                                            bytes32 s
                                        ) internal {
                                            _permit(owner, spender, value, deadline, abi.encodePacked(r, s, v));
                                        }
                                        /**
                                         * @notice Verify a signed approval permit and execute if valid
                                         * @dev EOA wallet signatures should be packed in the order of r, s, v.
                                         * @param owner      Token owner's address (Authorizer)
                                         * @param spender    Spender's address
                                         * @param value      Amount of allowance
                                         * @param deadline   The time at which the signature expires (unix time), or max uint256 value to signal no expiration
                                         * @param signature  Signature byte array signed by an EOA wallet or a contract wallet
                                         */
                                        function _permit(
                                            address owner,
                                            address spender,
                                            uint256 value,
                                            uint256 deadline,
                                            bytes memory signature
                                        ) internal {
                                            require(
                                                deadline == type(uint256).max || deadline >= now,
                                                "FiatTokenV2: permit is expired"
                                            );
                                            bytes32 typedDataHash = MessageHashUtils.toTypedDataHash(
                                                _domainSeparator(),
                                                keccak256(
                                                    abi.encode(
                                                        PERMIT_TYPEHASH,
                                                        owner,
                                                        spender,
                                                        value,
                                                        _permitNonces[owner]++,
                                                        deadline
                                                    )
                                                )
                                            );
                                            require(
                                                SignatureChecker.isValidSignatureNow(
                                                    owner,
                                                    typedDataHash,
                                                    signature
                                                ),
                                                "EIP2612: invalid signature"
                                            );
                                            _approve(owner, spender, value);
                                        }
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    import { AbstractFiatTokenV1 } from "../v1/AbstractFiatTokenV1.sol";
                                    abstract contract AbstractFiatTokenV2 is AbstractFiatTokenV1 {
                                        function _increaseAllowance(
                                            address owner,
                                            address spender,
                                            uint256 increment
                                        ) internal virtual;
                                        function _decreaseAllowance(
                                            address owner,
                                            address spender,
                                            uint256 decrement
                                        ) internal virtual;
                                    }
                                    /**
                                     * SPDX-License-Identifier: MIT
                                     *
                                     * Copyright (c) 2016 Smart Contract Solutions, Inc.
                                     * Copyright (c) 2018-2020 CENTRE SECZ
                                     *
                                     * Permission is hereby granted, free of charge, to any person obtaining a copy
                                     * of this software and associated documentation files (the "Software"), to deal
                                     * in the Software without restriction, including without limitation the rights
                                     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
                                     * copies of the Software, and to permit persons to whom the Software is
                                     * furnished to do so, subject to the following conditions:
                                     *
                                     * The above copyright notice and this permission notice shall be included in
                                     * copies or substantial portions of the Software.
                                     *
                                     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
                                     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
                                     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
                                     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
                                     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
                                     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
                                     * SOFTWARE.
                                     */
                                    pragma solidity 0.6.12;
                                    import { Ownable } from "./Ownable.sol";
                                    /**
                                     * @notice Base contract which allows children to implement an emergency stop
                                     * mechanism
                                     * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/feb665136c0dae9912e08397c1a21c4af3651ef3/contracts/lifecycle/Pausable.sol
                                     * Modifications:
                                     * 1. Added pauser role, switched pause/unpause to be onlyPauser (6/14/2018)
                                     * 2. Removed whenNotPause/whenPaused from pause/unpause (6/14/2018)
                                     * 3. Removed whenPaused (6/14/2018)
                                     * 4. Switches ownable library to use ZeppelinOS (7/12/18)
                                     * 5. Remove constructor (7/13/18)
                                     * 6. Reformat, conform to Solidity 0.6 syntax and add error messages (5/13/20)
                                     * 7. Make public functions external (5/27/20)
                                     */
                                    contract Pausable is Ownable {
                                        event Pause();
                                        event Unpause();
                                        event PauserChanged(address indexed newAddress);
                                        address public pauser;
                                        bool public paused = false;
                                        /**
                                         * @dev Modifier to make a function callable only when the contract is not paused.
                                         */
                                        modifier whenNotPaused() {
                                            require(!paused, "Pausable: paused");
                                            _;
                                        }
                                        /**
                                         * @dev throws if called by any account other than the pauser
                                         */
                                        modifier onlyPauser() {
                                            require(msg.sender == pauser, "Pausable: caller is not the pauser");
                                            _;
                                        }
                                        /**
                                         * @dev called by the owner to pause, triggers stopped state
                                         */
                                        function pause() external onlyPauser {
                                            paused = true;
                                            emit Pause();
                                        }
                                        /**
                                         * @dev called by the owner to unpause, returns to normal state
                                         */
                                        function unpause() external onlyPauser {
                                            paused = false;
                                            emit Unpause();
                                        }
                                        /**
                                         * @notice Updates the pauser address.
                                         * @param _newPauser The address of the new pauser.
                                         */
                                        function updatePauser(address _newPauser) external onlyOwner {
                                            require(
                                                _newPauser != address(0),
                                                "Pausable: new pauser is the zero address"
                                            );
                                            pauser = _newPauser;
                                            emit PauserChanged(pauser);
                                        }
                                    }
                                    /**
                                     * SPDX-License-Identifier: MIT
                                     *
                                     * Copyright (c) 2018 zOS Global Limited.
                                     * Copyright (c) 2018-2020 CENTRE SECZ
                                     *
                                     * Permission is hereby granted, free of charge, to any person obtaining a copy
                                     * of this software and associated documentation files (the "Software"), to deal
                                     * in the Software without restriction, including without limitation the rights
                                     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
                                     * copies of the Software, and to permit persons to whom the Software is
                                     * furnished to do so, subject to the following conditions:
                                     *
                                     * The above copyright notice and this permission notice shall be included in
                                     * copies or substantial portions of the Software.
                                     *
                                     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
                                     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
                                     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
                                     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
                                     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
                                     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
                                     * SOFTWARE.
                                     */
                                    pragma solidity 0.6.12;
                                    /**
                                     * @notice The Ownable contract has an owner address, and provides basic
                                     * authorization control functions
                                     * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-labs/blob/3887ab77b8adafba4a26ace002f3a684c1a3388b/upgradeability_ownership/contracts/ownership/Ownable.sol
                                     * Modifications:
                                     * 1. Consolidate OwnableStorage into this contract (7/13/18)
                                     * 2. Reformat, conform to Solidity 0.6 syntax, and add error messages (5/13/20)
                                     * 3. Make public functions external (5/27/20)
                                     */
                                    contract Ownable {
                                        // Owner of the contract
                                        address private _owner;
                                        /**
                                         * @dev Event to show ownership has been transferred
                                         * @param previousOwner representing the address of the previous owner
                                         * @param newOwner representing the address of the new owner
                                         */
                                        event OwnershipTransferred(address previousOwner, address newOwner);
                                        /**
                                         * @dev The constructor sets the original owner of the contract to the sender account.
                                         */
                                        constructor() public {
                                            setOwner(msg.sender);
                                        }
                                        /**
                                         * @dev Tells the address of the owner
                                         * @return the address of the owner
                                         */
                                        function owner() external view returns (address) {
                                            return _owner;
                                        }
                                        /**
                                         * @dev Sets a new owner address
                                         */
                                        function setOwner(address newOwner) internal {
                                            _owner = newOwner;
                                        }
                                        /**
                                         * @dev Throws if called by any account other than the owner.
                                         */
                                        modifier onlyOwner() {
                                            require(msg.sender == _owner, "Ownable: caller is not the owner");
                                            _;
                                        }
                                        /**
                                         * @dev Allows the current owner to transfer control of the contract to a newOwner.
                                         * @param newOwner The address to transfer ownership to.
                                         */
                                        function transferOwnership(address newOwner) external onlyOwner {
                                            require(
                                                newOwner != address(0),
                                                "Ownable: new owner is the zero address"
                                            );
                                            emit OwnershipTransferred(_owner, newOwner);
                                            setOwner(newOwner);
                                        }
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
                                    import { AbstractFiatTokenV1 } from "./AbstractFiatTokenV1.sol";
                                    import { Ownable } from "./Ownable.sol";
                                    import { Pausable } from "./Pausable.sol";
                                    import { Blacklistable } from "./Blacklistable.sol";
                                    /**
                                     * @title FiatToken
                                     * @dev ERC20 Token backed by fiat reserves
                                     */
                                    contract FiatTokenV1 is AbstractFiatTokenV1, Ownable, Pausable, Blacklistable {
                                        using SafeMath for uint256;
                                        string public name;
                                        string public symbol;
                                        uint8 public decimals;
                                        string public currency;
                                        address public masterMinter;
                                        bool internal initialized;
                                        /// @dev A mapping that stores the balance and blacklist states for a given address.
                                        /// The first bit defines whether the address is blacklisted (1 if blacklisted, 0 otherwise).
                                        /// The last 255 bits define the balance for the address.
                                        mapping(address => uint256) internal balanceAndBlacklistStates;
                                        mapping(address => mapping(address => uint256)) internal allowed;
                                        uint256 internal totalSupply_ = 0;
                                        mapping(address => bool) internal minters;
                                        mapping(address => uint256) internal minterAllowed;
                                        event Mint(address indexed minter, address indexed to, uint256 amount);
                                        event Burn(address indexed burner, uint256 amount);
                                        event MinterConfigured(address indexed minter, uint256 minterAllowedAmount);
                                        event MinterRemoved(address indexed oldMinter);
                                        event MasterMinterChanged(address indexed newMasterMinter);
                                        /**
                                         * @notice Initializes the fiat token contract.
                                         * @param tokenName       The name of the fiat token.
                                         * @param tokenSymbol     The symbol of the fiat token.
                                         * @param tokenCurrency   The fiat currency that the token represents.
                                         * @param tokenDecimals   The number of decimals that the token uses.
                                         * @param newMasterMinter The masterMinter address for the fiat token.
                                         * @param newPauser       The pauser address for the fiat token.
                                         * @param newBlacklister  The blacklister address for the fiat token.
                                         * @param newOwner        The owner of the fiat token.
                                         */
                                        function initialize(
                                            string memory tokenName,
                                            string memory tokenSymbol,
                                            string memory tokenCurrency,
                                            uint8 tokenDecimals,
                                            address newMasterMinter,
                                            address newPauser,
                                            address newBlacklister,
                                            address newOwner
                                        ) public {
                                            require(!initialized, "FiatToken: contract is already initialized");
                                            require(
                                                newMasterMinter != address(0),
                                                "FiatToken: new masterMinter is the zero address"
                                            );
                                            require(
                                                newPauser != address(0),
                                                "FiatToken: new pauser is the zero address"
                                            );
                                            require(
                                                newBlacklister != address(0),
                                                "FiatToken: new blacklister is the zero address"
                                            );
                                            require(
                                                newOwner != address(0),
                                                "FiatToken: new owner is the zero address"
                                            );
                                            name = tokenName;
                                            symbol = tokenSymbol;
                                            currency = tokenCurrency;
                                            decimals = tokenDecimals;
                                            masterMinter = newMasterMinter;
                                            pauser = newPauser;
                                            blacklister = newBlacklister;
                                            setOwner(newOwner);
                                            initialized = true;
                                        }
                                        /**
                                         * @dev Throws if called by any account other than a minter.
                                         */
                                        modifier onlyMinters() {
                                            require(minters[msg.sender], "FiatToken: caller is not a minter");
                                            _;
                                        }
                                        /**
                                         * @notice Mints fiat tokens to an address.
                                         * @param _to The address that will receive the minted tokens.
                                         * @param _amount The amount of tokens to mint. Must be less than or equal
                                         * to the minterAllowance of the caller.
                                         * @return True if the operation was successful.
                                         */
                                        function mint(address _to, uint256 _amount)
                                            external
                                            whenNotPaused
                                            onlyMinters
                                            notBlacklisted(msg.sender)
                                            notBlacklisted(_to)
                                            returns (bool)
                                        {
                                            require(_to != address(0), "FiatToken: mint to the zero address");
                                            require(_amount > 0, "FiatToken: mint amount not greater than 0");
                                            uint256 mintingAllowedAmount = minterAllowed[msg.sender];
                                            require(
                                                _amount <= mintingAllowedAmount,
                                                "FiatToken: mint amount exceeds minterAllowance"
                                            );
                                            totalSupply_ = totalSupply_.add(_amount);
                                            _setBalance(_to, _balanceOf(_to).add(_amount));
                                            minterAllowed[msg.sender] = mintingAllowedAmount.sub(_amount);
                                            emit Mint(msg.sender, _to, _amount);
                                            emit Transfer(address(0), _to, _amount);
                                            return true;
                                        }
                                        /**
                                         * @dev Throws if called by any account other than the masterMinter
                                         */
                                        modifier onlyMasterMinter() {
                                            require(
                                                msg.sender == masterMinter,
                                                "FiatToken: caller is not the masterMinter"
                                            );
                                            _;
                                        }
                                        /**
                                         * @notice Gets the minter allowance for an account.
                                         * @param minter The address to check.
                                         * @return The remaining minter allowance for the account.
                                         */
                                        function minterAllowance(address minter) external view returns (uint256) {
                                            return minterAllowed[minter];
                                        }
                                        /**
                                         * @notice Checks if an account is a minter.
                                         * @param account The address to check.
                                         * @return True if the account is a minter, false if the account is not a minter.
                                         */
                                        function isMinter(address account) external view returns (bool) {
                                            return minters[account];
                                        }
                                        /**
                                         * @notice Gets the remaining amount of fiat tokens a spender is allowed to transfer on
                                         * behalf of the token owner.
                                         * @param owner   The token owner's address.
                                         * @param spender The spender's address.
                                         * @return The remaining allowance.
                                         */
                                        function allowance(address owner, address spender)
                                            external
                                            override
                                            view
                                            returns (uint256)
                                        {
                                            return allowed[owner][spender];
                                        }
                                        /**
                                         * @notice Gets the totalSupply of the fiat token.
                                         * @return The totalSupply of the fiat token.
                                         */
                                        function totalSupply() external override view returns (uint256) {
                                            return totalSupply_;
                                        }
                                        /**
                                         * @notice Gets the fiat token balance of an account.
                                         * @param account  The address to check.
                                         * @return balance The fiat token balance of the account.
                                         */
                                        function balanceOf(address account)
                                            external
                                            override
                                            view
                                            returns (uint256)
                                        {
                                            return _balanceOf(account);
                                        }
                                        /**
                                         * @notice Sets a fiat token allowance for a spender to spend on behalf of the caller.
                                         * @param spender The spender's address.
                                         * @param value   The allowance amount.
                                         * @return True if the operation was successful.
                                         */
                                        function approve(address spender, uint256 value)
                                            external
                                            virtual
                                            override
                                            whenNotPaused
                                            notBlacklisted(msg.sender)
                                            notBlacklisted(spender)
                                            returns (bool)
                                        {
                                            _approve(msg.sender, spender, value);
                                            return true;
                                        }
                                        /**
                                         * @dev Internal function to set allowance.
                                         * @param owner     Token owner's address.
                                         * @param spender   Spender's address.
                                         * @param value     Allowance amount.
                                         */
                                        function _approve(
                                            address owner,
                                            address spender,
                                            uint256 value
                                        ) internal override {
                                            require(owner != address(0), "ERC20: approve from the zero address");
                                            require(spender != address(0), "ERC20: approve to the zero address");
                                            allowed[owner][spender] = value;
                                            emit Approval(owner, spender, value);
                                        }
                                        /**
                                         * @notice Transfers tokens from an address to another by spending the caller's allowance.
                                         * @dev The caller must have some fiat token allowance on the payer's tokens.
                                         * @param from  Payer's address.
                                         * @param to    Payee's address.
                                         * @param value Transfer amount.
                                         * @return True if the operation was successful.
                                         */
                                        function transferFrom(
                                            address from,
                                            address to,
                                            uint256 value
                                        )
                                            external
                                            override
                                            whenNotPaused
                                            notBlacklisted(msg.sender)
                                            notBlacklisted(from)
                                            notBlacklisted(to)
                                            returns (bool)
                                        {
                                            require(
                                                value <= allowed[from][msg.sender],
                                                "ERC20: transfer amount exceeds allowance"
                                            );
                                            _transfer(from, to, value);
                                            allowed[from][msg.sender] = allowed[from][msg.sender].sub(value);
                                            return true;
                                        }
                                        /**
                                         * @notice Transfers tokens from the caller.
                                         * @param to    Payee's address.
                                         * @param value Transfer amount.
                                         * @return True if the operation was successful.
                                         */
                                        function transfer(address to, uint256 value)
                                            external
                                            override
                                            whenNotPaused
                                            notBlacklisted(msg.sender)
                                            notBlacklisted(to)
                                            returns (bool)
                                        {
                                            _transfer(msg.sender, to, value);
                                            return true;
                                        }
                                        /**
                                         * @dev Internal function to process transfers.
                                         * @param from  Payer's address.
                                         * @param to    Payee's address.
                                         * @param value Transfer amount.
                                         */
                                        function _transfer(
                                            address from,
                                            address to,
                                            uint256 value
                                        ) internal override {
                                            require(from != address(0), "ERC20: transfer from the zero address");
                                            require(to != address(0), "ERC20: transfer to the zero address");
                                            require(
                                                value <= _balanceOf(from),
                                                "ERC20: transfer amount exceeds balance"
                                            );
                                            _setBalance(from, _balanceOf(from).sub(value));
                                            _setBalance(to, _balanceOf(to).add(value));
                                            emit Transfer(from, to, value);
                                        }
                                        /**
                                         * @notice Adds or updates a new minter with a mint allowance.
                                         * @param minter The address of the minter.
                                         * @param minterAllowedAmount The minting amount allowed for the minter.
                                         * @return True if the operation was successful.
                                         */
                                        function configureMinter(address minter, uint256 minterAllowedAmount)
                                            external
                                            whenNotPaused
                                            onlyMasterMinter
                                            returns (bool)
                                        {
                                            minters[minter] = true;
                                            minterAllowed[minter] = minterAllowedAmount;
                                            emit MinterConfigured(minter, minterAllowedAmount);
                                            return true;
                                        }
                                        /**
                                         * @notice Removes a minter.
                                         * @param minter The address of the minter to remove.
                                         * @return True if the operation was successful.
                                         */
                                        function removeMinter(address minter)
                                            external
                                            onlyMasterMinter
                                            returns (bool)
                                        {
                                            minters[minter] = false;
                                            minterAllowed[minter] = 0;
                                            emit MinterRemoved(minter);
                                            return true;
                                        }
                                        /**
                                         * @notice Allows a minter to burn some of its own tokens.
                                         * @dev The caller must be a minter, must not be blacklisted, and the amount to burn
                                         * should be less than or equal to the account's balance.
                                         * @param _amount the amount of tokens to be burned.
                                         */
                                        function burn(uint256 _amount)
                                            external
                                            whenNotPaused
                                            onlyMinters
                                            notBlacklisted(msg.sender)
                                        {
                                            uint256 balance = _balanceOf(msg.sender);
                                            require(_amount > 0, "FiatToken: burn amount not greater than 0");
                                            require(balance >= _amount, "FiatToken: burn amount exceeds balance");
                                            totalSupply_ = totalSupply_.sub(_amount);
                                            _setBalance(msg.sender, balance.sub(_amount));
                                            emit Burn(msg.sender, _amount);
                                            emit Transfer(msg.sender, address(0), _amount);
                                        }
                                        /**
                                         * @notice Updates the master minter address.
                                         * @param _newMasterMinter The address of the new master minter.
                                         */
                                        function updateMasterMinter(address _newMasterMinter) external onlyOwner {
                                            require(
                                                _newMasterMinter != address(0),
                                                "FiatToken: new masterMinter is the zero address"
                                            );
                                            masterMinter = _newMasterMinter;
                                            emit MasterMinterChanged(masterMinter);
                                        }
                                        /**
                                         * @inheritdoc Blacklistable
                                         */
                                        function _blacklist(address _account) internal override {
                                            _setBlacklistState(_account, true);
                                        }
                                        /**
                                         * @inheritdoc Blacklistable
                                         */
                                        function _unBlacklist(address _account) internal override {
                                            _setBlacklistState(_account, false);
                                        }
                                        /**
                                         * @dev Helper method that sets the blacklist state of an account.
                                         * @param _account         The address of the account.
                                         * @param _shouldBlacklist True if the account should be blacklisted, false if the account should be unblacklisted.
                                         */
                                        function _setBlacklistState(address _account, bool _shouldBlacklist)
                                            internal
                                            virtual
                                        {
                                            _deprecatedBlacklisted[_account] = _shouldBlacklist;
                                        }
                                        /**
                                         * @dev Helper method that sets the balance of an account.
                                         * @param _account The address of the account.
                                         * @param _balance The new fiat token balance of the account.
                                         */
                                        function _setBalance(address _account, uint256 _balance) internal virtual {
                                            balanceAndBlacklistStates[_account] = _balance;
                                        }
                                        /**
                                         * @inheritdoc Blacklistable
                                         */
                                        function _isBlacklisted(address _account)
                                            internal
                                            virtual
                                            override
                                            view
                                            returns (bool)
                                        {
                                            return _deprecatedBlacklisted[_account];
                                        }
                                        /**
                                         * @dev Helper method to obtain the balance of an account.
                                         * @param _account  The address of the account.
                                         * @return          The fiat token balance of the account.
                                         */
                                        function _balanceOf(address _account)
                                            internal
                                            virtual
                                            view
                                            returns (uint256)
                                        {
                                            return balanceAndBlacklistStates[_account];
                                        }
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    import { Ownable } from "./Ownable.sol";
                                    /**
                                     * @title Blacklistable Token
                                     * @dev Allows accounts to be blacklisted by a "blacklister" role
                                     */
                                    abstract contract Blacklistable is Ownable {
                                        address public blacklister;
                                        mapping(address => bool) internal _deprecatedBlacklisted;
                                        event Blacklisted(address indexed _account);
                                        event UnBlacklisted(address indexed _account);
                                        event BlacklisterChanged(address indexed newBlacklister);
                                        /**
                                         * @dev Throws if called by any account other than the blacklister.
                                         */
                                        modifier onlyBlacklister() {
                                            require(
                                                msg.sender == blacklister,
                                                "Blacklistable: caller is not the blacklister"
                                            );
                                            _;
                                        }
                                        /**
                                         * @dev Throws if argument account is blacklisted.
                                         * @param _account The address to check.
                                         */
                                        modifier notBlacklisted(address _account) {
                                            require(
                                                !_isBlacklisted(_account),
                                                "Blacklistable: account is blacklisted"
                                            );
                                            _;
                                        }
                                        /**
                                         * @notice Checks if account is blacklisted.
                                         * @param _account The address to check.
                                         * @return True if the account is blacklisted, false if the account is not blacklisted.
                                         */
                                        function isBlacklisted(address _account) external view returns (bool) {
                                            return _isBlacklisted(_account);
                                        }
                                        /**
                                         * @notice Adds account to blacklist.
                                         * @param _account The address to blacklist.
                                         */
                                        function blacklist(address _account) external onlyBlacklister {
                                            _blacklist(_account);
                                            emit Blacklisted(_account);
                                        }
                                        /**
                                         * @notice Removes account from blacklist.
                                         * @param _account The address to remove from the blacklist.
                                         */
                                        function unBlacklist(address _account) external onlyBlacklister {
                                            _unBlacklist(_account);
                                            emit UnBlacklisted(_account);
                                        }
                                        /**
                                         * @notice Updates the blacklister address.
                                         * @param _newBlacklister The address of the new blacklister.
                                         */
                                        function updateBlacklister(address _newBlacklister) external onlyOwner {
                                            require(
                                                _newBlacklister != address(0),
                                                "Blacklistable: new blacklister is the zero address"
                                            );
                                            blacklister = _newBlacklister;
                                            emit BlacklisterChanged(blacklister);
                                        }
                                        /**
                                         * @dev Checks if account is blacklisted.
                                         * @param _account The address to check.
                                         * @return true if the account is blacklisted, false otherwise.
                                         */
                                        function _isBlacklisted(address _account)
                                            internal
                                            virtual
                                            view
                                            returns (bool);
                                        /**
                                         * @dev Helper method that blacklists an account.
                                         * @param _account The address to blacklist.
                                         */
                                        function _blacklist(address _account) internal virtual;
                                        /**
                                         * @dev Helper method that unblacklists an account.
                                         * @param _account The address to unblacklist.
                                         */
                                        function _unBlacklist(address _account) internal virtual;
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                                    abstract contract AbstractFiatTokenV1 is IERC20 {
                                        function _approve(
                                            address owner,
                                            address spender,
                                            uint256 value
                                        ) internal virtual;
                                        function _transfer(
                                            address from,
                                            address to,
                                            uint256 value
                                        ) internal virtual;
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    import { Ownable } from "../v1/Ownable.sol";
                                    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                                    import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
                                    contract Rescuable is Ownable {
                                        using SafeERC20 for IERC20;
                                        address private _rescuer;
                                        event RescuerChanged(address indexed newRescuer);
                                        /**
                                         * @notice Returns current rescuer
                                         * @return Rescuer's address
                                         */
                                        function rescuer() external view returns (address) {
                                            return _rescuer;
                                        }
                                        /**
                                         * @notice Revert if called by any account other than the rescuer.
                                         */
                                        modifier onlyRescuer() {
                                            require(msg.sender == _rescuer, "Rescuable: caller is not the rescuer");
                                            _;
                                        }
                                        /**
                                         * @notice Rescue ERC20 tokens locked up in this contract.
                                         * @param tokenContract ERC20 token contract address
                                         * @param to        Recipient address
                                         * @param amount    Amount to withdraw
                                         */
                                        function rescueERC20(
                                            IERC20 tokenContract,
                                            address to,
                                            uint256 amount
                                        ) external onlyRescuer {
                                            tokenContract.safeTransfer(to, amount);
                                        }
                                        /**
                                         * @notice Updates the rescuer address.
                                         * @param newRescuer The address of the new rescuer.
                                         */
                                        function updateRescuer(address newRescuer) external onlyOwner {
                                            require(
                                                newRescuer != address(0),
                                                "Rescuable: new rescuer is the zero address"
                                            );
                                            _rescuer = newRescuer;
                                            emit RescuerChanged(newRescuer);
                                        }
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    import { FiatTokenV1 } from "../v1/FiatTokenV1.sol";
                                    import { Rescuable } from "./Rescuable.sol";
                                    /**
                                     * @title FiatTokenV1_1
                                     * @dev ERC20 Token backed by fiat reserves
                                     */
                                    contract FiatTokenV1_1 is FiatTokenV1, Rescuable {
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    import { ECRecover } from "./ECRecover.sol";
                                    import { IERC1271 } from "../interface/IERC1271.sol";
                                    /**
                                     * @dev Signature verification helper that can be used instead of `ECRecover.recover` to seamlessly support both ECDSA
                                     * signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets.
                                     *
                                     * Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/21bb89ef5bfc789b9333eb05e3ba2b7b284ac77c/contracts/utils/cryptography/SignatureChecker.sol
                                     */
                                    library SignatureChecker {
                                        /**
                                         * @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the
                                         * signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECRecover.recover`.
                                         * @param signer        Address of the claimed signer
                                         * @param digest        Keccak-256 hash digest of the signed message
                                         * @param signature     Signature byte array associated with hash
                                         */
                                        function isValidSignatureNow(
                                            address signer,
                                            bytes32 digest,
                                            bytes memory signature
                                        ) external view returns (bool) {
                                            if (!isContract(signer)) {
                                                return ECRecover.recover(digest, signature) == signer;
                                            }
                                            return isValidERC1271SignatureNow(signer, digest, signature);
                                        }
                                        /**
                                         * @dev Checks if a signature is valid for a given signer and data hash. The signature is validated
                                         * against the signer smart contract using ERC1271.
                                         * @param signer        Address of the claimed signer
                                         * @param digest        Keccak-256 hash digest of the signed message
                                         * @param signature     Signature byte array associated with hash
                                         *
                                         * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
                                         * change through time. It could return true at block N and false at block N+1 (or the opposite).
                                         */
                                        function isValidERC1271SignatureNow(
                                            address signer,
                                            bytes32 digest,
                                            bytes memory signature
                                        ) internal view returns (bool) {
                                            (bool success, bytes memory result) = signer.staticcall(
                                                abi.encodeWithSelector(
                                                    IERC1271.isValidSignature.selector,
                                                    digest,
                                                    signature
                                                )
                                            );
                                            return (success &&
                                                result.length >= 32 &&
                                                abi.decode(result, (bytes32)) ==
                                                bytes32(IERC1271.isValidSignature.selector));
                                        }
                                        /**
                                         * @dev Checks if the input address is a smart contract.
                                         */
                                        function isContract(address addr) internal view returns (bool) {
                                            uint256 size;
                                            assembly {
                                                size := extcodesize(addr)
                                            }
                                            return size > 0;
                                        }
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    /**
                                     * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
                                     *
                                     * The library provides methods for generating a hash of a message that conforms to the
                                     * https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
                                     * specifications.
                                     */
                                    library MessageHashUtils {
                                        /**
                                         * @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`).
                                         * Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/21bb89ef5bfc789b9333eb05e3ba2b7b284ac77c/contracts/utils/cryptography/MessageHashUtils.sol
                                         *
                                         * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
                                         * `\\x19\\x01` and hashing the result. It corresponds to the hash signed by the
                                         * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
                                         *
                                         * @param domainSeparator    Domain separator
                                         * @param structHash         Hashed EIP-712 data struct
                                         * @return digest            The keccak256 digest of an EIP-712 typed data
                                         */
                                        function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash)
                                            internal
                                            pure
                                            returns (bytes32 digest)
                                        {
                                            assembly {
                                                let ptr := mload(0x40)
                                                mstore(ptr, "\\x19\\x01")
                                                mstore(add(ptr, 0x02), domainSeparator)
                                                mstore(add(ptr, 0x22), structHash)
                                                digest := keccak256(ptr, 0x42)
                                            }
                                        }
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    /**
                                     * @title EIP712
                                     * @notice A library that provides EIP712 helper functions
                                     */
                                    library EIP712 {
                                        /**
                                         * @notice Make EIP712 domain separator
                                         * @param name      Contract name
                                         * @param version   Contract version
                                         * @param chainId   Blockchain ID
                                         * @return Domain separator
                                         */
                                        function makeDomainSeparator(
                                            string memory name,
                                            string memory version,
                                            uint256 chainId
                                        ) internal view returns (bytes32) {
                                            return
                                                keccak256(
                                                    abi.encode(
                                                        // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
                                                        0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
                                                        keccak256(bytes(name)),
                                                        keccak256(bytes(version)),
                                                        chainId,
                                                        address(this)
                                                    )
                                                );
                                        }
                                        /**
                                         * @notice Make EIP712 domain separator
                                         * @param name      Contract name
                                         * @param version   Contract version
                                         * @return Domain separator
                                         */
                                        function makeDomainSeparator(string memory name, string memory version)
                                            internal
                                            view
                                            returns (bytes32)
                                        {
                                            uint256 chainId;
                                            assembly {
                                                chainId := chainid()
                                            }
                                            return makeDomainSeparator(name, version, chainId);
                                        }
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    /**
                                     * @title ECRecover
                                     * @notice A library that provides a safe ECDSA recovery function
                                     */
                                    library ECRecover {
                                        /**
                                         * @notice Recover signer's address from a signed message
                                         * @dev Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/65e4ffde586ec89af3b7e9140bdc9235d1254853/contracts/cryptography/ECDSA.sol
                                         * Modifications: Accept v, r, and s as separate arguments
                                         * @param digest    Keccak-256 hash digest of the signed message
                                         * @param v         v of the signature
                                         * @param r         r of the signature
                                         * @param s         s of the signature
                                         * @return Signer address
                                         */
                                        function recover(
                                            bytes32 digest,
                                            uint8 v,
                                            bytes32 r,
                                            bytes32 s
                                        ) internal pure returns (address) {
                                            // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                                            // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                                            // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
                                            // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                                            //
                                            // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                                            // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                                            // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                                            // these malleable signatures as well.
                                            if (
                                                uint256(s) >
                                                0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
                                            ) {
                                                revert("ECRecover: invalid signature 's' value");
                                            }
                                            if (v != 27 && v != 28) {
                                                revert("ECRecover: invalid signature 'v' value");
                                            }
                                            // If the signature is valid (and not malleable), return the signer address
                                            address signer = ecrecover(digest, v, r, s);
                                            require(signer != address(0), "ECRecover: invalid signature");
                                            return signer;
                                        }
                                        /**
                                         * @notice Recover signer's address from a signed message
                                         * @dev Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/0053ee040a7ff1dbc39691c9e67a69f564930a88/contracts/utils/cryptography/ECDSA.sol
                                         * @param digest    Keccak-256 hash digest of the signed message
                                         * @param signature Signature byte array associated with hash
                                         * @return Signer address
                                         */
                                        function recover(bytes32 digest, bytes memory signature)
                                            internal
                                            pure
                                            returns (address)
                                        {
                                            require(signature.length == 65, "ECRecover: invalid signature length");
                                            bytes32 r;
                                            bytes32 s;
                                            uint8 v;
                                            // ecrecover takes the signature parameters, and the only way to get them
                                            // currently is to use assembly.
                                            /// @solidity memory-safe-assembly
                                            assembly {
                                                r := mload(add(signature, 0x20))
                                                s := mload(add(signature, 0x40))
                                                v := byte(0, mload(add(signature, 0x60)))
                                            }
                                            return recover(digest, v, r, s);
                                        }
                                    }
                                    /**
                                     * SPDX-License-Identifier: Apache-2.0
                                     *
                                     * Copyright (c) 2023, Circle Internet Financial, LLC.
                                     *
                                     * Licensed under the Apache License, Version 2.0 (the "License");
                                     * you may not use this file except in compliance with the License.
                                     * You may obtain a copy of the License at
                                     *
                                     * http://www.apache.org/licenses/LICENSE-2.0
                                     *
                                     * Unless required by applicable law or agreed to in writing, software
                                     * distributed under the License is distributed on an "AS IS" BASIS,
                                     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                     * See the License for the specific language governing permissions and
                                     * limitations under the License.
                                     */
                                    pragma solidity 0.6.12;
                                    /**
                                     * @dev Interface of the ERC1271 standard signature validation method for
                                     * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
                                     */
                                    interface IERC1271 {
                                        /**
                                         * @dev Should return whether the signature provided is valid for the provided data
                                         * @param hash          Hash of the data to be signed
                                         * @param signature     Signature byte array associated with the provided data hash
                                         * @return magicValue   bytes4 magic value 0x1626ba7e when function passes
                                         */
                                        function isValidSignature(bytes32 hash, bytes memory signature)
                                            external
                                            view
                                            returns (bytes4 magicValue);
                                    }
                                    

                                    File 6 of 6: 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()