ETH Price: $2,522.96 (-0.11%)

Transaction Decoder

Block:
21219807 at Nov-19-2024 05:52:59 AM +UTC
Transaction Fee:
0.001794903290485512 ETH $4.53
Gas Used:
195,652 Gas / 9.173958306 Gwei

Emitted Events:

435 SwellToken.Transfer( from=[Receiver] CumulativeMerkleDrop, to=VaultAdapter, value=1035276977668852244393 )
436 SwellToken.Approval( owner=[Receiver] CumulativeMerkleDrop, spender=VaultAdapter, value=115792089237316195423570985008687907853269984665640477150328983659413077679058 )
437 SwellToken.Transfer( from=VaultAdapter, to=Yearn V3 Vault, value=1035276977668852244393 )
438 SwellToken.Approval( owner=VaultAdapter, spender=Yearn V3 Vault, value=115792089237316195423570985008687907853269984665640477150328983659413077679058 )
439 Yearn V3 Vault.Transfer( sender=0x0000000000000000000000000000000000000000, receiver=[Sender] 0x2f170020148f41ba8e0e00b43cf6a807090c9f97, value=1035276977668852244393 )
440 Yearn V3 Vault.Deposit( sender=VaultAdapter, owner=[Sender] 0x2f170020148f41ba8e0e00b43cf6a807090c9f97, assets=1035276977668852244393, shares=1035276977668852244393 )
441 SwellToken.Transfer( from=[Receiver] CumulativeMerkleDrop, to=[Sender] 0x2f170020148f41ba8e0e00b43cf6a807090c9f97, value=557456834129381977751 )
442 CumulativeMerkleDrop.Claimed( account=[Sender] 0x2f170020148f41ba8e0e00b43cf6a807090c9f97, amount=1592733811798234222144, amountToLock=1035276977668852244393 )

Account State Difference:

  Address   Before After State Difference Code
0x0a6E7Ba5...EC7B35676
0x2F170020...7090c9F97
0.056301599125468148 Eth
Nonce: 461
0.054506695834982636 Eth
Nonce: 462
0.001794903290485512
0x342F0D37...39f739d75
(Swell Network: Cumulative Merkle Drop)
0x358d94b5...566acB7B6
(beaverbuild)
20.628628662517696718 Eth20.628902419577530806 Eth0.000273757059834088

Execution Trace

CumulativeMerkleDrop.claimAndLock( )
  • VaultAdapter.lock( _account=0x2F170020148f41BA8e0E00b43cf6A807090c9F97, _amount=1035276977668852244393 )
    • SwellToken.transferFrom( sender=0x342F0D375Ba986A65204750A4AECE3b39f739d75, recipient=0x6D8cC0262BB3802EAFaf4e7dDE7afd3383E8F872, amount=1035276977668852244393 ) => ( True )
    • V3.deposit( assets=1035276977668852244393, receiver=0x2F170020148f41BA8e0E00b43cf6A807090c9F97 ) => ( 1035276977668852244393 )
      • V3.deposit( assets=1035276977668852244393, receiver=0x2F170020148f41BA8e0E00b43cf6A807090c9F97 ) => ( 1035276977668852244393 )
        • SwellToken.transferFrom( sender=0x6D8cC0262BB3802EAFaf4e7dDE7afd3383E8F872, recipient=0x358d94b5b2F147D741088803d932Acb566acB7B6, amount=1035276977668852244393 ) => ( True )
        • SwellToken.transfer( recipient=0x2F170020148f41BA8e0E00b43cf6A807090c9F97, amount=557456834129381977751 ) => ( True )
          File 1 of 5: CumulativeMerkleDrop
          // SPDX-License-Identifier: UNLICENSED
          pragma solidity 0.8.23;
          import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import {Ownable, Ownable2Step} from "@openzeppelin/contracts-v5/access/Ownable2Step.sol";
          import {MerkleProof} from "@openzeppelin/contracts-v5/utils/cryptography/MerkleProof.sol";
          import {ILock} from "./interfaces/ILock.sol";
          import {ICumulativeMerkleDrop} from "./interfaces/ICumulativeMerkleDrop.sol";
          /// Contract which manages initial distribution of the SWELL token via merkle drop claim process.
          contract CumulativeMerkleDrop is Ownable2Step, ICumulativeMerkleDrop {
              /*//////////////////////////////////////////////////////////////
                                         CONSTANTS
              //////////////////////////////////////////////////////////////*/
              
              uint8 private constant OPEN = 1;
              uint8 private constant NOT_OPEN = 2;
              
              /*//////////////////////////////////////////////////////////////
                                         IMMUTABLES
              //////////////////////////////////////////////////////////////*/
              /// @inheritdoc ICumulativeMerkleDrop
              IERC20 public immutable token;
              /*//////////////////////////////////////////////////////////////
                                         VARIABLES
              //////////////////////////////////////////////////////////////*/
              /// @inheritdoc ICumulativeMerkleDrop
              uint8 public claimIsOpen;
              /// @inheritdoc ICumulativeMerkleDrop
              ILock public stakingContract;
              /// @inheritdoc ICumulativeMerkleDrop
              bytes32 public merkleRoot;
              /// @inheritdoc ICumulativeMerkleDrop
              mapping(address => uint256) public cumulativeClaimed;
              /*//////////////////////////////////////////////////////////////
                                        CONSTRUCTOR
              //////////////////////////////////////////////////////////////*/
              constructor(address _owner, address _token) Ownable(_owner) {
                  if (_token == address(0)) revert ADDRESS_NULL();
                  claimIsOpen = NOT_OPEN;
                  token = IERC20(_token);
              }
              /*//////////////////////////////////////////////////////////////
                                         MODIFIERS
              //////////////////////////////////////////////////////////////*/
              modifier onlyClaimOpen() {
                  if (claimIsOpen != OPEN) revert CLAIM_CLOSED();
                  _;
              }
              /*//////////////////////////////////////////////////////////////
                                          SETTERS
              //////////////////////////////////////////////////////////////*/
              /// @inheritdoc ICumulativeMerkleDrop
              function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner {
                  if (_merkleRoot == merkleRoot) revert SAME_MERKLE_ROOT();
                  emit MerkleRootUpdated(merkleRoot, _merkleRoot);
                  merkleRoot = _merkleRoot;
              }
              /// @inheritdoc ICumulativeMerkleDrop
              function setStakingContract(address _stakingContract) external onlyOwner {
                  if (_stakingContract == address(0)) revert ADDRESS_NULL();
                  address oldStakingContract = address(stakingContract);
                  
                  if (_stakingContract == oldStakingContract) revert SAME_STAKING_CONTRACT();
                  if (ILock(_stakingContract).token() != token) revert STAKING_TOKEN_MISMATCH();
                  emit StakingContractUpdated(oldStakingContract, _stakingContract);
                  stakingContract = ILock(_stakingContract);
                  token.approve(address(_stakingContract), type(uint256).max);
                  if (oldStakingContract != address(0)) {
                      token.approve(oldStakingContract, 0);
                  }
              }
              /// @inheritdoc ICumulativeMerkleDrop
              function clearStakingContract() external onlyOwner {
                  address oldStakingContract = address(stakingContract);
                  if (oldStakingContract == address(0)) revert SAME_STAKING_CONTRACT();
                  emit StakingContractCleared();
                  stakingContract = ILock(address(0));
                  token.approve(oldStakingContract, 0);
              }
              /// @inheritdoc ICumulativeMerkleDrop
              function setClaimStatus(uint8 status) external onlyOwner {
                  if (status != OPEN && status != NOT_OPEN) revert INVALID_STATUS();
                  emit ClaimStatusUpdated(claimIsOpen, status);
                  claimIsOpen = status;
              }
              /*//////////////////////////////////////////////////////////////
                                       MAIN FUNCTIONS
              //////////////////////////////////////////////////////////////*/
              /// @inheritdoc ICumulativeMerkleDrop
              function claimAndLock(uint256 cumulativeAmount, uint256 amountToLock, bytes32[] calldata merkleProof)
                  external
                  onlyClaimOpen
              {
                  // Verify the merkle proof
                  if (!verifyProof(merkleProof, cumulativeAmount, msg.sender)) revert INVALID_PROOF();
                  // Mark it claimed
                  uint256 preclaimed = cumulativeClaimed[msg.sender];
                  if (preclaimed >= cumulativeAmount) revert NOTHING_TO_CLAIM();
                  cumulativeClaimed[msg.sender] = cumulativeAmount;
                  // Send the token
                  uint256 amount = cumulativeAmount - preclaimed;
                  if (amountToLock > 0) {
                      if (amountToLock > amount) revert AMOUNT_TO_LOCK_GT_AMOUNT_CLAIMED();
                      // Ensure the staking contract is set before locking
                      if (address(stakingContract) == address(0)) revert STAKING_NOT_AVAILABLE();
                      stakingContract.lock(msg.sender, amountToLock);
                  }
                  if (amount != amountToLock) token.transfer(msg.sender, amount - amountToLock);
                  emit Claimed(msg.sender, amount, amountToLock);
              }
              /*//////////////////////////////////////////////////////////////
                                           VIEWS
              //////////////////////////////////////////////////////////////*/
              /// @inheritdoc ICumulativeMerkleDrop
              function verifyProof(bytes32[] calldata proof, uint256 amount, address addr) public view returns (bool) {
                  bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(addr, amount))));
                  return MerkleProof.verify(proof, merkleRoot, leaf);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.0 (token/ERC20/IERC20.sol)
          pragma solidity ^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
          // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)
          pragma solidity ^0.8.20;
          import {Ownable} from "./Ownable.sol";
          /**
           * @dev Contract module which provides access control mechanism, where
           * there is an account (an owner) that can be granted exclusive access to
           * specific functions.
           *
           * The initial owner is specified at deployment time in the constructor for `Ownable`. This
           * can later be changed with {transferOwnership} and {acceptOwnership}.
           *
           * This module is used through inheritance. It will make available all functions
           * from parent (Ownable).
           */
          abstract contract Ownable2Step is Ownable {
              address private _pendingOwner;
              event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
              /**
               * @dev Returns the address of the pending owner.
               */
              function pendingOwner() public view virtual returns (address) {
                  return _pendingOwner;
              }
              /**
               * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
               * Can only be called by the current owner.
               */
              function transferOwnership(address newOwner) public virtual override onlyOwner {
                  _pendingOwner = newOwner;
                  emit OwnershipTransferStarted(owner(), newOwner);
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
               * Internal function without access restriction.
               */
              function _transferOwnership(address newOwner) internal virtual override {
                  delete _pendingOwner;
                  super._transferOwnership(newOwner);
              }
              /**
               * @dev The new owner accepts the ownership transfer.
               */
              function acceptOwnership() public virtual {
                  address sender = _msgSender();
                  if (pendingOwner() != sender) {
                      revert OwnableUnauthorizedAccount(sender);
                  }
                  _transferOwnership(sender);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MerkleProof.sol)
          pragma solidity ^0.8.20;
          /**
           * @dev These functions deal with verification of Merkle Tree proofs.
           *
           * The tree and the proofs can be generated using our
           * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
           * You will find a quickstart guide in the readme.
           *
           * WARNING: You should avoid using leaf values that are 64 bytes long prior to
           * hashing, or use a hash function other than keccak256 for hashing leaves.
           * This is because the concatenation of a sorted pair of internal nodes in
           * the Merkle tree could be reinterpreted as a leaf value.
           * OpenZeppelin's JavaScript library generates Merkle trees that are safe
           * against this attack out of the box.
           */
          library MerkleProof {
              /**
               *@dev The multiproof provided is not valid.
               */
              error MerkleProofInvalidMultiproof();
              /**
               * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
               * defined by `root`. For this, a `proof` must be provided, containing
               * sibling hashes on the branch from the leaf to the root of the tree. Each
               * pair of leaves and each pair of pre-images are assumed to be sorted.
               */
              function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
                  return processProof(proof, leaf) == root;
              }
              /**
               * @dev Calldata version of {verify}
               */
              function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
                  return processProofCalldata(proof, leaf) == root;
              }
              /**
               * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
               * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
               * hash matches the root of the tree. When processing the proof, the pairs
               * of leafs & pre-images are assumed to be sorted.
               */
              function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
                  bytes32 computedHash = leaf;
                  for (uint256 i = 0; i < proof.length; i++) {
                      computedHash = _hashPair(computedHash, proof[i]);
                  }
                  return computedHash;
              }
              /**
               * @dev Calldata version of {processProof}
               */
              function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
                  bytes32 computedHash = leaf;
                  for (uint256 i = 0; i < proof.length; i++) {
                      computedHash = _hashPair(computedHash, proof[i]);
                  }
                  return computedHash;
              }
              /**
               * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
               * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
               *
               * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
               */
              function multiProofVerify(
                  bytes32[] memory proof,
                  bool[] memory proofFlags,
                  bytes32 root,
                  bytes32[] memory leaves
              ) internal pure returns (bool) {
                  return processMultiProof(proof, proofFlags, leaves) == root;
              }
              /**
               * @dev Calldata version of {multiProofVerify}
               *
               * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
               */
              function multiProofVerifyCalldata(
                  bytes32[] calldata proof,
                  bool[] calldata proofFlags,
                  bytes32 root,
                  bytes32[] memory leaves
              ) internal pure returns (bool) {
                  return processMultiProofCalldata(proof, proofFlags, leaves) == root;
              }
              /**
               * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
               * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
               * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
               * respectively.
               *
               * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
               * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
               * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
               */
              function processMultiProof(
                  bytes32[] memory proof,
                  bool[] memory proofFlags,
                  bytes32[] memory leaves
              ) internal pure returns (bytes32 merkleRoot) {
                  // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
                  // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
                  // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
                  // the Merkle tree.
                  uint256 leavesLen = leaves.length;
                  uint256 proofLen = proof.length;
                  uint256 totalHashes = proofFlags.length;
                  // Check proof validity.
                  if (leavesLen + proofLen != totalHashes + 1) {
                      revert MerkleProofInvalidMultiproof();
                  }
                  // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
                  // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
                  bytes32[] memory hashes = new bytes32[](totalHashes);
                  uint256 leafPos = 0;
                  uint256 hashPos = 0;
                  uint256 proofPos = 0;
                  // At each step, we compute the next hash using two values:
                  // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
                  //   get the next hash.
                  // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
                  //   `proof` array.
                  for (uint256 i = 0; i < totalHashes; i++) {
                      bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
                      bytes32 b = proofFlags[i]
                          ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                          : proof[proofPos++];
                      hashes[i] = _hashPair(a, b);
                  }
                  if (totalHashes > 0) {
                      if (proofPos != proofLen) {
                          revert MerkleProofInvalidMultiproof();
                      }
                      unchecked {
                          return hashes[totalHashes - 1];
                      }
                  } else if (leavesLen > 0) {
                      return leaves[0];
                  } else {
                      return proof[0];
                  }
              }
              /**
               * @dev Calldata version of {processMultiProof}.
               *
               * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
               */
              function processMultiProofCalldata(
                  bytes32[] calldata proof,
                  bool[] calldata proofFlags,
                  bytes32[] memory leaves
              ) internal pure returns (bytes32 merkleRoot) {
                  // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
                  // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
                  // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
                  // the Merkle tree.
                  uint256 leavesLen = leaves.length;
                  uint256 proofLen = proof.length;
                  uint256 totalHashes = proofFlags.length;
                  // Check proof validity.
                  if (leavesLen + proofLen != totalHashes + 1) {
                      revert MerkleProofInvalidMultiproof();
                  }
                  // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
                  // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
                  bytes32[] memory hashes = new bytes32[](totalHashes);
                  uint256 leafPos = 0;
                  uint256 hashPos = 0;
                  uint256 proofPos = 0;
                  // At each step, we compute the next hash using two values:
                  // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
                  //   get the next hash.
                  // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
                  //   `proof` array.
                  for (uint256 i = 0; i < totalHashes; i++) {
                      bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
                      bytes32 b = proofFlags[i]
                          ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                          : proof[proofPos++];
                      hashes[i] = _hashPair(a, b);
                  }
                  if (totalHashes > 0) {
                      if (proofPos != proofLen) {
                          revert MerkleProofInvalidMultiproof();
                      }
                      unchecked {
                          return hashes[totalHashes - 1];
                      }
                  } else if (leavesLen > 0) {
                      return leaves[0];
                  } else {
                      return proof[0];
                  }
              }
              /**
               * @dev Sorts the pair (a, b) and hashes the result.
               */
              function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
                  return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
              }
              /**
               * @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
               */
              function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
                  /// @solidity memory-safe-assembly
                  assembly {
                      mstore(0x00, a)
                      mstore(0x20, b)
                      value := keccak256(0x00, 0x40)
                  }
              }
          }
          // SPDX-License-Identifier: UNLICENSED
          pragma solidity 0.8.23;
          import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          interface ILock {
              /// @notice locks the token in the staking contract.
              /// @param _account The account address to lock for.
              /// @param _amount The amount of token to lock.
              function lock(address _account, uint256 _amount) external;
              /// @notice Get the staking token address.
              /// @return The staking token address.
              function token() external view returns (IERC20);
          }
          // SPDX-License-Identifier: UNLICENSED
          pragma solidity 0.8.23;
          import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import {ILock} from "./ILock.sol";
          interface ICumulativeMerkleDrop {
              /// @notice error emitted when address is null.
              error ADDRESS_NULL();
              /// @notice error emitted when claim is closed.
              error CLAIM_CLOSED();
              /// @notice error emitted when amount to lock is greater than claimable amount.
              error AMOUNT_TO_LOCK_GT_AMOUNT_CLAIMED();
              /// @notice error emitted when submited proof is invalid.
              error INVALID_PROOF();
              /// @notice error emitted when claim status is invalid.
              error INVALID_STATUS();
              /// @notice error emitted when nothing to claim.
              error NOTHING_TO_CLAIM();
              /// @notice error emitted when an admin tries to update the merkle root with the same value.
              error SAME_MERKLE_ROOT();
              /// @notice error emitted when an admin tries to update the staking contract to the same address.
              error SAME_STAKING_CONTRACT();
              /// @notice error emitted when the provided staking contract token address does not match the drop token address.
              error STAKING_TOKEN_MISMATCH();
              /// @notice error emitted when staking is set to the zero address and the user attempts to lock funds.
              error STAKING_NOT_AVAILABLE();
              /// @notice event emitted when claim is made.
              /// @param account The account that made the claim.
              /// @param amount The amount of token claimed.
              /// @param amountToLock The amount of token locked.
              event Claimed(address indexed account, uint256 amount, uint256 amountToLock);
              /// @notice event emitted when claim status is updated.
              /// @param oldStatus The old status of the claim.
              /// @param newStatus The new status of the claim.
              event ClaimStatusUpdated(uint8 oldStatus, uint8 newStatus);
              /// @notice event emitted when Merkle root is updated.
              /// @param oldMerkleRoot The old Merkle root.
              /// @param newMerkleRoot The new Merkle root.
              event MerkleRootUpdated(bytes32 oldMerkleRoot, bytes32 newMerkleRoot);
              /// @notice event emitted when stakingContract contract is updated.
              /// @param oldStakingContract The old stakingContract contract address.
              /// @param newStakingContract The new stakingContract contract address.
              event StakingContractUpdated(address oldStakingContract, address newStakingContract);
              /// @notice event emitted when stakingContract contract is cleared.
              event StakingContractCleared();
              /// @notice Claim and lock token.
              /// @param cumulativeAmount The cumulative amount of token claimed.
              /// @param amountToLock The amount of token to lock.
              /// @param merkleProof The merkle proof.
              /// @notice It is only possible to lock if there is a staking contract set.
              function claimAndLock(uint256 cumulativeAmount, uint256 amountToLock, bytes32[] memory merkleProof) external;
              /// @notice Get the status of the claim.
              /// @return The status of the claim, 1 for open, 2 for closed.
              function claimIsOpen() external view returns (uint8);
              /// @notice Get the cumulative claimed amount of an account.
              /// @return The cumulative claimed amount of an account.
              function cumulativeClaimed(address) external view returns (uint256);
              /// @notice Get the current Merkle root.
              /// @return The current Merkle root.
              function merkleRoot() external view returns (bytes32);
              /// @notice Set the status of the claim.
              /// @param status The status of the claim, 1 for open, 2 for closed.
              function setClaimStatus(uint8 status) external;
              /// @notice Set the Merkle root.
              /// @param _merkleRoot The new Merkle root.
              function setMerkleRoot(bytes32 _merkleRoot) external;
              /// @notice Set the staking contract address.
              /// @param _stakingContract The staking contract address.
              function setStakingContract(address _stakingContract) external;
              /// @notice Clear the staking contract address.
              /// @notice After calling, it is not possible to lock funds until a new staking contract is set.
              function clearStakingContract() external;
              /// @notice Get the staking contract address.
              /// @return The staking contract address.
              function stakingContract() external view returns (ILock);
              /// @notice Get the token address.
              /// @return The token address.
              function token() external view returns (IERC20);
              /// @notice Verify the merkle proof.
              /// @param proof The merkle proof.
              /// @param amount The amount of token claimed.
              /// @param addr The address of the claimer.
              /// @return True if the proof is valid, false otherwise.
              function verifyProof(bytes32[] memory proof, uint256 amount, address addr) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
          pragma solidity ^0.8.20;
          import {Context} from "../utils/Context.sol";
          /**
           * @dev Contract module which provides a basic access control mechanism, where
           * there is an account (an owner) that can be granted exclusive access to
           * specific functions.
           *
           * The initial owner is set to the address provided by the deployer. This can
           * later be changed with {transferOwnership}.
           *
           * This module is used through inheritance. It will make available the modifier
           * `onlyOwner`, which can be applied to your functions to restrict their use to
           * the owner.
           */
          abstract contract Ownable is Context {
              address private _owner;
              /**
               * @dev The caller account is not authorized to perform an operation.
               */
              error OwnableUnauthorizedAccount(address account);
              /**
               * @dev The owner is not a valid owner account. (eg. `address(0)`)
               */
              error OwnableInvalidOwner(address owner);
              event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
              /**
               * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
               */
              constructor(address initialOwner) {
                  if (initialOwner == address(0)) {
                      revert OwnableInvalidOwner(address(0));
                  }
                  _transferOwnership(initialOwner);
              }
              /**
               * @dev Throws if called by any account other than the owner.
               */
              modifier onlyOwner() {
                  _checkOwner();
                  _;
              }
              /**
               * @dev Returns the address of the current owner.
               */
              function owner() public view virtual returns (address) {
                  return _owner;
              }
              /**
               * @dev Throws if the sender is not the owner.
               */
              function _checkOwner() internal view virtual {
                  if (owner() != _msgSender()) {
                      revert OwnableUnauthorizedAccount(_msgSender());
                  }
              }
              /**
               * @dev Leaves the contract without owner. It will not be possible to call
               * `onlyOwner` functions. Can only be called by the current owner.
               *
               * NOTE: Renouncing ownership will leave the contract without an owner,
               * thereby disabling any functionality that is only available to the owner.
               */
              function renounceOwnership() public virtual onlyOwner {
                  _transferOwnership(address(0));
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Can only be called by the current owner.
               */
              function transferOwnership(address newOwner) public virtual onlyOwner {
                  if (newOwner == address(0)) {
                      revert OwnableInvalidOwner(address(0));
                  }
                  _transferOwnership(newOwner);
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Internal function without access restriction.
               */
              function _transferOwnership(address newOwner) internal virtual {
                  address oldOwner = _owner;
                  _owner = newOwner;
                  emit OwnershipTransferred(oldOwner, newOwner);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
          pragma solidity ^0.8.20;
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with meta-transactions the account sending and
           * paying for execution may not be the actual sender (as far as an application
           * is concerned).
           *
           * This contract is only required for intermediate, library-like contracts.
           */
          abstract contract Context {
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
              function _contextSuffixLength() internal view virtual returns (uint256) {
                  return 0;
              }
          }
          

          File 2 of 5: VaultAdapter
          // SPDX-License-Identifier: UNLICENSED
          pragma solidity 0.8.23;
          import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import {ILock} from "./interfaces/ILock.sol";
          import {ICumulativeMerkleDrop} from "./interfaces/ICumulativeMerkleDrop.sol";
          import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
          interface IVault {
              function deposit(uint256 assets, address receiver) external;
          }
          /// Implementation of ILock that deposits tokens into a vault.
          contract VaultAdapter is ILock {
              using SafeERC20 for IERC20;
              IERC20 public token;
              IVault public vault;
              
              constructor(address _token, address _vault) {
                  token = IERC20(_token);
                  vault = IVault(_vault);
                  IERC20(_token).approve(_vault, type(uint256).max);
              }
              
              function lock(address _account, uint256 _amount) external override {
                  token.safeTransferFrom(msg.sender, address(this), _amount);
                  IVault(address(vault)).deposit(_amount, _account);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.0 (token/ERC20/IERC20.sol)
          pragma solidity ^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: UNLICENSED
          pragma solidity 0.8.23;
          import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          interface ILock {
              /// @notice locks the token in the staking contract.
              /// @param _account The account address to lock for.
              /// @param _amount The amount of token to lock.
              function lock(address _account, uint256 _amount) external;
              /// @notice Get the staking token address.
              /// @return The staking token address.
              function token() external view returns (IERC20);
          }
          // SPDX-License-Identifier: UNLICENSED
          pragma solidity 0.8.23;
          import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import {ILock} from "./ILock.sol";
          interface ICumulativeMerkleDrop {
              /// @notice error emitted when address is null.
              error ADDRESS_NULL();
              /// @notice error emitted when claim is closed.
              error CLAIM_CLOSED();
              /// @notice error emitted when amount to lock is greater than claimable amount.
              error AMOUNT_TO_LOCK_GT_AMOUNT_CLAIMED();
              /// @notice error emitted when submited proof is invalid.
              error INVALID_PROOF();
              /// @notice error emitted when claim status is invalid.
              error INVALID_STATUS();
              /// @notice error emitted when nothing to claim.
              error NOTHING_TO_CLAIM();
              /// @notice error emitted when an admin tries to update the merkle root with the same value.
              error SAME_MERKLE_ROOT();
              /// @notice error emitted when an admin tries to update the staking contract to the same address.
              error SAME_STAKING_CONTRACT();
              /// @notice error emitted when the provided staking contract token address does not match the drop token address.
              error STAKING_TOKEN_MISMATCH();
              /// @notice error emitted when staking is set to the zero address and the user attempts to lock funds.
              error STAKING_NOT_AVAILABLE();
              /// @notice event emitted when claim is made.
              /// @param account The account that made the claim.
              /// @param amount The amount of token claimed.
              /// @param amountToLock The amount of token locked.
              event Claimed(address indexed account, uint256 amount, uint256 amountToLock);
              /// @notice event emitted when claim status is updated.
              /// @param oldStatus The old status of the claim.
              /// @param newStatus The new status of the claim.
              event ClaimStatusUpdated(uint8 oldStatus, uint8 newStatus);
              /// @notice event emitted when Merkle root is updated.
              /// @param oldMerkleRoot The old Merkle root.
              /// @param newMerkleRoot The new Merkle root.
              event MerkleRootUpdated(bytes32 oldMerkleRoot, bytes32 newMerkleRoot);
              /// @notice event emitted when stakingContract contract is updated.
              /// @param oldStakingContract The old stakingContract contract address.
              /// @param newStakingContract The new stakingContract contract address.
              event StakingContractUpdated(address oldStakingContract, address newStakingContract);
              /// @notice event emitted when stakingContract contract is cleared.
              event StakingContractCleared();
              /// @notice Claim and lock token.
              /// @param cumulativeAmount The cumulative amount of token claimed.
              /// @param amountToLock The amount of token to lock.
              /// @param merkleProof The merkle proof.
              /// @notice It is only possible to lock if there is a staking contract set.
              function claimAndLock(uint256 cumulativeAmount, uint256 amountToLock, bytes32[] memory merkleProof) external;
              /// @notice Get the status of the claim.
              /// @return The status of the claim, 1 for open, 2 for closed.
              function claimIsOpen() external view returns (uint8);
              /// @notice Get the cumulative claimed amount of an account.
              /// @return The cumulative claimed amount of an account.
              function cumulativeClaimed(address) external view returns (uint256);
              /// @notice Get the current Merkle root.
              /// @return The current Merkle root.
              function merkleRoot() external view returns (bytes32);
              /// @notice Set the status of the claim.
              /// @param status The status of the claim, 1 for open, 2 for closed.
              function setClaimStatus(uint8 status) external;
              /// @notice Set the Merkle root.
              /// @param _merkleRoot The new Merkle root.
              function setMerkleRoot(bytes32 _merkleRoot) external;
              /// @notice Set the staking contract address.
              /// @param _stakingContract The staking contract address.
              function setStakingContract(address _stakingContract) external;
              /// @notice Clear the staking contract address.
              /// @notice After calling, it is not possible to lock funds until a new staking contract is set.
              function clearStakingContract() external;
              /// @notice Get the staking contract address.
              /// @return The staking contract address.
              function stakingContract() external view returns (ILock);
              /// @notice Get the token address.
              /// @return The token address.
              function token() external view returns (IERC20);
              /// @notice Verify the merkle proof.
              /// @param proof The merkle proof.
              /// @param amount The amount of token claimed.
              /// @param addr The address of the claimer.
              /// @return True if the proof is valid, false otherwise.
              function verifyProof(bytes32[] memory proof, uint256 amount, address addr) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.0 (token/ERC20/utils/SafeERC20.sol)
          pragma solidity ^0.8.0;
          import "../IERC20.sol";
          import "../../../utils/Address.sol";
          /**
           * @title SafeERC20
           * @dev Wrappers around ERC20 operations that throw on failure (when the token
           * contract returns false). Tokens that return no value (and instead revert or
           * throw on failure) are also supported, non-reverting calls are assumed to be
           * successful.
           * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
           * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
           */
          library SafeERC20 {
              using Address for address;
              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'
                  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) + value;
                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
              }
              function safeDecreaseAllowance(
                  IERC20 token,
                  address spender,
                  uint256 value
              ) internal {
                  unchecked {
                      uint256 oldAllowance = token.allowance(address(this), spender);
                      require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                      uint256 newAllowance = oldAllowance - value;
                      _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
                      require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.0 (utils/Address.sol)
          pragma solidity ^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;
                  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");
                  (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");
                  (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");
                  (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");
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal pure returns (bytes memory) {
                  if (success) {
                      return returndata;
                  } else {
                      // 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
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }
          

          File 3 of 5: SwellToken
          // SPDX-License-Identifier: UNLICENSED
          pragma solidity 0.8.23;
          import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
          /// SWELL ERC20 contract
          contract SwellToken is ERC20 {
              constructor(address _receiver, uint256 _totalSupply) ERC20("Swell Governance Token", "SWELL") {
                  _mint(_receiver, _totalSupply);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.0 (token/ERC20/ERC20.sol)
          pragma solidity ^0.8.0;
          import "./IERC20.sol";
          import "./extensions/IERC20Metadata.sol";
          import "../../utils/Context.sol";
          /**
           * @dev Implementation of the {IERC20} interface.
           *
           * This implementation is agnostic to the way tokens are created. This means
           * that a supply mechanism has to be added in a derived contract using {_mint}.
           * For a generic mechanism see {ERC20PresetMinterPauser}.
           *
           * TIP: For a detailed writeup see our guide
           * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
           * to implement supply mechanisms].
           *
           * We have followed general OpenZeppelin Contracts guidelines: functions revert
           * instead returning `false` on failure. This behavior is nonetheless
           * conventional and does not conflict with the expectations of ERC20
           * applications.
           *
           * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
           * This allows applications to reconstruct the allowance for all accounts just
           * by listening to said events. Other implementations of the EIP may not emit
           * these events, as it isn't required by the specification.
           *
           * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
           * functions have been added to mitigate the well-known issues around setting
           * allowances. See {IERC20-approve}.
           */
          contract ERC20 is Context, IERC20, IERC20Metadata {
              mapping(address => uint256) private _balances;
              mapping(address => mapping(address => uint256)) private _allowances;
              uint256 private _totalSupply;
              string private _name;
              string private _symbol;
              /**
               * @dev Sets the values for {name} and {symbol}.
               *
               * The default value of {decimals} is 18. To select a different value for
               * {decimals} you should overload it.
               *
               * All two of these values are immutable: they can only be set once during
               * construction.
               */
              constructor(string memory name_, string memory symbol_) {
                  _name = name_;
                  _symbol = symbol_;
              }
              /**
               * @dev Returns the name of the token.
               */
              function name() public view virtual override returns (string memory) {
                  return _name;
              }
              /**
               * @dev Returns the symbol of the token, usually a shorter version of the
               * name.
               */
              function symbol() public view virtual override returns (string memory) {
                  return _symbol;
              }
              /**
               * @dev Returns the number of decimals used to get its user representation.
               * For example, if `decimals` equals `2`, a balance of `505` tokens should
               * be displayed to a user as `5.05` (`505 / 10 ** 2`).
               *
               * Tokens usually opt for a value of 18, imitating the relationship between
               * Ether and Wei. This is the value {ERC20} uses, unless this function is
               * overridden;
               *
               * NOTE: This information is only used for _display_ purposes: it in
               * no way affects any of the arithmetic of the contract, including
               * {IERC20-balanceOf} and {IERC20-transfer}.
               */
              function decimals() public view virtual override returns (uint8) {
                  return 18;
              }
              /**
               * @dev See {IERC20-totalSupply}.
               */
              function totalSupply() public view virtual override returns (uint256) {
                  return _totalSupply;
              }
              /**
               * @dev See {IERC20-balanceOf}.
               */
              function balanceOf(address account) public view virtual override returns (uint256) {
                  return _balances[account];
              }
              /**
               * @dev See {IERC20-transfer}.
               *
               * Requirements:
               *
               * - `recipient` cannot be the zero address.
               * - the caller must have a balance of at least `amount`.
               */
              function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
                  _transfer(_msgSender(), recipient, amount);
                  return true;
              }
              /**
               * @dev See {IERC20-allowance}.
               */
              function allowance(address owner, address spender) public view virtual override returns (uint256) {
                  return _allowances[owner][spender];
              }
              /**
               * @dev See {IERC20-approve}.
               *
               * Requirements:
               *
               * - `spender` cannot be the zero address.
               */
              function approve(address spender, uint256 amount) public virtual override returns (bool) {
                  _approve(_msgSender(), spender, amount);
                  return true;
              }
              /**
               * @dev See {IERC20-transferFrom}.
               *
               * Emits an {Approval} event indicating the updated allowance. This is not
               * required by the EIP. See the note at the beginning of {ERC20}.
               *
               * Requirements:
               *
               * - `sender` and `recipient` cannot be the zero address.
               * - `sender` must have a balance of at least `amount`.
               * - the caller must have allowance for ``sender``'s tokens of at least
               * `amount`.
               */
              function transferFrom(
                  address sender,
                  address recipient,
                  uint256 amount
              ) public virtual override returns (bool) {
                  _transfer(sender, recipient, amount);
                  uint256 currentAllowance = _allowances[sender][_msgSender()];
                  require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
                  unchecked {
                      _approve(sender, _msgSender(), currentAllowance - amount);
                  }
                  return true;
              }
              /**
               * @dev Atomically increases the allowance granted to `spender` by the caller.
               *
               * This is an alternative to {approve} that can be used as a mitigation for
               * problems described in {IERC20-approve}.
               *
               * Emits an {Approval} event indicating the updated allowance.
               *
               * Requirements:
               *
               * - `spender` cannot be the zero address.
               */
              function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
                  _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
                  return true;
              }
              /**
               * @dev Atomically decreases the allowance granted to `spender` by the caller.
               *
               * This is an alternative to {approve} that can be used as a mitigation for
               * problems described in {IERC20-approve}.
               *
               * Emits an {Approval} event indicating the updated allowance.
               *
               * Requirements:
               *
               * - `spender` cannot be the zero address.
               * - `spender` must have allowance for the caller of at least
               * `subtractedValue`.
               */
              function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
                  uint256 currentAllowance = _allowances[_msgSender()][spender];
                  require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
                  unchecked {
                      _approve(_msgSender(), spender, currentAllowance - subtractedValue);
                  }
                  return true;
              }
              /**
               * @dev Moves `amount` of tokens from `sender` to `recipient`.
               *
               * This internal function is equivalent to {transfer}, and can be used to
               * e.g. implement automatic token fees, slashing mechanisms, etc.
               *
               * Emits a {Transfer} event.
               *
               * Requirements:
               *
               * - `sender` cannot be the zero address.
               * - `recipient` cannot be the zero address.
               * - `sender` must have a balance of at least `amount`.
               */
              function _transfer(
                  address sender,
                  address recipient,
                  uint256 amount
              ) internal virtual {
                  require(sender != address(0), "ERC20: transfer from the zero address");
                  require(recipient != address(0), "ERC20: transfer to the zero address");
                  _beforeTokenTransfer(sender, recipient, amount);
                  uint256 senderBalance = _balances[sender];
                  require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
                  unchecked {
                      _balances[sender] = senderBalance - amount;
                  }
                  _balances[recipient] += amount;
                  emit Transfer(sender, recipient, amount);
                  _afterTokenTransfer(sender, recipient, amount);
              }
              /** @dev Creates `amount` tokens and assigns them to `account`, increasing
               * the total supply.
               *
               * Emits a {Transfer} event with `from` set to the zero address.
               *
               * Requirements:
               *
               * - `account` cannot be the zero address.
               */
              function _mint(address account, uint256 amount) internal virtual {
                  require(account != address(0), "ERC20: mint to the zero address");
                  _beforeTokenTransfer(address(0), account, amount);
                  _totalSupply += amount;
                  _balances[account] += amount;
                  emit Transfer(address(0), account, amount);
                  _afterTokenTransfer(address(0), account, amount);
              }
              /**
               * @dev Destroys `amount` tokens from `account`, reducing the
               * total supply.
               *
               * Emits a {Transfer} event with `to` set to the zero address.
               *
               * Requirements:
               *
               * - `account` cannot be the zero address.
               * - `account` must have at least `amount` tokens.
               */
              function _burn(address account, uint256 amount) internal virtual {
                  require(account != address(0), "ERC20: burn from the zero address");
                  _beforeTokenTransfer(account, address(0), amount);
                  uint256 accountBalance = _balances[account];
                  require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
                  unchecked {
                      _balances[account] = accountBalance - amount;
                  }
                  _totalSupply -= amount;
                  emit Transfer(account, address(0), amount);
                  _afterTokenTransfer(account, address(0), amount);
              }
              /**
               * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
               *
               * This internal function is equivalent to `approve`, and can be used to
               * e.g. set automatic allowances for certain subsystems, etc.
               *
               * Emits an {Approval} event.
               *
               * Requirements:
               *
               * - `owner` cannot be the zero address.
               * - `spender` cannot be the zero address.
               */
              function _approve(
                  address owner,
                  address spender,
                  uint256 amount
              ) internal virtual {
                  require(owner != address(0), "ERC20: approve from the zero address");
                  require(spender != address(0), "ERC20: approve to the zero address");
                  _allowances[owner][spender] = amount;
                  emit Approval(owner, spender, amount);
              }
              /**
               * @dev Hook that is called before any transfer of tokens. This includes
               * minting and burning.
               *
               * Calling conditions:
               *
               * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
               * will be transferred to `to`.
               * - when `from` is zero, `amount` tokens will be minted for `to`.
               * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
               * - `from` and `to` are never both zero.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _beforeTokenTransfer(
                  address from,
                  address to,
                  uint256 amount
              ) internal virtual {}
              /**
               * @dev Hook that is called after any transfer of tokens. This includes
               * minting and burning.
               *
               * Calling conditions:
               *
               * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
               * has been transferred to `to`.
               * - when `from` is zero, `amount` tokens have been minted for `to`.
               * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
               * - `from` and `to` are never both zero.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _afterTokenTransfer(
                  address from,
                  address to,
                  uint256 amount
              ) internal virtual {}
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.0 (token/ERC20/IERC20.sol)
          pragma solidity ^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
          // OpenZeppelin Contracts v4.4.0 (token/ERC20/extensions/IERC20Metadata.sol)
          pragma solidity ^0.8.0;
          import "../IERC20.sol";
          /**
           * @dev Interface for the optional metadata functions from the ERC20 standard.
           *
           * _Available since v4.1._
           */
          interface IERC20Metadata is IERC20 {
              /**
               * @dev Returns the name of the token.
               */
              function name() external view returns (string memory);
              /**
               * @dev Returns the symbol of the token.
               */
              function symbol() external view returns (string memory);
              /**
               * @dev Returns the decimals places of the token.
               */
              function decimals() external view returns (uint8);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.0 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with meta-transactions the account sending and
           * paying for execution may not be the actual sender (as far as an application
           * is concerned).
           *
           * This contract is only required for intermediate, library-like contracts.
           */
          abstract contract Context {
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
          }
          

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

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