From 35e373d5d868f422a4435aa25e63b601532fc042 Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 9 Oct 2024 11:06:49 +0200 Subject: [PATCH] Revert "chore: downgrade vault (#18)" This reverts commit 92b278d5885088423381ffb986ec45f4a8579a5f. --- .github/workflows/ci.yaml | 7 - .pre-commit-config.yaml | 2 +- contracts/RewardsHandler.vy | 3 +- contracts/interfaces/IVault.vyi | 5 - contracts/yearn/BaseStrategy.sol | 1489 ----------- contracts/yearn/DummyStrategy.sol | 18 - contracts/yearn/TokenizedStrategy.sol | 3378 ------------------------- contracts/yearn/Vault.vy | 225 +- contracts/yearn/VaultFactory.vy | 65 +- poetry.lock | 296 +-- pyproject.toml | 4 +- tests/unitary/conftest.py | 40 +- 12 files changed, 312 insertions(+), 5220 deletions(-) delete mode 100644 contracts/yearn/BaseStrategy.sol delete mode 100644 contracts/yearn/DummyStrategy.sol delete mode 100644 contracts/yearn/TokenizedStrategy.sol diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 77c551e..0554bb2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,13 +30,6 @@ jobs: steps: - uses: actions/checkout@v3 - # Install Solidity compiler (solc) - - name: Install solc - run: | - sudo add-apt-repository -y ppa:ethereum/ethereum - sudo apt-get update - sudo apt-get install -y solc - # install poetry to allow cacheing - name: Install poetry run: pipx install poetry diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 99af589..a755f12 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,4 +25,4 @@ repos: args: ["--profile", "black", --line-length=100] default_language_version: - python: python3.12 + python: python3.12.6 diff --git a/contracts/RewardsHandler.vy b/contracts/RewardsHandler.vy index 967cba2..244d36f 100644 --- a/contracts/RewardsHandler.vy +++ b/contracts/RewardsHandler.vy @@ -298,7 +298,8 @@ def set_distribution_time(new_distribution_time: uint256): extcall vault.setProfitMaxUnlockTime(new_distribution_time) # enact the changes - extcall vault.process_report(staticcall vault.default_queue(0)) + extcall vault.process_report(vault.address) + @external def set_minimum_weight(new_minimum_weight: uint256): diff --git a/contracts/interfaces/IVault.vyi b/contracts/interfaces/IVault.vyi index b3b1864..2950660 100644 --- a/contracts/interfaces/IVault.vyi +++ b/contracts/interfaces/IVault.vyi @@ -9,8 +9,3 @@ def setProfitMaxUnlockTime(new_profit_max_unlock_time: uint256): @external def process_report(strategy: address) -> (uint256, uint256): ... - -@view -@external -def default_queue(index: uint256) -> address: - ... diff --git a/contracts/yearn/BaseStrategy.sol b/contracts/yearn/BaseStrategy.sol deleted file mode 100644 index 8a80d76..0000000 --- a/contracts/yearn/BaseStrategy.sol +++ /dev/null @@ -1,1489 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity >=0.8.18 ^0.8.0; - -// lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol - -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) - -/** - * @dev Interface of the ERC20 standard as defined in the EIP. - */ -interface IERC20 { - /** - * @dev Emitted when `value` tokens are moved from one account (`from`) to - * another (`to`). - * - * Note that `value` may be zero. - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @dev Emitted when the allowance of a `spender` for an `owner` is set by - * a call to {approve}. `value` is the new allowance. - */ - event Approval(address indexed owner, address indexed spender, uint256 value); - - /** - * @dev Returns the amount of tokens in existence. - */ - function totalSupply() external view returns (uint256); - - /** - * @dev Returns the amount of tokens owned by `account`. - */ - function balanceOf(address account) external view returns (uint256); - - /** - * @dev Moves `amount` tokens from the caller's account to `to`. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transfer(address to, uint256 amount) external returns (bool); - - /** - * @dev Returns the remaining number of tokens that `spender` will be - * allowed to spend on behalf of `owner` through {transferFrom}. This is - * zero by default. - * - * This value changes when {approve} or {transferFrom} are called. - */ - function allowance(address owner, address spender) external view returns (uint256); - - /** - * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * IMPORTANT: Beware that changing an allowance with this method brings the risk - * that someone may use both the old and the new allowance by unfortunate - * transaction ordering. One possible solution to mitigate this race - * condition is to first reduce the spender's allowance to 0 and set the - * desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * - * Emits an {Approval} event. - */ - function approve(address spender, uint256 amount) external returns (bool); - - /** - * @dev Moves `amount` tokens from `from` to `to` using the - * allowance mechanism. `amount` is then deducted from the caller's - * allowance. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transferFrom(address from, address to, uint256 amount) external returns (bool); -} - -// lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol - -// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol) - -/** - * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in - * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. - * - * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by - * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't - * need to send a transaction, and thus is not required to hold Ether at all. - * - * ==== Security Considerations - * - * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature - * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be - * considered as an intention to spend the allowance in any specific way. The second is that because permits have - * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should - * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be - * generally recommended is: - * - * ```solidity - * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { - * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} - * doThing(..., value); - * } - * - * function doThing(..., uint256 value) public { - * token.safeTransferFrom(msg.sender, address(this), value); - * ... - * } - * ``` - * - * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of - * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also - * {SafeERC20-safeTransferFrom}). - * - * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so - * contracts should have entry points that don't rely on permit. - */ -interface IERC20Permit { - /** - * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, - * given ``owner``'s signed approval. - * - * IMPORTANT: The same issues {IERC20-approve} has related to transaction - * ordering also apply here. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `deadline` must be a timestamp in the future. - * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` - * over the EIP712-formatted function arguments. - * - the signature must use ``owner``'s current nonce (see {nonces}). - * - * For more information on the signature format, see the - * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP - * section]. - * - * CAUTION: See Security Considerations above. - */ - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @dev Returns the current nonce for `owner`. This value must be - * included whenever a signature is generated for {permit}. - * - * Every successful call to {permit} increases ``owner``'s nonce by one. This - * prevents a signature from being used multiple times. - */ - function nonces(address owner) external view returns (uint256); - - /** - * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. - */ - // solhint-disable-next-line func-name-mixedcase - function DOMAIN_SEPARATOR() external view returns (bytes32); -} - -// lib/openzeppelin-contracts/contracts/utils/Context.sol - -// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol) - -/** - * @dev Provides information about the current execution context, including the - * sender of the transaction and its data. While these are generally available - * via msg.sender and msg.data, they should not be accessed in such a direct - * manner, since when dealing with meta-transactions the account sending and - * paying for execution may not be the actual sender (as far as an application - * is concerned). - * - * This contract is only required for intermediate, library-like contracts. - */ -abstract contract Context { - function _msgSender() internal view virtual returns (address) { - return msg.sender; - } - - function _msgData() internal view virtual returns (bytes calldata) { - return msg.data; - } - - function _contextSuffixLength() internal view virtual returns (uint256) { - return 0; - } -} - -// lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol - -// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) - -/** - * @dev Interface for the optional metadata functions from the ERC20 standard. - * - * _Available since v4.1._ - */ -interface IERC20Metadata is IERC20 { - /** - * @dev Returns the name of the token. - */ - function name() external view returns (string memory); - - /** - * @dev Returns the symbol of the token. - */ - function symbol() external view returns (string memory); - - /** - * @dev Returns the decimals places of the token. - */ - function decimals() external view returns (uint8); -} - -// lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol - -// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol) - -/** - * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in - * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. - * - * _Available since v4.7._ - */ -interface IERC4626 is IERC20, IERC20Metadata { - event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); - - event Withdraw( - address indexed sender, - address indexed receiver, - address indexed owner, - uint256 assets, - uint256 shares - ); - - /** - * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. - * - * - MUST be an ERC-20 token contract. - * - MUST NOT revert. - */ - function asset() external view returns (address assetTokenAddress); - - /** - * @dev Returns the total amount of the underlying asset that is “managed” by Vault. - * - * - SHOULD include any compounding that occurs from yield. - * - MUST be inclusive of any fees that are charged against assets in the Vault. - * - MUST NOT revert. - */ - function totalAssets() external view returns (uint256 totalManagedAssets); - - /** - * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal - * scenario where all the conditions are met. - * - * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. - * - MUST NOT show any variations depending on the caller. - * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. - * - MUST NOT revert. - * - * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the - * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and - * from. - */ - function convertToShares(uint256 assets) external view returns (uint256 shares); - - /** - * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal - * scenario where all the conditions are met. - * - * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. - * - MUST NOT show any variations depending on the caller. - * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. - * - MUST NOT revert. - * - * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the - * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and - * from. - */ - function convertToAssets(uint256 shares) external view returns (uint256 assets); - - /** - * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, - * through a deposit call. - * - * - MUST return a limited value if receiver is subject to some deposit limit. - * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. - * - MUST NOT revert. - */ - function maxDeposit(address receiver) external view returns (uint256 maxAssets); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given - * current on-chain conditions. - * - * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit - * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called - * in the same transaction. - * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the - * deposit would be accepted, regardless if the user has enough tokens approved, etc. - * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by depositing. - */ - function previewDeposit(uint256 assets) external view returns (uint256 shares); - - /** - * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. - * - * - MUST emit the Deposit event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the - * deposit execution, and are accounted for during deposit. - * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not - * approving enough underlying tokens to the Vault contract, etc). - * - * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. - */ - function deposit(uint256 assets, address receiver) external returns (uint256 shares); - - /** - * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. - * - MUST return a limited value if receiver is subject to some mint limit. - * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. - * - MUST NOT revert. - */ - function maxMint(address receiver) external view returns (uint256 maxShares); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given - * current on-chain conditions. - * - * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call - * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the - * same transaction. - * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint - * would be accepted, regardless if the user has enough tokens approved, etc. - * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by minting. - */ - function previewMint(uint256 shares) external view returns (uint256 assets); - - /** - * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. - * - * - MUST emit the Deposit event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint - * execution, and are accounted for during mint. - * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not - * approving enough underlying tokens to the Vault contract, etc). - * - * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. - */ - function mint(uint256 shares, address receiver) external returns (uint256 assets); - - /** - * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the - * Vault, through a withdraw call. - * - * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. - * - MUST NOT revert. - */ - function maxWithdraw(address owner) external view returns (uint256 maxAssets); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, - * given current on-chain conditions. - * - * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw - * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if - * called - * in the same transaction. - * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though - * the withdrawal would be accepted, regardless if the user has enough shares, etc. - * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by depositing. - */ - function previewWithdraw(uint256 assets) external view returns (uint256 shares); - - /** - * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. - * - * - MUST emit the Withdraw event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the - * withdraw execution, and are accounted for during withdraw. - * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner - * not having enough shares, etc). - * - * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. - * Those methods should be performed separately. - */ - function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); - - /** - * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, - * through a redeem call. - * - * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. - * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. - * - MUST NOT revert. - */ - function maxRedeem(address owner) external view returns (uint256 maxShares); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, - * given current on-chain conditions. - * - * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call - * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the - * same transaction. - * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the - * redemption would be accepted, regardless if the user has enough shares, etc. - * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by redeeming. - */ - function previewRedeem(uint256 shares) external view returns (uint256 assets); - - /** - * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. - * - * - MUST emit the Withdraw event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the - * redeem execution, and are accounted for during redeem. - * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner - * not having enough shares, etc). - * - * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. - * Those methods should be performed separately. - */ - function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); -} - -// lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol - -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.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.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How - * to implement supply mechanisms]. - * - * The default value of {decimals} is 18. To change this, you should override - * this function so it returns a different value. - * - * 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}. - * - * 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 default value returned by this function, unless - * it's 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: - * - * - `to` cannot be the zero address. - * - the caller must have a balance of at least `amount`. - */ - function transfer(address to, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _transfer(owner, to, 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}. - * - * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on - * `transferFrom`. This is semantically equivalent to an infinite approval. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function approve(address spender, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _approve(owner, 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}. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Requirements: - * - * - `from` and `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - * - the caller must have allowance for ``from``'s tokens of at least - * `amount`. - */ - function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, amount); - _transfer(from, to, 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) { - address owner = _msgSender(); - _approve(owner, spender, allowance(owner, 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) { - address owner = _msgSender(); - uint256 currentAllowance = allowance(owner, spender); - require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); - unchecked { - _approve(owner, spender, currentAllowance - subtractedValue); - } - - return true; - } - - /** - * @dev Moves `amount` of tokens from `from` to `to`. - * - * 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: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - */ - function _transfer(address from, address to, uint256 amount) internal virtual { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); - - _beforeTokenTransfer(from, to, amount); - - uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); - unchecked { - _balances[from] = fromBalance - amount; - // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by - // decrementing then incrementing. - _balances[to] += amount; - } - - emit Transfer(from, to, amount); - - _afterTokenTransfer(from, to, 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; - unchecked { - // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. - _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; - // Overflow not possible: amount <= accountBalance <= totalSupply. - _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 Updates `owner` s allowance for `spender` based on spent `amount`. - * - * Does not update the allowance amount in case of infinite allowance. - * Revert if not enough allowance is available. - * - * Might emit an {Approval} event. - */ - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - require(currentAllowance >= amount, "ERC20: insufficient allowance"); - unchecked { - _approve(owner, spender, currentAllowance - 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 {} -} - -// src/interfaces/ITokenizedStrategy.sol - -// Interface that implements the 4626 standard and the implementation functions -interface ITokenizedStrategy is IERC4626, IERC20Permit { - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - event StrategyShutdown(); - - event NewTokenizedStrategy( - address indexed strategy, - address indexed asset, - string apiVersion - ); - - event Reported( - uint256 profit, - uint256 loss, - uint256 protocolFees, - uint256 performanceFees - ); - - event UpdatePerformanceFeeRecipient( - address indexed newPerformanceFeeRecipient - ); - - event UpdateKeeper(address indexed newKeeper); - - event UpdatePerformanceFee(uint16 newPerformanceFee); - - event UpdateManagement(address indexed newManagement); - - event UpdateEmergencyAdmin(address indexed newEmergencyAdmin); - - event UpdateProfitMaxUnlockTime(uint256 newProfitMaxUnlockTime); - - event UpdatePendingManagement(address indexed newPendingManagement); - - /*////////////////////////////////////////////////////////////// - INITIALIZATION - //////////////////////////////////////////////////////////////*/ - - function initialize( - address _asset, - string memory _name, - address _management, - address _performanceFeeRecipient, - address _keeper - ) external; - - /*////////////////////////////////////////////////////////////// - NON-STANDARD 4626 OPTIONS - //////////////////////////////////////////////////////////////*/ - - function withdraw( - uint256 assets, - address receiver, - address owner, - uint256 maxLoss - ) external returns (uint256); - - function redeem( - uint256 shares, - address receiver, - address owner, - uint256 maxLoss - ) external returns (uint256); - - function maxWithdraw( - address owner, - uint256 /*maxLoss*/ - ) external view returns (uint256); - - function maxRedeem( - address owner, - uint256 /*maxLoss*/ - ) external view returns (uint256); - - /*////////////////////////////////////////////////////////////// - MODIFIER HELPERS - //////////////////////////////////////////////////////////////*/ - - function requireManagement(address _sender) external view; - - function requireKeeperOrManagement(address _sender) external view; - - function requireEmergencyAuthorized(address _sender) external view; - - /*////////////////////////////////////////////////////////////// - KEEPERS FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - function tend() external; - - function report() external returns (uint256 _profit, uint256 _loss); - - /*////////////////////////////////////////////////////////////// - CONSTANTS - //////////////////////////////////////////////////////////////*/ - - function MAX_FEE() external view returns (uint16); - - function FACTORY() external view returns (address); - - /*////////////////////////////////////////////////////////////// - GETTERS - //////////////////////////////////////////////////////////////*/ - - function apiVersion() external view returns (string memory); - - function pricePerShare() external view returns (uint256); - - function management() external view returns (address); - - function pendingManagement() external view returns (address); - - function keeper() external view returns (address); - - function emergencyAdmin() external view returns (address); - - function performanceFee() external view returns (uint16); - - function performanceFeeRecipient() external view returns (address); - - function fullProfitUnlockDate() external view returns (uint256); - - function profitUnlockingRate() external view returns (uint256); - - function profitMaxUnlockTime() external view returns (uint256); - - function lastReport() external view returns (uint256); - - function isShutdown() external view returns (bool); - - function unlockedShares() external view returns (uint256); - - /*////////////////////////////////////////////////////////////// - SETTERS - //////////////////////////////////////////////////////////////*/ - - function setPendingManagement(address) external; - - function acceptManagement() external; - - function setKeeper(address _keeper) external; - - function setEmergencyAdmin(address _emergencyAdmin) external; - - function setPerformanceFee(uint16 _performanceFee) external; - - function setPerformanceFeeRecipient( - address _performanceFeeRecipient - ) external; - - function setProfitMaxUnlockTime(uint256 _profitMaxUnlockTime) external; - - function setName(string calldata _newName) external; - - function shutdownStrategy() external; - - function emergencyWithdraw(uint256 _amount) external; -} - -// src/BaseStrategy.sol - -// TokenizedStrategy interface used for internal view delegateCalls. - -/** - * @title YearnV3 Base Strategy - * @author yearn.finance - * @notice - * BaseStrategy implements all of the required functionality to - * seamlessly integrate with the `TokenizedStrategy` implementation contract - * allowing anyone to easily build a fully permissionless ERC-4626 compliant - * Vault by inheriting this contract and overriding three simple functions. - - * It utilizes an immutable proxy pattern that allows the BaseStrategy - * to remain simple and small. All standard logic is held within the - * `TokenizedStrategy` and is reused over any n strategies all using the - * `fallback` function to delegatecall the implementation so that strategists - * can only be concerned with writing their strategy specific code. - * - * This contract should be inherited and the three main abstract methods - * `_deployFunds`, `_freeFunds` and `_harvestAndReport` implemented to adapt - * the Strategy to the particular needs it has to generate yield. There are - * other optional methods that can be implemented to further customize - * the strategy if desired. - * - * All default storage for the strategy is controlled and updated by the - * `TokenizedStrategy`. The implementation holds a storage struct that - * contains all needed global variables in a manual storage slot. This - * means strategists can feel free to implement their own custom storage - * variables as they need with no concern of collisions. All global variables - * can be viewed within the Strategy by a simple call using the - * `TokenizedStrategy` variable. IE: TokenizedStrategy.globalVariable();. - */ -abstract contract BaseStrategy { - /*////////////////////////////////////////////////////////////// - MODIFIERS - //////////////////////////////////////////////////////////////*/ - /** - * @dev Used on TokenizedStrategy callback functions to make sure it is post - * a delegateCall from this address to the TokenizedStrategy. - */ - modifier onlySelf() { - _onlySelf(); - _; - } - - /** - * @dev Use to assure that the call is coming from the strategies management. - */ - modifier onlyManagement() { - TokenizedStrategy.requireManagement(msg.sender); - _; - } - - /** - * @dev Use to assure that the call is coming from either the strategies - * management or the keeper. - */ - modifier onlyKeepers() { - TokenizedStrategy.requireKeeperOrManagement(msg.sender); - _; - } - - /** - * @dev Use to assure that the call is coming from either the strategies - * management or the emergency admin. - */ - modifier onlyEmergencyAuthorized() { - TokenizedStrategy.requireEmergencyAuthorized(msg.sender); - _; - } - - /** - * @dev Require that the msg.sender is this address. - */ - function _onlySelf() internal view { - require(msg.sender == address(this), "!self"); - } - - /*////////////////////////////////////////////////////////////// - CONSTANTS - //////////////////////////////////////////////////////////////*/ - - /** - * @dev This is the address of the TokenizedStrategy implementation - * contract that will be used by all strategies to handle the - * accounting, logic, storage etc. - * - * Any external calls to the that don't hit one of the functions - * defined in this base or the strategy will end up being forwarded - * through the fallback function, which will delegateCall this address. - * - * This address should be the same for every strategy, never be adjusted - * and always be checked before any integration with the Strategy. - */ - // NOTE: This is a holder address based on expected deterministic location for testing - address public constant tokenizedStrategyAddress = - 0x2e234DAe75C793f67A35089C9d99245E1C58470b; - - /*////////////////////////////////////////////////////////////// - IMMUTABLES - //////////////////////////////////////////////////////////////*/ - - /** - * @dev Underlying asset the Strategy is earning yield on. - * Stored here for cheap retrievals within the strategy. - */ - ERC20 internal immutable asset; - - /** - * @dev This variable is set to address(this) during initialization of each strategy. - * - * This can be used to retrieve storage data within the strategy - * contract as if it were a linked library. - * - * i.e. uint256 totalAssets = TokenizedStrategy.totalAssets() - * - * Using address(this) will mean any calls using this variable will lead - * to a call to itself. Which will hit the fallback function and - * delegateCall that to the actual TokenizedStrategy. - */ - ITokenizedStrategy internal immutable TokenizedStrategy; - - /** - * @notice Used to initialize the strategy on deployment. - * - * This will set the `TokenizedStrategy` variable for easy - * internal view calls to the implementation. As well as - * initializing the default storage variables based on the - * parameters and using the deployer for the permissioned roles. - * - * @param _asset Address of the underlying asset. - * @param _name Name the strategy will use. - */ - constructor(address _asset, string memory _name) { - asset = ERC20(_asset); - - // Set instance of the implementation for internal use. - TokenizedStrategy = ITokenizedStrategy(address(this)); - - // Initialize the strategy's storage variables. - _delegateCall( - abi.encodeCall( - ITokenizedStrategy.initialize, - (_asset, _name, msg.sender, msg.sender, msg.sender) - ) - ); - - // Store the tokenizedStrategyAddress at the standard implementation - // address storage slot so etherscan picks up the interface. This gets - // stored on initialization and never updated. - assembly { - sstore( - // keccak256('eip1967.proxy.implementation' - 1) - 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc, - tokenizedStrategyAddress - ) - } - } - - /*////////////////////////////////////////////////////////////// - NEEDED TO BE OVERRIDDEN BY STRATEGIST - //////////////////////////////////////////////////////////////*/ - - /** - * @dev Can deploy up to '_amount' of 'asset' in the yield source. - * - * This function is called at the end of a {deposit} or {mint} - * call. Meaning that unless a whitelist is implemented it will - * be entirely permissionless and thus can be sandwiched or otherwise - * manipulated. - * - * @param _amount The amount of 'asset' that the strategy can attempt - * to deposit in the yield source. - */ - function _deployFunds(uint256 _amount) internal virtual; - - /** - * @dev Should attempt to free the '_amount' of 'asset'. - * - * NOTE: The amount of 'asset' that is already loose has already - * been accounted for. - * - * This function is called during {withdraw} and {redeem} calls. - * Meaning that unless a whitelist is implemented it will be - * entirely permissionless and thus can be sandwiched or otherwise - * manipulated. - * - * Should not rely on asset.balanceOf(address(this)) calls other than - * for diff accounting purposes. - * - * Any difference between `_amount` and what is actually freed will be - * counted as a loss and passed on to the withdrawer. This means - * care should be taken in times of illiquidity. It may be better to revert - * if withdraws are simply illiquid so not to realize incorrect losses. - * - * @param _amount, The amount of 'asset' to be freed. - */ - function _freeFunds(uint256 _amount) internal virtual; - - /** - * @dev Internal function to harvest all rewards, redeploy any idle - * funds and return an accurate accounting of all funds currently - * held by the Strategy. - * - * This should do any needed harvesting, rewards selling, accrual, - * redepositing etc. to get the most accurate view of current assets. - * - * NOTE: All applicable assets including loose assets should be - * accounted for in this function. - * - * Care should be taken when relying on oracles or swap values rather - * than actual amounts as all Strategy profit/loss accounting will - * be done based on this returned value. - * - * This can still be called post a shutdown, a strategist can check - * `TokenizedStrategy.isShutdown()` to decide if funds should be - * redeployed or simply realize any profits/losses. - * - * @return _totalAssets A trusted and accurate account for the total - * amount of 'asset' the strategy currently holds including idle funds. - */ - function _harvestAndReport() - internal - virtual - returns (uint256 _totalAssets); - - /*////////////////////////////////////////////////////////////// - OPTIONAL TO OVERRIDE BY STRATEGIST - //////////////////////////////////////////////////////////////*/ - - /** - * @dev Optional function for strategist to override that can - * be called in between reports. - * - * If '_tend' is used tendTrigger() will also need to be overridden. - * - * This call can only be called by a permissioned role so may be - * through protected relays. - * - * This can be used to harvest and compound rewards, deposit idle funds, - * perform needed position maintenance or anything else that doesn't need - * a full report for. - * - * EX: A strategy that can not deposit funds without getting - * sandwiched can use the tend when a certain threshold - * of idle to totalAssets has been reached. - * - * This will have no effect on PPS of the strategy till report() is called. - * - * @param _totalIdle The current amount of idle funds that are available to deploy. - */ - function _tend(uint256 _totalIdle) internal virtual {} - - /** - * @dev Optional trigger to override if tend() will be used by the strategy. - * This must be implemented if the strategy hopes to invoke _tend(). - * - * @return . Should return true if tend() should be called by keeper or false if not. - */ - function _tendTrigger() internal view virtual returns (bool) { - return false; - } - - /** - * @notice Returns if tend() should be called by a keeper. - * - * @return . Should return true if tend() should be called by keeper or false if not. - * @return . Calldata for the tend call. - */ - function tendTrigger() external view virtual returns (bool, bytes memory) { - return ( - // Return the status of the tend trigger. - _tendTrigger(), - // And the needed calldata either way. - abi.encodeWithSelector(ITokenizedStrategy.tend.selector) - ); - } - - /** - * @notice Gets the max amount of `asset` that an address can deposit. - * @dev Defaults to an unlimited amount for any address. But can - * be overridden by strategists. - * - * This function will be called before any deposit or mints to enforce - * any limits desired by the strategist. This can be used for either a - * traditional deposit limit or for implementing a whitelist etc. - * - * EX: - * if(isAllowed[_owner]) return super.availableDepositLimit(_owner); - * - * This does not need to take into account any conversion rates - * from shares to assets. But should know that any non max uint256 - * amounts may be converted to shares. So it is recommended to keep - * custom amounts low enough as not to cause overflow when multiplied - * by `totalSupply`. - * - * @param . The address that is depositing into the strategy. - * @return . The available amount the `_owner` can deposit in terms of `asset` - */ - function availableDepositLimit( - address /*_owner*/ - ) public view virtual returns (uint256) { - return type(uint256).max; - } - - /** - * @notice Gets the max amount of `asset` that can be withdrawn. - * @dev Defaults to an unlimited amount for any address. But can - * be overridden by strategists. - * - * This function will be called before any withdraw or redeem to enforce - * any limits desired by the strategist. This can be used for illiquid - * or sandwichable strategies. It should never be lower than `totalIdle`. - * - * EX: - * return TokenIzedStrategy.totalIdle(); - * - * This does not need to take into account the `_owner`'s share balance - * or conversion rates from shares to assets. - * - * @param . The address that is withdrawing from the strategy. - * @return . The available amount that can be withdrawn in terms of `asset` - */ - function availableWithdrawLimit( - address /*_owner*/ - ) public view virtual returns (uint256) { - return type(uint256).max; - } - - /** - * @dev Optional function for a strategist to override that will - * allow management to manually withdraw deployed funds from the - * yield source if a strategy is shutdown. - * - * This should attempt to free `_amount`, noting that `_amount` may - * be more than is currently deployed. - * - * NOTE: This will not realize any profits or losses. A separate - * {report} will be needed in order to record any profit/loss. If - * a report may need to be called after a shutdown it is important - * to check if the strategy is shutdown during {_harvestAndReport} - * so that it does not simply re-deploy all funds that had been freed. - * - * EX: - * if(freeAsset > 0 && !TokenizedStrategy.isShutdown()) { - * depositFunds... - * } - * - * @param _amount The amount of asset to attempt to free. - */ - function _emergencyWithdraw(uint256 _amount) internal virtual {} - - /*////////////////////////////////////////////////////////////// - TokenizedStrategy HOOKS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Can deploy up to '_amount' of 'asset' in yield source. - * @dev Callback for the TokenizedStrategy to call during a {deposit} - * or {mint} to tell the strategy it can deploy funds. - * - * Since this can only be called after a {deposit} or {mint} - * delegateCall to the TokenizedStrategy msg.sender == address(this). - * - * Unless a whitelist is implemented this will be entirely permissionless - * and thus can be sandwiched or otherwise manipulated. - * - * @param _amount The amount of 'asset' that the strategy can - * attempt to deposit in the yield source. - */ - function deployFunds(uint256 _amount) external virtual onlySelf { - _deployFunds(_amount); - } - - /** - * @notice Should attempt to free the '_amount' of 'asset'. - * @dev Callback for the TokenizedStrategy to call during a withdraw - * or redeem to free the needed funds to service the withdraw. - * - * This can only be called after a 'withdraw' or 'redeem' delegateCall - * to the TokenizedStrategy so msg.sender == address(this). - * - * @param _amount The amount of 'asset' that the strategy should attempt to free up. - */ - function freeFunds(uint256 _amount) external virtual onlySelf { - _freeFunds(_amount); - } - - /** - * @notice Returns the accurate amount of all funds currently - * held by the Strategy. - * @dev Callback for the TokenizedStrategy to call during a report to - * get an accurate accounting of assets the strategy controls. - * - * This can only be called after a report() delegateCall to the - * TokenizedStrategy so msg.sender == address(this). - * - * @return . A trusted and accurate account for the total amount - * of 'asset' the strategy currently holds including idle funds. - */ - function harvestAndReport() external virtual onlySelf returns (uint256) { - return _harvestAndReport(); - } - - /** - * @notice Will call the internal '_tend' when a keeper tends the strategy. - * @dev Callback for the TokenizedStrategy to initiate a _tend call in the strategy. - * - * This can only be called after a tend() delegateCall to the TokenizedStrategy - * so msg.sender == address(this). - * - * We name the function `tendThis` so that `tend` calls are forwarded to - * the TokenizedStrategy. - - * @param _totalIdle The amount of current idle funds that can be - * deployed during the tend - */ - function tendThis(uint256 _totalIdle) external virtual onlySelf { - _tend(_totalIdle); - } - - /** - * @notice Will call the internal '_emergencyWithdraw' function. - * @dev Callback for the TokenizedStrategy during an emergency withdraw. - * - * This can only be called after a emergencyWithdraw() delegateCall to - * the TokenizedStrategy so msg.sender == address(this). - * - * We name the function `shutdownWithdraw` so that `emergencyWithdraw` - * calls are forwarded to the TokenizedStrategy. - * - * @param _amount The amount of asset to attempt to free. - */ - function shutdownWithdraw(uint256 _amount) external virtual onlySelf { - _emergencyWithdraw(_amount); - } - - /** - * @dev Function used to delegate call the TokenizedStrategy with - * certain `_calldata` and return any return values. - * - * This is used to setup the initial storage of the strategy, and - * can be used by strategist to forward any other call to the - * TokenizedStrategy implementation. - * - * @param _calldata The abi encoded calldata to use in delegatecall. - * @return . The return value if the call was successful in bytes. - */ - function _delegateCall( - bytes memory _calldata - ) internal returns (bytes memory) { - // Delegate call the tokenized strategy with provided calldata. - (bool success, bytes memory result) = tokenizedStrategyAddress - .delegatecall(_calldata); - - // If the call reverted. Return the error. - if (!success) { - assembly { - let ptr := mload(0x40) - let size := returndatasize() - returndatacopy(ptr, 0, size) - revert(ptr, size) - } - } - - // Return the result. - return result; - } - - /** - * @dev Execute a function on the TokenizedStrategy and return any value. - * - * This fallback function will be executed when any of the standard functions - * defined in the TokenizedStrategy are called since they wont be defined in - * this contract. - * - * It will delegatecall the TokenizedStrategy implementation with the exact - * calldata and return any relevant values. - * - */ - fallback() external { - // load our target address - address _tokenizedStrategyAddress = tokenizedStrategyAddress; - // Execute external function using delegatecall and return any value. - assembly { - // Copy function selector and any arguments. - calldatacopy(0, 0, calldatasize()) - // Execute function delegatecall. - let result := delegatecall( - gas(), - _tokenizedStrategyAddress, - 0, - calldatasize(), - 0, - 0 - ) - // Get any return value - returndatacopy(0, 0, returndatasize()) - // Return any return value or error back to the caller - switch result - case 0 { - revert(0, returndatasize()) - } - default { - return(0, returndatasize()) - } - } - } -} diff --git a/contracts/yearn/DummyStrategy.sol b/contracts/yearn/DummyStrategy.sol deleted file mode 100644 index 715224f..0000000 --- a/contracts/yearn/DummyStrategy.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity >=0.8.18 ^0.8.0; - -import {BaseStrategy} from "./BaseStrategy.sol"; - -contract DummyStrategy is BaseStrategy { - constructor( - address _asset, - string memory _name - ) BaseStrategy(_asset, _name) {} - - - function _deployFunds(uint256 _amount) internal override {} - function _freeFunds(uint256 _amount) internal override {} - function _harvestAndReport() internal override returns (uint256 _totalAssets) { - _totalAssets = asset.balanceOf(address(this)); - } -} diff --git a/contracts/yearn/TokenizedStrategy.sol b/contracts/yearn/TokenizedStrategy.sol deleted file mode 100644 index 9590328..0000000 --- a/contracts/yearn/TokenizedStrategy.sol +++ /dev/null @@ -1,3378 +0,0 @@ - // SPDX-License-Identifier: AGPL-3.0 - pragma solidity >=0.8.18 ^0.8.0 ^0.8.1; - - // lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol - - // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) - - /** - * @dev Interface of the ERC20 standard as defined in the EIP. - */ - interface IERC20 { - /** - * @dev Emitted when `value` tokens are moved from one account (`from`) to - * another (`to`). - * - * Note that `value` may be zero. - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @dev Emitted when the allowance of a `spender` for an `owner` is set by - * a call to {approve}. `value` is the new allowance. - */ - event Approval(address indexed owner, address indexed spender, uint256 value); - - /** - * @dev Returns the amount of tokens in existence. - */ - function totalSupply() external view returns (uint256); - - /** - * @dev Returns the amount of tokens owned by `account`. - */ - function balanceOf(address account) external view returns (uint256); - - /** - * @dev Moves `amount` tokens from the caller's account to `to`. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transfer(address to, uint256 amount) external returns (bool); - - /** - * @dev Returns the remaining number of tokens that `spender` will be - * allowed to spend on behalf of `owner` through {transferFrom}. This is - * zero by default. - * - * This value changes when {approve} or {transferFrom} are called. - */ - function allowance(address owner, address spender) external view returns (uint256); - - /** - * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * IMPORTANT: Beware that changing an allowance with this method brings the risk - * that someone may use both the old and the new allowance by unfortunate - * transaction ordering. One possible solution to mitigate this race - * condition is to first reduce the spender's allowance to 0 and set the - * desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * - * Emits an {Approval} event. - */ - function approve(address spender, uint256 amount) external returns (bool); - - /** - * @dev Moves `amount` tokens from `from` to `to` using the - * allowance mechanism. `amount` is then deducted from the caller's - * allowance. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transferFrom(address from, address to, uint256 amount) external returns (bool); - } - - // lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol - - // OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol) - - /** - * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in - * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. - * - * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by - * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't - * need to send a transaction, and thus is not required to hold Ether at all. - * - * ==== Security Considerations - * - * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature - * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be - * considered as an intention to spend the allowance in any specific way. The second is that because permits have - * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should - * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be - * generally recommended is: - * - * ```solidity - * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { - * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} - * doThing(..., value); - * } - * - * function doThing(..., uint256 value) public { - * token.safeTransferFrom(msg.sender, address(this), value); - * ... - * } - * ``` - * - * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of - * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also - * {SafeERC20-safeTransferFrom}). - * - * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so - * contracts should have entry points that don't rely on permit. - */ - interface IERC20Permit { - /** - * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, - * given ``owner``'s signed approval. - * - * IMPORTANT: The same issues {IERC20-approve} has related to transaction - * ordering also apply here. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `deadline` must be a timestamp in the future. - * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` - * over the EIP712-formatted function arguments. - * - the signature must use ``owner``'s current nonce (see {nonces}). - * - * For more information on the signature format, see the - * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP - * section]. - * - * CAUTION: See Security Considerations above. - */ - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @dev Returns the current nonce for `owner`. This value must be - * included whenever a signature is generated for {permit}. - * - * Every successful call to {permit} increases ``owner``'s nonce by one. This - * prevents a signature from being used multiple times. - */ - function nonces(address owner) external view returns (uint256); - - /** - * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. - */ - // solhint-disable-next-line func-name-mixedcase - function DOMAIN_SEPARATOR() external view returns (bytes32); - } - - // lib/openzeppelin-contracts/contracts/utils/Address.sol - - // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) - - /** - * @dev Collection of functions related to the address type - */ - library Address { - /** - * @dev Returns true if `account` is a contract. - * - * [IMPORTANT] - * ==== - * It is unsafe to assume that an address for which this function returns - * false is an externally-owned account (EOA) and not a contract. - * - * Among others, `isContract` will return false for the following - * types of addresses: - * - * - an externally-owned account - * - a contract in construction - * - an address where a contract will be created - * - an address where a contract lived, but was destroyed - * - * Furthermore, `isContract` will also return true if the target contract within - * the same transaction is already scheduled for destruction by `SELFDESTRUCT`, - * which only has an effect at the end of a transaction. - * ==== - * - * [IMPORTANT] - * ==== - * You shouldn't rely on `isContract` to protect against flash loan attacks! - * - * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets - * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract - * constructor. - * ==== - */ - function isContract(address account) internal view returns (bool) { - // This method relies on extcodesize/address.code.length, which returns 0 - // for contracts in construction, since the code is only stored at the end - // of the constructor execution. - - return account.code.length > 0; - } - - /** - * @dev Replacement for Solidity's `transfer`: sends `amount` wei to - * `recipient`, forwarding all available gas and reverting on errors. - * - * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost - * of certain opcodes, possibly making contracts go over the 2300 gas limit - * imposed by `transfer`, making them unable to receive funds via - * `transfer`. {sendValue} removes this limitation. - * - * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. - * - * IMPORTANT: because control is transferred to `recipient`, care must be - * taken to not create reentrancy vulnerabilities. Consider using - * {ReentrancyGuard} or the - * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. - */ - function sendValue(address payable recipient, uint256 amount) internal { - require(address(this).balance >= amount, "Address: insufficient balance"); - - (bool success, ) = recipient.call{value: amount}(""); - require(success, "Address: unable to send value, recipient may have reverted"); - } - - /** - * @dev Performs a Solidity function call using a low level `call`. A - * plain `call` is an unsafe replacement for a function call: use this - * function instead. - * - * If `target` reverts with a revert reason, it is bubbled up by this - * function (like regular Solidity function calls). - * - * Returns the raw returned data. To convert to the expected return value, - * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. - * - * Requirements: - * - * - `target` must be a contract. - * - calling `target` with `data` must not revert. - * - * _Available since v3.1._ - */ - function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, "Address: low-level call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with - * `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but also transferring `value` wei to `target`. - * - * Requirements: - * - * - the calling contract must have an ETH balance of at least `value`. - * - the called Solidity function must be `payable`. - * - * _Available since v3.1._ - */ - function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { - return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); - } - - /** - * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but - * with `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value, - string memory errorMessage - ) internal returns (bytes memory) { - require(address(this).balance >= value, "Address: insufficient balance for call"); - (bool success, bytes memory returndata) = target.call{value: value}(data); - return verifyCallResultFromTarget(target, success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { - return functionStaticCall(target, data, "Address: low-level static call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall( - address target, - bytes memory data, - string memory errorMessage - ) internal view returns (bytes memory) { - (bool success, bytes memory returndata) = target.staticcall(data); - return verifyCallResultFromTarget(target, success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { - return functionDelegateCall(target, data, "Address: low-level delegate call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - (bool success, bytes memory returndata) = target.delegatecall(data); - return verifyCallResultFromTarget(target, success, returndata, errorMessage); - } - - /** - * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling - * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. - * - * _Available since v4.8._ - */ - function verifyCallResultFromTarget( - address target, - bool success, - bytes memory returndata, - string memory errorMessage - ) internal view returns (bytes memory) { - if (success) { - if (returndata.length == 0) { - // only check isContract if the call was successful and the return data is empty - // otherwise we already know that it was a contract - require(isContract(target), "Address: call to non-contract"); - } - return returndata; - } else { - _revert(returndata, errorMessage); - } - } - - /** - * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the - * revert reason or using the provided one. - * - * _Available since v4.3._ - */ - function verifyCallResult( - bool success, - bytes memory returndata, - string memory errorMessage - ) internal pure returns (bytes memory) { - if (success) { - return returndata; - } else { - _revert(returndata, errorMessage); - } - } - - function _revert(bytes memory returndata, string memory errorMessage) private pure { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert(errorMessage); - } - } - } - - // lib/openzeppelin-contracts/contracts/utils/Context.sol - - // OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol) - - /** - * @dev Provides information about the current execution context, including the - * sender of the transaction and its data. While these are generally available - * via msg.sender and msg.data, they should not be accessed in such a direct - * manner, since when dealing with meta-transactions the account sending and - * paying for execution may not be the actual sender (as far as an application - * is concerned). - * - * This contract is only required for intermediate, library-like contracts. - */ - abstract contract Context { - function _msgSender() internal view virtual returns (address) { - return msg.sender; - } - - function _msgData() internal view virtual returns (bytes calldata) { - return msg.data; - } - - function _contextSuffixLength() internal view virtual returns (uint256) { - return 0; - } - } - - // lib/openzeppelin-contracts/contracts/utils/math/Math.sol - - // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) - - /** - * @dev Standard math utilities missing in the Solidity language. - */ - library Math { - enum Rounding { - Down, // Toward negative infinity - Up, // Toward infinity - Zero // Toward zero - } - - /** - * @dev Returns the largest of two numbers. - */ - function max(uint256 a, uint256 b) internal pure returns (uint256) { - return a > b ? a : b; - } - - /** - * @dev Returns the smallest of two numbers. - */ - function min(uint256 a, uint256 b) internal pure returns (uint256) { - return a < b ? a : b; - } - - /** - * @dev Returns the average of two numbers. The result is rounded towards - * zero. - */ - function average(uint256 a, uint256 b) internal pure returns (uint256) { - // (a + b) / 2 can overflow. - return (a & b) + (a ^ b) / 2; - } - - /** - * @dev Returns the ceiling of the division of two numbers. - * - * This differs from standard division with `/` in that it rounds up instead - * of rounding down. - */ - function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { - // (a + b - 1) / b can overflow on addition, so we distribute. - return a == 0 ? 0 : (a - 1) / b + 1; - } - - /** - * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 - * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) - * with further edits by Uniswap Labs also under MIT license. - */ - function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { - unchecked { - // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use - // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 - // variables such that product = prod1 * 2^256 + prod0. - uint256 prod0; // Least significant 256 bits of the product - uint256 prod1; // Most significant 256 bits of the product - assembly { - let mm := mulmod(x, y, not(0)) - prod0 := mul(x, y) - prod1 := sub(sub(mm, prod0), lt(mm, prod0)) - } - - // Handle non-overflow cases, 256 by 256 division. - if (prod1 == 0) { - // Solidity will revert if denominator == 0, unlike the div opcode on its own. - // The surrounding unchecked block does not change this fact. - // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. - return prod0 / denominator; - } - - // Make sure the result is less than 2^256. Also prevents denominator == 0. - require(denominator > prod1, "Math: mulDiv overflow"); - - /////////////////////////////////////////////// - // 512 by 256 division. - /////////////////////////////////////////////// - - // Make division exact by subtracting the remainder from [prod1 prod0]. - uint256 remainder; - assembly { - // Compute remainder using mulmod. - remainder := mulmod(x, y, denominator) - - // Subtract 256 bit number from 512 bit number. - prod1 := sub(prod1, gt(remainder, prod0)) - prod0 := sub(prod0, remainder) - } - - // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. - // See https://cs.stackexchange.com/q/138556/92363. - - // Does not overflow because the denominator cannot be zero at this stage in the function. - uint256 twos = denominator & (~denominator + 1); - assembly { - // Divide denominator by twos. - denominator := div(denominator, twos) - - // Divide [prod1 prod0] by twos. - prod0 := div(prod0, twos) - - // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. - twos := add(div(sub(0, twos), twos), 1) - } - - // Shift in bits from prod1 into prod0. - prod0 |= prod1 * twos; - - // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such - // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for - // four bits. That is, denominator * inv = 1 mod 2^4. - uint256 inverse = (3 * denominator) ^ 2; - - // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works - // in modular arithmetic, doubling the correct bits in each step. - inverse *= 2 - denominator * inverse; // inverse mod 2^8 - inverse *= 2 - denominator * inverse; // inverse mod 2^16 - inverse *= 2 - denominator * inverse; // inverse mod 2^32 - inverse *= 2 - denominator * inverse; // inverse mod 2^64 - inverse *= 2 - denominator * inverse; // inverse mod 2^128 - inverse *= 2 - denominator * inverse; // inverse mod 2^256 - - // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. - // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is - // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 - // is no longer required. - result = prod0 * inverse; - return result; - } - } - - /** - * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. - */ - function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { - uint256 result = mulDiv(x, y, denominator); - if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) { - result += 1; - } - return result; - } - - /** - * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. - * - * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). - */ - function sqrt(uint256 a) internal pure returns (uint256) { - if (a == 0) { - return 0; - } - - // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. - // - // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have - // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. - // - // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` - // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` - // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` - // - // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. - uint256 result = 1 << (log2(a) >> 1); - - // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, - // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at - // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision - // into the expected uint128 result. - unchecked { - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - return min(result, a / result); - } - } - - /** - * @notice Calculates sqrt(a), following the selected rounding direction. - */ - function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = sqrt(a); - return result + (rounding == Rounding.Up && result * result < a ? 1 : 0); - } - } - - /** - * @dev Return the log in base 2, rounded down, of a positive value. - * Returns 0 if given 0. - */ - function log2(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - unchecked { - if (value >> 128 > 0) { - value >>= 128; - result += 128; - } - if (value >> 64 > 0) { - value >>= 64; - result += 64; - } - if (value >> 32 > 0) { - value >>= 32; - result += 32; - } - if (value >> 16 > 0) { - value >>= 16; - result += 16; - } - if (value >> 8 > 0) { - value >>= 8; - result += 8; - } - if (value >> 4 > 0) { - value >>= 4; - result += 4; - } - if (value >> 2 > 0) { - value >>= 2; - result += 2; - } - if (value >> 1 > 0) { - result += 1; - } - } - return result; - } - - /** - * @dev Return the log in base 2, following the selected rounding direction, of a positive value. - * Returns 0 if given 0. - */ - function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = log2(value); - return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0); - } - } - - /** - * @dev Return the log in base 10, rounded down, of a positive value. - * Returns 0 if given 0. - */ - function log10(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - unchecked { - if (value >= 10 ** 64) { - value /= 10 ** 64; - result += 64; - } - if (value >= 10 ** 32) { - value /= 10 ** 32; - result += 32; - } - if (value >= 10 ** 16) { - value /= 10 ** 16; - result += 16; - } - if (value >= 10 ** 8) { - value /= 10 ** 8; - result += 8; - } - if (value >= 10 ** 4) { - value /= 10 ** 4; - result += 4; - } - if (value >= 10 ** 2) { - value /= 10 ** 2; - result += 2; - } - if (value >= 10 ** 1) { - result += 1; - } - } - return result; - } - - /** - * @dev Return the log in base 10, following the selected rounding direction, of a positive value. - * Returns 0 if given 0. - */ - function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = log10(value); - return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0); - } - } - - /** - * @dev Return the log in base 256, rounded down, of a positive value. - * Returns 0 if given 0. - * - * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. - */ - function log256(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - unchecked { - if (value >> 128 > 0) { - value >>= 128; - result += 16; - } - if (value >> 64 > 0) { - value >>= 64; - result += 8; - } - if (value >> 32 > 0) { - value >>= 32; - result += 4; - } - if (value >> 16 > 0) { - value >>= 16; - result += 2; - } - if (value >> 8 > 0) { - result += 1; - } - } - return result; - } - - /** - * @dev Return the log in base 256, following the selected rounding direction, of a positive value. - * Returns 0 if given 0. - */ - function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = log256(value); - return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0); - } - } - } - - // src/interfaces/IBaseStrategy.sol - - interface IBaseStrategy { - function tokenizedStrategyAddress() external view returns (address); - - /*////////////////////////////////////////////////////////////// - IMMUTABLE FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - function availableDepositLimit( - address _owner - ) external view returns (uint256); - - function availableWithdrawLimit( - address _owner - ) external view returns (uint256); - - function deployFunds(uint256 _assets) external; - - function freeFunds(uint256 _amount) external; - - function harvestAndReport() external returns (uint256); - - function tendThis(uint256 _totalIdle) external; - - function shutdownWithdraw(uint256 _amount) external; - - function tendTrigger() external view returns (bool, bytes memory); - } - - // src/interfaces/IFactory.sol - - interface IFactory { - function protocol_fee_config() external view returns (uint16, address); - } - - // lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol - - // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) - - /** - * @dev Interface for the optional metadata functions from the ERC20 standard. - * - * _Available since v4.1._ - */ - interface IERC20Metadata is IERC20 { - /** - * @dev Returns the name of the token. - */ - function name() external view returns (string memory); - - /** - * @dev Returns the symbol of the token. - */ - function symbol() external view returns (string memory); - - /** - * @dev Returns the decimals places of the token. - */ - function decimals() external view returns (uint8); - } - - // lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol - - // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.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.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How - * to implement supply mechanisms]. - * - * The default value of {decimals} is 18. To change this, you should override - * this function so it returns a different value. - * - * 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}. - * - * 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 default value returned by this function, unless - * it's 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: - * - * - `to` cannot be the zero address. - * - the caller must have a balance of at least `amount`. - */ - function transfer(address to, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _transfer(owner, to, 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}. - * - * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on - * `transferFrom`. This is semantically equivalent to an infinite approval. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function approve(address spender, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _approve(owner, 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}. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Requirements: - * - * - `from` and `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - * - the caller must have allowance for ``from``'s tokens of at least - * `amount`. - */ - function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, amount); - _transfer(from, to, 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) { - address owner = _msgSender(); - _approve(owner, spender, allowance(owner, 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) { - address owner = _msgSender(); - uint256 currentAllowance = allowance(owner, spender); - require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); - unchecked { - _approve(owner, spender, currentAllowance - subtractedValue); - } - - return true; - } - - /** - * @dev Moves `amount` of tokens from `from` to `to`. - * - * 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: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - */ - function _transfer(address from, address to, uint256 amount) internal virtual { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); - - _beforeTokenTransfer(from, to, amount); - - uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); - unchecked { - _balances[from] = fromBalance - amount; - // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by - // decrementing then incrementing. - _balances[to] += amount; - } - - emit Transfer(from, to, amount); - - _afterTokenTransfer(from, to, 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; - unchecked { - // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. - _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; - // Overflow not possible: amount <= accountBalance <= totalSupply. - _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 Updates `owner` s allowance for `spender` based on spent `amount`. - * - * Does not update the allowance amount in case of infinite allowance. - * Revert if not enough allowance is available. - * - * Might emit an {Approval} event. - */ - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - require(currentAllowance >= amount, "ERC20: insufficient allowance"); - unchecked { - _approve(owner, spender, currentAllowance - 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 {} - } - - // lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol - - // OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.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; - - /** - * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, - * non-reverting calls are assumed to be successful. - */ - function safeTransfer(IERC20 token, address to, uint256 value) internal { - _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); - } - - /** - * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the - * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. - */ - function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { - _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); - } - - /** - * @dev Deprecated. This function has issues similar to the ones found in - * {IERC20-approve}, and its usage is discouraged. - * - * Whenever possible, use {safeIncreaseAllowance} and - * {safeDecreaseAllowance} instead. - */ - function safeApprove(IERC20 token, address spender, uint256 value) internal { - // safeApprove should only be called when setting an initial allowance, - // or when resetting it to zero. To increase and decrease it, use - // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' - require( - (value == 0) || (token.allowance(address(this), spender) == 0), - "SafeERC20: approve from non-zero to non-zero allowance" - ); - _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); - } - - /** - * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, - * non-reverting calls are assumed to be successful. - */ - function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { - uint256 oldAllowance = token.allowance(address(this), spender); - _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value)); - } - - /** - * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value, - * non-reverting calls are assumed to be successful. - */ - function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { - unchecked { - uint256 oldAllowance = token.allowance(address(this), spender); - require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); - _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value)); - } - } - - /** - * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, - * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval - * to be set to zero before setting it to a non-zero value, such as USDT. - */ - function forceApprove(IERC20 token, address spender, uint256 value) internal { - bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value); - - if (!_callOptionalReturnBool(token, approvalCall)) { - _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0)); - _callOptionalReturn(token, approvalCall); - } - } - - /** - * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. - * Revert on invalid signature. - */ - function safePermit( - IERC20Permit token, - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) internal { - uint256 nonceBefore = token.nonces(owner); - token.permit(owner, spender, value, deadline, v, r, s); - uint256 nonceAfter = token.nonces(owner); - require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); - } - - /** - * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement - * on the return value: the return value is optional (but if data is returned, it must not be false). - * @param token The token targeted by the call. - * @param data The call data (encoded using abi.encode or one of its variants). - */ - function _callOptionalReturn(IERC20 token, bytes memory data) private { - // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since - // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that - // the target address contains contract code and also asserts for success in the low-level call. - - bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); - require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); - } - - /** - * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement - * on the return value: the return value is optional (but if data is returned, it must not be false). - * @param token The token targeted by the call. - * @param data The call data (encoded using abi.encode or one of its variants). - * - * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. - */ - function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { - // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since - // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false - // and not revert is the subcall reverts. - - (bool success, bytes memory returndata) = address(token).call(data); - return - success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token)); - } - } - - // src/TokenizedStrategy.sol - - /**$$$$$$$$$$$$$$$$$$$$$$$$$$$&Mr/|1+~>>iiiiiiiiiii>~+{|tuMW$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ - $$$$$$$$$$$$$$$$$$$$$$$$$B#j]->iiiiiiiiiiiiiiiiiiiiiiiiiiii>-?f*B$$$$$$$$$$$$$$$$$$$$$$$$$ - $$$$$$$$$$$$$$$$$$$$$@zj}~iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii~}fv@$$$$$$$$$$$$$$$$$$$$$ - $$$$$$$$$$$$$$$$$$@z(+iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii+)zB$$$$$$$$$$$$$$$$$$ - $$$$$$$$$$$$$$$$Mf~iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii~t#@$$$$$$$$$$$$$$$ - $$$$$$$$$$$$$@u[iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii?n@$$$$$$$$$$$$$ - $$$$$$$$$$$@z]iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii?u@$$$$$$$$$$$ - $$$$$$$$$$v]iiiiiiiiiiiiiiii,.';iiiiiiiiiiiiiiiiiiiiiiiiii;'."iiiiiiiiiiiiiiii?u$$$$$$$$$$ - $$$$$$$$%)>iiiiiiiiiiiiiii,. ';iiiiiiiiiiiiiiiiiiiiii;' ."iiiiiiiiiiiiiiii1%$$$$$$$$ - $$$$$$$c~iiiiiiiiiiiiiii,. ';iiiiiiiiiiiiiiiiii;' ."iiiiiiiiiiiiiii~u$$$$$$$ - $$$$$B/>iiiiiiiiiiiiii!' `IiiiiiiiiiiiiiiI` .Iiiiiiiiiiiiiii>|%$$$$$ - $$$$@)iiiiiiiiiiiiiiiii;' `Iiiiiiiiiiil` ';iiiiiiiiiiiiiiiii}@$$$$ - $$$B|iiiiiiiiiiiiiiiiiiii;' `Iiiiiiil` ';iiiiiiiiiiiiiiiiiiii1B$$$ - $$@)iiiiiiiiiiiiiiiiiiiiiii:' `;iiI` ':iiiiiiiiiiiiiiiiiiiiiii{B$$ - $$|iiiiiiiiiiiiiiiiiiiiiiiiii;' `` ':iiiiiiiiiiiiiiiiiiiiiiiiii1$$ - $v>iiiiiiiiiiiiiiiiiiiiiiiiiiii:' ':iiiiiiiiiiiiiiiiiiiiiiiiiiii>x$ - &?iiiiiiiiiiiiiiiiiiiiiiiiiiiiiii:' .,iiiiiiiiiiiiiiiiiiiiiiiiiiiiiii-W - ziiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii:' .,iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiv - -iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii:' .,iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii- - iiiiiiiiiiiiiiiiii. `!iiiiiiiiiiiiiiiiiiiiiii^ .liiiiiiiiiiiiiiiiiir$ - $$(iiiiiiiiiiiiiiiiii;. ."iiiiiiiiiiiiiiiiiiii,. :iiiiiiiiiiiiiiiiii}$$ - $$@{iiiiiiiiiiiiiiiiii;. .`:iiiiiiiiiiiiii;^. :iiiiiiiiiiiiiiiiii}B$$ - $$$B)iiiiiiiiiiiiiiiiii!' '`",::::,"`'. .Iiiiiiiiiiiiiiiiiii{%$$$ - $$$$@1iiiiiiiiiiiiiiiiiii,. ^iiiiiiiiiiiiiiiiiii[@$$$$ - $$$$$B|>iiiiiiiiiiiiiiiiii!^. `liiiiiiiiiiiiiiiiii>)%$$$$$ - $$$$$$$c~iiiiiiiiiiiiiiiiiiii"' ."!iiiiiiiiiiiiiiiiiii~n$$$$$$$ - $$$$$$$$B)iiiiiiiiiiiiiiiiiiiii!,`. .'"liiiiiiiiiiiiiiiiiiiii1%$$$$$$$$ - $$$$$$$$$@u]iiiiiiiiiiiiiiiiiiiiiiil,^`'.. ..''^,liiiiiiiiiiiiiiiiiiiiiii-x@$$$$$$$$$ - $$$$$$$$$$$@v?iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii-x$$$$$$$$$$$$ - $$$$$$$$$$$$$@n?iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii-rB$$$$$$$$$$$$$ - $$$$$$$$$$$$$$$$/~iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii<\*@$$$$$$$$$$$$$$$$ - $$$$$$$$$$$$$$$$$$Bc1~iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii~{v%$$$$$$$$$$$$$$$$$$ - $$$$$$$$$$$$$$$$$$$$$Bvf]iiiiiiiiiiiiiiiiiiiiiiiiiiiii+_tc%$$$$$$$$$$$$$$$$$$$$$$$$$ - $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$W#u/|{+~>iiiiiiiiiiii><+{|/n#W$$$$$$$$$$$$$$$$$$$$$$$$$$$$*/ - - /** - * @title Yearn Tokenized Strategy - * @author yearn.finance - * @notice - * This TokenizedStrategy can be used by anyone wishing to easily build - * and deploy their own custom ERC4626 compliant single strategy Vault. - * - * The TokenizedStrategy contract is meant to be used as the proxy - * implementation contract that will handle all logic, storage and - * management for a custom strategy that inherits the `BaseStrategy`. - * Any function calls to the strategy that are not defined within that - * strategy will be forwarded through a delegateCall to this contract. - - * A strategist only needs to override a few simple functions that are - * focused entirely on the strategy specific needs to easily and cheaply - * deploy their own permissionless 4626 compliant vault. - */ - contract TokenizedStrategy { - using Math for uint256; - using SafeERC20 for ERC20; - - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - /** - * @notice Emitted when a strategy is shutdown. - */ - event StrategyShutdown(); - - /** - * @notice Emitted on the initialization of any new `strategy` that uses `asset` - * with this specific `apiVersion`. - */ - event NewTokenizedStrategy( - address indexed strategy, - address indexed asset, - string apiVersion - ); - - /** - * @notice Emitted when the strategy reports `profit` or `loss` and - * `performanceFees` and `protocolFees` are paid out. - */ - event Reported( - uint256 profit, - uint256 loss, - uint256 protocolFees, - uint256 performanceFees - ); - - /** - * @notice Emitted when the 'performanceFeeRecipient' address is - * updated to 'newPerformanceFeeRecipient'. - */ - event UpdatePerformanceFeeRecipient( - address indexed newPerformanceFeeRecipient - ); - - /** - * @notice Emitted when the 'keeper' address is updated to 'newKeeper'. - */ - event UpdateKeeper(address indexed newKeeper); - - /** - * @notice Emitted when the 'performanceFee' is updated to 'newPerformanceFee'. - */ - event UpdatePerformanceFee(uint16 newPerformanceFee); - - /** - * @notice Emitted when the 'management' address is updated to 'newManagement'. - */ - event UpdateManagement(address indexed newManagement); - - /** - * @notice Emitted when the 'emergencyAdmin' address is updated to 'newEmergencyAdmin'. - */ - event UpdateEmergencyAdmin(address indexed newEmergencyAdmin); - - /** - * @notice Emitted when the 'profitMaxUnlockTime' is updated to 'newProfitMaxUnlockTime'. - */ - event UpdateProfitMaxUnlockTime(uint256 newProfitMaxUnlockTime); - - /** - * @notice Emitted when the 'pendingManagement' address is updated to 'newPendingManagement'. - */ - event UpdatePendingManagement(address indexed newPendingManagement); - - /** - * @notice 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 - ); - - /** - * @notice 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); - - /** - * @notice Emitted when the `caller` has exchanged `assets` for `shares`, - * and transferred those `shares` to `owner`. - */ - event Deposit( - address indexed caller, - address indexed owner, - uint256 assets, - uint256 shares - ); - - /** - * @notice Emitted when the `caller` has exchanged `owner`s `shares` for `assets`, - * and transferred those `assets` to `receiver`. - */ - event Withdraw( - address indexed caller, - address indexed receiver, - address indexed owner, - uint256 assets, - uint256 shares - ); - - /*////////////////////////////////////////////////////////////// - STORAGE STRUCT - //////////////////////////////////////////////////////////////*/ - - /** - * @dev The struct that will hold all the storage data for each strategy - * that uses this implementation. - * - * This replaces all state variables for a traditional contract. This - * full struct will be initialized on the creation of the strategy - * and continually updated and read from for the life of the contract. - * - * We combine all the variables into one struct to limit the amount of - * times the custom storage slots need to be loaded during complex functions. - * - * Loading the corresponding storage slot for the struct does not - * load any of the contents of the struct into memory. So the size - * will not increase memory related gas usage. - */ - // prettier-ignore - struct StrategyData { - // The ERC20 compliant underlying asset that will be - // used by the Strategy - ERC20 asset; - - // These are the corresponding ERC20 variables needed for the - // strategies token that is issued and burned on each deposit or withdraw. - uint8 decimals; // The amount of decimals that `asset` and strategy use. - string name; // The name of the token for the strategy. - uint256 totalSupply; // The total amount of shares currently issued. - mapping(address => uint256) nonces; // Mapping of nonces used for permit functions. - mapping(address => uint256) balances; // Mapping to track current balances for each account that holds shares. - mapping(address => mapping(address => uint256)) allowances; // Mapping to track the allowances for the strategies shares. - - // We manually track `totalAssets` to prevent PPS manipulation through airdrops. - uint256 totalAssets; - - // Variables for profit reporting and locking. - // We use uint96 for timestamps to fit in the same slot as an address. That overflows in 2.5e+21 years. - // I know Yearn moves slowly but surely V4 will be out by then. - // If the timestamps ever overflow tell the cyborgs still using this code I'm sorry for being cheap. - uint256 profitUnlockingRate; // The rate at which locked profit is unlocking. - uint96 fullProfitUnlockDate; // The timestamp at which all locked shares will unlock. - address keeper; // Address given permission to call {report} and {tend}. - uint32 profitMaxUnlockTime; // The amount of seconds that the reported profit unlocks over. - uint16 performanceFee; // The percent in basis points of profit that is charged as a fee. - address performanceFeeRecipient; // The address to pay the `performanceFee` to. - uint96 lastReport; // The last time a {report} was called. - - // Access management variables. - address management; // Main address that can set all configurable variables. - address pendingManagement; // Address that is pending to take over `management`. - address emergencyAdmin; // Address to act in emergencies as well as `management`. - - // Strategy Status - uint8 entered; // To prevent reentrancy. Use uint8 for gas savings. - bool shutdown; // Bool that can be used to stop deposits into the strategy. - } - - /*////////////////////////////////////////////////////////////// - MODIFIERS - //////////////////////////////////////////////////////////////*/ - - /** - * @dev Require that the call is coming from the strategies management. - */ - modifier onlyManagement() { - requireManagement(msg.sender); - _; - } - - /** - * @dev Require that the call is coming from either the strategies - * management or the keeper. - */ - modifier onlyKeepers() { - requireKeeperOrManagement(msg.sender); - _; - } - - /** - * @dev Require that the call is coming from either the strategies - * management or the emergencyAdmin. - */ - modifier onlyEmergencyAuthorized() { - requireEmergencyAuthorized(msg.sender); - _; - } - - /** - * @dev Prevents a contract from calling itself, directly or indirectly. - * Placed over all state changing functions for increased safety. - */ - modifier nonReentrant() { - StrategyData storage S = _strategyStorage(); - // On the first call to nonReentrant, `entered` will be false (2) - require(S.entered != ENTERED, "ReentrancyGuard: reentrant call"); - - // Any calls to nonReentrant after this point will fail - S.entered = ENTERED; - - _; - - // Reset to false (1) once call has finished. - S.entered = NOT_ENTERED; - } - - /** - * @notice Require a caller is `management`. - * @dev Is left public so that it can be used by the Strategy. - * - * When the Strategy calls this the msg.sender would be the - * address of the strategy so we need to specify the sender. - * - * @param _sender The original msg.sender. - */ - function requireManagement(address _sender) public view { - require(_sender == _strategyStorage().management, "!management"); - } - - /** - * @notice Require a caller is the `keeper` or `management`. - * @dev Is left public so that it can be used by the Strategy. - * - * When the Strategy calls this the msg.sender would be the - * address of the strategy so we need to specify the sender. - * - * @param _sender The original msg.sender. - */ - function requireKeeperOrManagement(address _sender) public view { - StrategyData storage S = _strategyStorage(); - require(_sender == S.keeper || _sender == S.management, "!keeper"); - } - - /** - * @notice Require a caller is the `management` or `emergencyAdmin`. - * @dev Is left public so that it can be used by the Strategy. - * - * When the Strategy calls this the msg.sender would be the - * address of the strategy so we need to specify the sender. - * - * @param _sender The original msg.sender. - */ - function requireEmergencyAuthorized(address _sender) public view { - StrategyData storage S = _strategyStorage(); - require( - _sender == S.emergencyAdmin || _sender == S.management, - "!emergency authorized" - ); - } - - /*////////////////////////////////////////////////////////////// - CONSTANTS - //////////////////////////////////////////////////////////////*/ - - /// @notice API version this TokenizedStrategy implements. - string internal constant API_VERSION = "3.0.3"; - - /// @notice Value to set the `entered` flag to during a call. - uint8 internal constant ENTERED = 2; - /// @notice Value to set the `entered` flag to at the end of the call. - uint8 internal constant NOT_ENTERED = 1; - - /// @notice Maximum in Basis Points the Performance Fee can be set to. - uint16 public constant MAX_FEE = 5_000; // 50% - - /// @notice Used for fee calculations. - uint256 internal constant MAX_BPS = 10_000; - /// @notice Used for profit unlocking rate calculations. - uint256 internal constant MAX_BPS_EXTENDED = 1_000_000_000_000; - - /// @notice Seconds per year for max profit unlocking time. - uint256 internal constant SECONDS_PER_YEAR = 31_556_952; // 365.2425 days - - /** - * @dev Custom storage slot that will be used to store the - * `StrategyData` struct that holds each strategies - * specific storage variables. - * - * Any storage updates done by the TokenizedStrategy actually update - * the storage of the calling contract. This variable points - * to the specific location that will be used to store the - * struct that holds all that data. - * - * We use a custom string in order to get a random - * storage slot that will allow for strategists to use any - * amount of storage in their strategy without worrying - * about collisions. - */ - bytes32 internal constant BASE_STRATEGY_STORAGE = - bytes32(uint256(keccak256("yearn.base.strategy.storage")) - 1); - - /*////////////////////////////////////////////////////////////// - IMMUTABLE - //////////////////////////////////////////////////////////////*/ - - /// @notice Address of the previously deployed Vault factory that the - // protocol fee config is retrieved from. - address public immutable FACTORY; - - /*////////////////////////////////////////////////////////////// - STORAGE GETTER - //////////////////////////////////////////////////////////////*/ - - /** - * @dev will return the actual storage slot where the strategy - * specific `StrategyData` struct is stored for both read - * and write operations. - * - * This loads just the slot location, not the full struct - * so it can be used in a gas efficient manner. - */ - function _strategyStorage() internal pure returns (StrategyData storage S) { - // Since STORAGE_SLOT is a constant, we have to put a variable - // on the stack to access it from an inline assembly block. - bytes32 slot = BASE_STRATEGY_STORAGE; - assembly { - S.slot := slot - } - } - - /*////////////////////////////////////////////////////////////// - INITIALIZATION - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Used to initialize storage for a newly deployed strategy. - * @dev This should be called atomically whenever a new strategy is - * deployed and can only be called once for each strategy. - * - * This will set all the default storage that must be set for a - * strategy to function. Any changes can be made post deployment - * through external calls from `management`. - * - * The function will also emit an event that off chain indexers can - * look for to track any new deployments using this TokenizedStrategy. - * - * @param _asset Address of the underlying asset. - * @param _name Name the strategy will use. - * @param _management Address to set as the strategies `management`. - * @param _performanceFeeRecipient Address to receive performance fees. - * @param _keeper Address to set as strategies `keeper`. - */ - function initialize( - address _asset, - string memory _name, - address _management, - address _performanceFeeRecipient, - address _keeper - ) external { - // Cache storage pointer. - StrategyData storage S = _strategyStorage(); - - // Make sure we aren't initialized. - require(address(S.asset) == address(0), "initialized"); - - // Set the strategy's underlying asset. - S.asset = ERC20(_asset); - // Set the Strategy Tokens name. - S.name = _name; - // Set decimals based off the `asset`. - S.decimals = ERC20(_asset).decimals(); - - // Default to a 10 day profit unlock period. - S.profitMaxUnlockTime = 10 days; - // Set address to receive performance fees. - // Can't be address(0) or we will be burning fees. - require(_performanceFeeRecipient != address(0), "ZERO ADDRESS"); - // Can't mint shares to its self because of profit locking. - require(_performanceFeeRecipient != address(this), "self"); - S.performanceFeeRecipient = _performanceFeeRecipient; - // Default to a 10% performance fee. - S.performanceFee = 1_000; - // Set last report to this block. - S.lastReport = uint96(block.timestamp); - - // Set the default management address. Can't be 0. - require(_management != address(0), "ZERO ADDRESS"); - S.management = _management; - // Set the keeper address - S.keeper = _keeper; - - // Emit event to signal a new strategy has been initialized. - emit NewTokenizedStrategy(address(this), _asset, API_VERSION); - } - - /*////////////////////////////////////////////////////////////// - ERC4626 WRITE METHODS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Mints `shares` of strategy shares to `receiver` by - * depositing exactly `assets` of underlying tokens. - * @param assets The amount of underlying to deposit in. - * @param receiver The address to receive the `shares`. - * @return shares The actual amount of shares issued. - */ - function deposit( - uint256 assets, - address receiver - ) external nonReentrant returns (uint256 shares) { - // Get the storage slot for all following calls. - StrategyData storage S = _strategyStorage(); - - // Deposit full balance if using max uint. - if (assets == type(uint256).max) { - assets = S.asset.balanceOf(msg.sender); - } - - // Checking max deposit will also check if shutdown. - require( - assets <= _maxDeposit(S, receiver), - "ERC4626: deposit more than max" - ); - // Check for rounding error. - require( - (shares = _convertToShares(S, assets, Math.Rounding.Down)) != 0, - "ZERO_SHARES" - ); - - _deposit(S, receiver, assets, shares); - } - - /** - * @notice Mints exactly `shares` of strategy shares to - * `receiver` by depositing `assets` of underlying tokens. - * @param shares The amount of strategy shares mint. - * @param receiver The address to receive the `shares`. - * @return assets The actual amount of asset deposited. - */ - function mint( - uint256 shares, - address receiver - ) external nonReentrant returns (uint256 assets) { - // Get the storage slot for all following calls. - StrategyData storage S = _strategyStorage(); - - // Checking max mint will also check if shutdown. - require(shares <= _maxMint(S, receiver), "ERC4626: mint more than max"); - // Check for rounding error. - require( - (assets = _convertToAssets(S, shares, Math.Rounding.Up)) != 0, - "ZERO_ASSETS" - ); - - _deposit(S, receiver, assets, shares); - } - - /** - * @notice Withdraws exactly `assets` from `owners` shares and sends - * the underlying tokens to `receiver`. - * @dev This will default to not allowing any loss to be taken. - * @param assets The amount of underlying to withdraw. - * @param receiver The address to receive `assets`. - * @param owner The address whose shares are burnt. - * @return shares The actual amount of shares burnt. - */ - function withdraw( - uint256 assets, - address receiver, - address owner - ) external returns (uint256 shares) { - return withdraw(assets, receiver, owner, 0); - } - - /** - * @notice Withdraws `assets` from `owners` shares and sends - * the underlying tokens to `receiver`. - * @dev This includes an added parameter to allow for losses. - * @param assets The amount of underlying to withdraw. - * @param receiver The address to receive `assets`. - * @param owner The address whose shares are burnt. - * @param maxLoss The amount of acceptable loss in Basis points. - * @return shares The actual amount of shares burnt. - */ - function withdraw( - uint256 assets, - address receiver, - address owner, - uint256 maxLoss - ) public nonReentrant returns (uint256 shares) { - // Get the storage slot for all following calls. - StrategyData storage S = _strategyStorage(); - require( - assets <= _maxWithdraw(S, owner), - "ERC4626: withdraw more than max" - ); - // Check for rounding error or 0 value. - require( - (shares = _convertToShares(S, assets, Math.Rounding.Up)) != 0, - "ZERO_SHARES" - ); - - // Withdraw and track the actual amount withdrawn for loss check. - _withdraw(S, receiver, owner, assets, shares, maxLoss); - } - - /** - * @notice Redeems exactly `shares` from `owner` and - * sends `assets` of underlying tokens to `receiver`. - * @dev This will default to allowing any loss passed to be realized. - * @param shares The amount of shares burnt. - * @param receiver The address to receive `assets`. - * @param owner The address whose shares are burnt. - * @return assets The actual amount of underlying withdrawn. - */ - function redeem( - uint256 shares, - address receiver, - address owner - ) external returns (uint256) { - // We default to not limiting a potential loss. - return redeem(shares, receiver, owner, MAX_BPS); - } - - /** - * @notice Redeems exactly `shares` from `owner` and - * sends `assets` of underlying tokens to `receiver`. - * @dev This includes an added parameter to allow for losses. - * @param shares The amount of shares burnt. - * @param receiver The address to receive `assets`. - * @param owner The address whose shares are burnt. - * @param maxLoss The amount of acceptable loss in Basis points. - * @return . The actual amount of underlying withdrawn. - */ - function redeem( - uint256 shares, - address receiver, - address owner, - uint256 maxLoss - ) public nonReentrant returns (uint256) { - // Get the storage slot for all following calls. - StrategyData storage S = _strategyStorage(); - require( - shares <= _maxRedeem(S, owner), - "ERC4626: redeem more than max" - ); - uint256 assets; - // Check for rounding error or 0 value. - require( - (assets = _convertToAssets(S, shares, Math.Rounding.Down)) != 0, - "ZERO_ASSETS" - ); - - // We need to return the actual amount withdrawn in case of a loss. - return _withdraw(S, receiver, owner, assets, shares, maxLoss); - } - - /*////////////////////////////////////////////////////////////// - EXTERNAL 4626 VIEW METHODS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Get the total amount of assets this strategy holds - * as of the last report. - * - * We manually track `totalAssets` to avoid any PPS manipulation. - * - * @return . Total assets the strategy holds. - */ - function totalAssets() external view returns (uint256) { - return _totalAssets(_strategyStorage()); - } - - /** - * @notice Get the current supply of the strategies shares. - * - * Locked shares issued to the strategy from profits are not - * counted towards the full supply until they are unlocked. - * - * As more shares slowly unlock the totalSupply will decrease - * causing the PPS of the strategy to increase. - * - * @return . Total amount of shares outstanding. - */ - function totalSupply() external view returns (uint256) { - return _totalSupply(_strategyStorage()); - } - - /** - * @notice The amount of shares that the strategy would - * exchange for the amount of assets provided, in an - * ideal scenario where all the conditions are met. - * - * @param assets The amount of underlying. - * @return . Expected shares that `assets` represents. - */ - function convertToShares(uint256 assets) external view returns (uint256) { - return _convertToShares(_strategyStorage(), assets, Math.Rounding.Down); - } - - /** - * @notice The amount of assets that the strategy would - * exchange for the amount of shares provided, in an - * ideal scenario where all the conditions are met. - * - * @param shares The amount of the strategies shares. - * @return . Expected amount of `asset` the shares represents. - */ - function convertToAssets(uint256 shares) external view returns (uint256) { - return _convertToAssets(_strategyStorage(), shares, Math.Rounding.Down); - } - - /** - * @notice Allows an on-chain or off-chain user to simulate - * the effects of their deposit at the current block, given - * current on-chain conditions. - * @dev This will round down. - * - * @param assets The amount of `asset` to deposits. - * @return . Expected shares that would be issued. - */ - function previewDeposit(uint256 assets) external view returns (uint256) { - return _convertToShares(_strategyStorage(), assets, Math.Rounding.Down); - } - - /** - * @notice Allows an on-chain or off-chain user to simulate - * the effects of their mint at the current block, given - * current on-chain conditions. - * @dev This is used instead of convertToAssets so that it can - * round up for safer mints. - * - * @param shares The amount of shares to mint. - * @return . The needed amount of `asset` for the mint. - */ - function previewMint(uint256 shares) external view returns (uint256) { - return _convertToAssets(_strategyStorage(), shares, Math.Rounding.Up); - } - - /** - * @notice Allows an on-chain or off-chain user to simulate - * the effects of their withdrawal at the current block, - * given current on-chain conditions. - * @dev This is used instead of convertToShares so that it can - * round up for safer withdraws. - * - * @param assets The amount of `asset` that would be withdrawn. - * @return . The amount of shares that would be burnt. - */ - function previewWithdraw(uint256 assets) external view returns (uint256) { - return _convertToShares(_strategyStorage(), assets, Math.Rounding.Up); - } - - /** - * @notice Allows an on-chain or off-chain user to simulate - * the effects of their redemption at the current block, - * given current on-chain conditions. - * @dev This will round down. - * - * @param shares The amount of shares that would be redeemed. - * @return . The amount of `asset` that would be returned. - */ - function previewRedeem(uint256 shares) external view returns (uint256) { - return _convertToAssets(_strategyStorage(), shares, Math.Rounding.Down); - } - - /** - * @notice Total number of underlying assets that can - * be deposited into the strategy, where `receiver` - * corresponds to the receiver of the shares of a {deposit} call. - * - * @param receiver The address receiving the shares. - * @return . The max that `receiver` can deposit in `asset`. - */ - function maxDeposit(address receiver) external view returns (uint256) { - return _maxDeposit(_strategyStorage(), receiver); - } - - /** - * @notice Total number of shares that can be minted to `receiver` - * of a {mint} call. - * - * @param receiver The address receiving the shares. - * @return _maxMint The max that `receiver` can mint in shares. - */ - function maxMint(address receiver) external view returns (uint256) { - return _maxMint(_strategyStorage(), receiver); - } - - /** - * @notice Total number of underlying assets that can be - * withdrawn from the strategy by `owner`, where `owner` - * corresponds to the msg.sender of a {redeem} call. - * - * @param owner The owner of the shares. - * @return _maxWithdraw Max amount of `asset` that can be withdrawn. - */ - function maxWithdraw(address owner) external view returns (uint256) { - return _maxWithdraw(_strategyStorage(), owner); - } - - /** - * @notice Variable `maxLoss` is ignored. - * @dev Accepts a `maxLoss` variable in order to match the multi - * strategy vaults ABI. - */ - function maxWithdraw( - address owner, - uint256 /*maxLoss*/ - ) external view returns (uint256) { - return _maxWithdraw(_strategyStorage(), owner); - } - - /** - * @notice Total number of strategy shares that can be - * redeemed from the strategy by `owner`, where `owner` - * corresponds to the msg.sender of a {redeem} call. - * - * @param owner The owner of the shares. - * @return _maxRedeem Max amount of shares that can be redeemed. - */ - function maxRedeem(address owner) external view returns (uint256) { - return _maxRedeem(_strategyStorage(), owner); - } - - /** - * @notice Variable `maxLoss` is ignored. - * @dev Accepts a `maxLoss` variable in order to match the multi - * strategy vaults ABI. - */ - function maxRedeem( - address owner, - uint256 /*maxLoss*/ - ) external view returns (uint256) { - return _maxRedeem(_strategyStorage(), owner); - } - - /*////////////////////////////////////////////////////////////// - INTERNAL 4626 VIEW METHODS - //////////////////////////////////////////////////////////////*/ - - /// @dev Internal implementation of {totalAssets}. - function _totalAssets( - StrategyData storage S - ) internal view returns (uint256) { - return S.totalAssets; - } - - /// @dev Internal implementation of {totalSupply}. - function _totalSupply( - StrategyData storage S - ) internal view returns (uint256) { - return S.totalSupply - _unlockedShares(S); - } - - /// @dev Internal implementation of {convertToShares}. - function _convertToShares( - StrategyData storage S, - uint256 assets, - Math.Rounding _rounding - ) internal view returns (uint256) { - // Saves an extra SLOAD if values are non-zero. - uint256 totalSupply_ = _totalSupply(S); - // If supply is 0, PPS = 1. - if (totalSupply_ == 0) return assets; - - uint256 totalAssets_ = _totalAssets(S); - // If assets are 0 but supply is not PPS = 0. - if (totalAssets_ == 0) return 0; - - return assets.mulDiv(totalSupply_, totalAssets_, _rounding); - } - - /// @dev Internal implementation of {convertToAssets}. - function _convertToAssets( - StrategyData storage S, - uint256 shares, - Math.Rounding _rounding - ) internal view returns (uint256) { - // Saves an extra SLOAD if totalSupply() is non-zero. - uint256 supply = _totalSupply(S); - - return - supply == 0 - ? shares - : shares.mulDiv(_totalAssets(S), supply, _rounding); - } - - /// @dev Internal implementation of {maxDeposit}. - function _maxDeposit( - StrategyData storage S, - address receiver - ) internal view returns (uint256) { - // Cannot deposit when shutdown or to the strategy. - if (S.shutdown || receiver == address(this)) return 0; - - return IBaseStrategy(address(this)).availableDepositLimit(receiver); - } - - /// @dev Internal implementation of {maxMint}. - function _maxMint( - StrategyData storage S, - address receiver - ) internal view returns (uint256 maxMint_) { - // Cannot mint when shutdown or to the strategy. - if (S.shutdown || receiver == address(this)) return 0; - - maxMint_ = IBaseStrategy(address(this)).availableDepositLimit(receiver); - if (maxMint_ != type(uint256).max) { - maxMint_ = _convertToShares(S, maxMint_, Math.Rounding.Down); - } - } - - /// @dev Internal implementation of {maxWithdraw}. - function _maxWithdraw( - StrategyData storage S, - address owner - ) internal view returns (uint256 maxWithdraw_) { - // Get the max the owner could withdraw currently. - maxWithdraw_ = IBaseStrategy(address(this)).availableWithdrawLimit( - owner - ); - - // If there is no limit enforced. - if (maxWithdraw_ == type(uint256).max) { - // Saves a min check if there is no withdrawal limit. - maxWithdraw_ = _convertToAssets( - S, - _balanceOf(S, owner), - Math.Rounding.Down - ); - } else { - maxWithdraw_ = Math.min( - _convertToAssets(S, _balanceOf(S, owner), Math.Rounding.Down), - maxWithdraw_ - ); - } - } - - /// @dev Internal implementation of {maxRedeem}. - function _maxRedeem( - StrategyData storage S, - address owner - ) internal view returns (uint256 maxRedeem_) { - // Get the max the owner could withdraw currently. - maxRedeem_ = IBaseStrategy(address(this)).availableWithdrawLimit(owner); - - // Conversion would overflow and saves a min check if there is no withdrawal limit. - if (maxRedeem_ == type(uint256).max) { - maxRedeem_ = _balanceOf(S, owner); - } else { - maxRedeem_ = Math.min( - // Can't redeem more than the balance. - _convertToShares(S, maxRedeem_, Math.Rounding.Down), - _balanceOf(S, owner) - ); - } - } - - /*////////////////////////////////////////////////////////////// - INTERNAL 4626 WRITE METHODS - //////////////////////////////////////////////////////////////*/ - - /** - * @dev Function to be called during {deposit} and {mint}. - * - * This function handles all logic including transfers, - * minting and accounting. - * - * We do all external calls before updating any internal - * values to prevent view reentrancy issues from the token - * transfers or the _deployFunds() calls. - */ - function _deposit( - StrategyData storage S, - address receiver, - uint256 assets, - uint256 shares - ) internal { - // Cache storage variables used more than once. - ERC20 _asset = S.asset; - - // Need to transfer before minting or ERC777s could reenter. - _asset.safeTransferFrom(msg.sender, address(this), assets); - - // We can deploy the full loose balance currently held. - IBaseStrategy(address(this)).deployFunds( - _asset.balanceOf(address(this)) - ); - - // Adjust total Assets. - S.totalAssets += assets; - - // mint shares - _mint(S, receiver, shares); - - emit Deposit(msg.sender, receiver, assets, shares); - } - - /** - * @dev To be called during {redeem} and {withdraw}. - * - * This will handle all logic, transfers and accounting - * in order to service the withdraw request. - * - * If we are not able to withdraw the full amount needed, it will - * be counted as a loss and passed on to the user. - */ - function _withdraw( - StrategyData storage S, - address receiver, - address owner, - uint256 assets, - uint256 shares, - uint256 maxLoss - ) internal returns (uint256) { - require(receiver != address(0), "ZERO ADDRESS"); - require(maxLoss <= MAX_BPS, "exceeds MAX_BPS"); - - // Spend allowance if applicable. - if (msg.sender != owner) { - _spendAllowance(S, owner, msg.sender, shares); - } - - // Cache `asset` since it is used multiple times.. - ERC20 _asset = S.asset; - - uint256 idle = _asset.balanceOf(address(this)); - uint256 loss; - // Check if we need to withdraw funds. - if (idle < assets) { - // Tell Strategy to free what we need. - unchecked { - IBaseStrategy(address(this)).freeFunds(assets - idle); - } - - // Return the actual amount withdrawn. Adjust for potential under withdraws. - idle = _asset.balanceOf(address(this)); - - // If we didn't get enough out then we have a loss. - if (idle < assets) { - unchecked { - loss = assets - idle; - } - // If a non-default max loss parameter was set. - if (maxLoss < MAX_BPS) { - // Make sure we are within the acceptable range. - require( - loss <= (assets * maxLoss) / MAX_BPS, - "too much loss" - ); - } - // Lower the amount to be withdrawn. - assets = idle; - } - } - - // Update assets based on how much we took. - S.totalAssets -= (assets + loss); - - _burn(S, owner, shares); - - // Transfer the amount of underlying to the receiver. - _asset.safeTransfer(receiver, assets); - - emit Withdraw(msg.sender, receiver, owner, assets, shares); - - // Return the actual amount of assets withdrawn. - return assets; - } - - /*////////////////////////////////////////////////////////////// - PROFIT REPORTING - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Function for keepers to call to harvest and record all - * profits accrued. - * - * @dev This will account for any gains/losses since the last report - * and charge fees accordingly. - * - * Any profit over the fees charged will be immediately locked - * so there is no change in PricePerShare. Then slowly unlocked - * over the `maxProfitUnlockTime` each second based on the - * calculated `profitUnlockingRate`. - * - * In case of a loss it will first attempt to offset the loss - * with any remaining locked shares from the last report in - * order to reduce any negative impact to PPS. - * - * Will then recalculate the new time to unlock profits over and the - * rate based on a weighted average of any remaining time from the - * last report and the new amount of shares to be locked. - * - * @return profit The notional amount of gain if any since the last - * report in terms of `asset`. - * @return loss The notional amount of loss if any since the last - * report in terms of `asset`. - */ - function report() - external - nonReentrant - onlyKeepers - returns (uint256 profit, uint256 loss) - { - // Cache storage pointer since its used repeatedly. - StrategyData storage S = _strategyStorage(); - - // Tell the strategy to report the real total assets it has. - // It should do all reward selling and redepositing now and - // account for deployed and loose `asset` so we can accurately - // account for all funds including those potentially airdropped - // and then have any profits immediately locked. - uint256 newTotalAssets = IBaseStrategy(address(this)) - .harvestAndReport(); - - uint256 oldTotalAssets = _totalAssets(S); - - // Get the amount of shares we need to burn from previous reports. - uint256 sharesToBurn = _unlockedShares(S); - - // Initialize variables needed throughout. - uint256 totalFees; - uint256 protocolFees; - uint256 sharesToLock; - uint256 _profitMaxUnlockTime = S.profitMaxUnlockTime; - // Calculate profit/loss. - if (newTotalAssets > oldTotalAssets) { - // We have a profit. - unchecked { - profit = newTotalAssets - oldTotalAssets; - } - - // We need to get the equivalent amount of shares - // at the current PPS before any minting or burning. - sharesToLock = _convertToShares(S, profit, Math.Rounding.Down); - - // Cache the performance fee. - uint16 fee = S.performanceFee; - uint256 totalFeeShares; - // If we are charging a performance fee - if (fee != 0) { - // Asses performance fees. - unchecked { - // Get in `asset` for the event. - totalFees = (profit * fee) / MAX_BPS; - // And in shares for the payment. - totalFeeShares = (sharesToLock * fee) / MAX_BPS; - } - - // Get the protocol fee config from the factory. - ( - uint16 protocolFeeBps, - address protocolFeesRecipient - ) = IFactory(FACTORY).protocol_fee_config(); - - uint256 protocolFeeShares; - // Check if there is a protocol fee to charge. - if (protocolFeeBps != 0) { - unchecked { - // Calculate protocol fees based on the performance Fees. - protocolFeeShares = - (totalFeeShares * protocolFeeBps) / - MAX_BPS; - // Need amount in underlying for event. - protocolFees = (totalFees * protocolFeeBps) / MAX_BPS; - } - - // Mint the protocol fees to the recipient. - _mint(S, protocolFeesRecipient, protocolFeeShares); - } - - // Mint the difference to the strategy fee recipient. - unchecked { - _mint( - S, - S.performanceFeeRecipient, - totalFeeShares - protocolFeeShares - ); - } - } - - // Check if we are locking profit. - if (_profitMaxUnlockTime != 0) { - // lock (profit - fees) - unchecked { - sharesToLock -= totalFeeShares; - } - - // If we are burning more than re-locking. - if (sharesToBurn > sharesToLock) { - // Burn the difference - unchecked { - _burn(S, address(this), sharesToBurn - sharesToLock); - } - } else if (sharesToLock > sharesToBurn) { - // Mint the shares to lock the strategy. - unchecked { - _mint(S, address(this), sharesToLock - sharesToBurn); - } - } - } - } else { - // Expect we have a loss. - unchecked { - loss = oldTotalAssets - newTotalAssets; - } - - // Check in case `else` was due to being equal. - if (loss != 0) { - // We will try and burn the unlocked shares and as much from any - // pending profit still unlocking to offset the loss to prevent any PPS decline post report. - sharesToBurn = Math.min( - // Cannot burn more than we have. - S.balances[address(this)], - // Try and burn both the shares already unlocked and the amount for the loss. - _convertToShares(S, loss, Math.Rounding.Down) + sharesToBurn - ); - } - - // Check if there is anything to burn. - if (sharesToBurn != 0) { - _burn(S, address(this), sharesToBurn); - } - } - - // Update unlocking rate and time to fully unlocked. - uint256 totalLockedShares = S.balances[address(this)]; - if (totalLockedShares != 0) { - uint256 previouslyLockedTime; - uint96 _fullProfitUnlockDate = S.fullProfitUnlockDate; - // Check if we need to account for shares still unlocking. - if (_fullProfitUnlockDate > block.timestamp) { - unchecked { - // There will only be previously locked shares if time remains. - // We calculate this here since it should be rare. - previouslyLockedTime = - (_fullProfitUnlockDate - block.timestamp) * - (totalLockedShares - sharesToLock); - } - } - - // newProfitLockingPeriod is a weighted average between the remaining - // time of the previously locked shares and the profitMaxUnlockTime. - uint256 newProfitLockingPeriod = (previouslyLockedTime + - sharesToLock * - _profitMaxUnlockTime) / totalLockedShares; - - // Calculate how many shares unlock per second. - S.profitUnlockingRate = - (totalLockedShares * MAX_BPS_EXTENDED) / - newProfitLockingPeriod; - - // Calculate how long until the full amount of shares is unlocked. - S.fullProfitUnlockDate = uint96( - block.timestamp + newProfitLockingPeriod - ); - } else { - // Only setting this to 0 will turn in the desired effect, - // no need to update profitUnlockingRate. - S.fullProfitUnlockDate = 0; - } - - // Update the new total assets value. - S.totalAssets = newTotalAssets; - S.lastReport = uint96(block.timestamp); - - // Emit event with info - emit Reported( - profit, - loss, - protocolFees, // Protocol fees - totalFees - protocolFees // Performance Fees - ); - } - - /** - * @notice Get how many shares have been unlocked since last report. - * @return . The amount of shares that have unlocked. - */ - function unlockedShares() external view returns (uint256) { - return _unlockedShares(_strategyStorage()); - } - - /** - * @dev To determine how many of the shares that were locked during the last - * report have since unlocked. - * - * If the `fullProfitUnlockDate` has passed the full strategy's balance will - * count as unlocked. - * - * @return unlocked The amount of shares that have unlocked. - */ - function _unlockedShares( - StrategyData storage S - ) internal view returns (uint256 unlocked) { - uint96 _fullProfitUnlockDate = S.fullProfitUnlockDate; - if (_fullProfitUnlockDate > block.timestamp) { - unchecked { - unlocked = - (S.profitUnlockingRate * (block.timestamp - S.lastReport)) / - MAX_BPS_EXTENDED; - } - } else if (_fullProfitUnlockDate != 0) { - // All shares have been unlocked. - unlocked = S.balances[address(this)]; - } - } - - /*////////////////////////////////////////////////////////////// - TENDING - //////////////////////////////////////////////////////////////*/ - - /** - * @notice For a 'keeper' to 'tend' the strategy if a custom - * tendTrigger() is implemented. - * - * @dev Both 'tendTrigger' and '_tend' will need to be overridden - * for this to be used. - * - * This will callback the internal '_tend' call in the BaseStrategy - * with the total current amount available to the strategy to deploy. - * - * This is a permissioned function so if desired it could - * be used for illiquid or manipulatable strategies to compound - * rewards, perform maintenance or deposit/withdraw funds. - * - * This will not cause any change in PPS. Total assets will - * be the same before and after. - * - * A report() call will be needed to record any profits or losses. - */ - function tend() external nonReentrant onlyKeepers { - // Tend the strategy with the current loose balance. - IBaseStrategy(address(this)).tendThis( - _strategyStorage().asset.balanceOf(address(this)) - ); - } - - /*////////////////////////////////////////////////////////////// - STRATEGY SHUTDOWN - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Used to shutdown the strategy preventing any further deposits. - * @dev Can only be called by the current `management` or `emergencyAdmin`. - * - * This will stop any new {deposit} or {mint} calls but will - * not prevent {withdraw} or {redeem}. It will also still allow for - * {tend} and {report} so that management can report any last losses - * in an emergency as well as provide any maintenance to allow for full - * withdraw. - * - * This is a one way switch and can never be set back once shutdown. - */ - function shutdownStrategy() external onlyEmergencyAuthorized { - _strategyStorage().shutdown = true; - - emit StrategyShutdown(); - } - - /** - * @notice To manually withdraw funds from the yield source after a - * strategy has been shutdown. - * @dev This can only be called post {shutdownStrategy}. - * - * This will never cause a change in PPS. Total assets will - * be the same before and after. - * - * A strategist will need to override the {_emergencyWithdraw} function - * in their strategy for this to work. - * - * @param amount The amount of asset to attempt to free. - */ - function emergencyWithdraw( - uint256 amount - ) external nonReentrant onlyEmergencyAuthorized { - // Make sure the strategy has been shutdown. - require(_strategyStorage().shutdown, "not shutdown"); - - // Withdraw from the yield source. - IBaseStrategy(address(this)).shutdownWithdraw(amount); - } - - /*////////////////////////////////////////////////////////////// - GETTER FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Get the underlying asset for the strategy. - * @return . The underlying asset. - */ - function asset() external view returns (address) { - return address(_strategyStorage().asset); - } - - /** - * @notice Get the API version for this TokenizedStrategy. - * @return . The API version for this TokenizedStrategy - */ - function apiVersion() external pure returns (string memory) { - return API_VERSION; - } - - /** - * @notice Get the current address that controls the strategy. - * @return . Address of management - */ - function management() external view returns (address) { - return _strategyStorage().management; - } - - /** - * @notice Get the current pending management address if any. - * @return . Address of pendingManagement - */ - function pendingManagement() external view returns (address) { - return _strategyStorage().pendingManagement; - } - - /** - * @notice Get the current address that can call tend and report. - * @return . Address of the keeper - */ - function keeper() external view returns (address) { - return _strategyStorage().keeper; - } - - /** - * @notice Get the current address that can shutdown and emergency withdraw. - * @return . Address of the emergencyAdmin - */ - function emergencyAdmin() external view returns (address) { - return _strategyStorage().emergencyAdmin; - } - - /** - * @notice Get the current performance fee charged on profits. - * denominated in Basis Points where 10_000 == 100% - * @return . Current performance fee. - */ - function performanceFee() external view returns (uint16) { - return _strategyStorage().performanceFee; - } - - /** - * @notice Get the current address that receives the performance fees. - * @return . Address of performanceFeeRecipient - */ - function performanceFeeRecipient() external view returns (address) { - return _strategyStorage().performanceFeeRecipient; - } - - /** - * @notice Gets the timestamp at which all profits will be unlocked. - * @return . The full profit unlocking timestamp - */ - function fullProfitUnlockDate() external view returns (uint256) { - return uint256(_strategyStorage().fullProfitUnlockDate); - } - - /** - * @notice The per second rate at which profits are unlocking. - * @dev This is denominated in EXTENDED_BPS decimals. - * @return . The current profit unlocking rate. - */ - function profitUnlockingRate() external view returns (uint256) { - return _strategyStorage().profitUnlockingRate; - } - - /** - * @notice Gets the current time profits are set to unlock over. - * @return . The current profit max unlock time. - */ - function profitMaxUnlockTime() external view returns (uint256) { - return _strategyStorage().profitMaxUnlockTime; - } - - /** - * @notice The timestamp of the last time protocol fees were charged. - * @return . The last report. - */ - function lastReport() external view returns (uint256) { - return uint256(_strategyStorage().lastReport); - } - - /** - * @notice Get the price per share. - * @dev This value offers limited precision. Integrations that require - * exact precision should use convertToAssets or convertToShares instead. - * - * @return . The price per share. - */ - function pricePerShare() external view returns (uint256) { - StrategyData storage S = _strategyStorage(); - return _convertToAssets(S, 10 ** S.decimals, Math.Rounding.Down); - } - - /** - * @notice To check if the strategy has been shutdown. - * @return . Whether or not the strategy is shutdown. - */ - function isShutdown() external view returns (bool) { - return _strategyStorage().shutdown; - } - - /*////////////////////////////////////////////////////////////// - SETTER FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Step one of two to set a new address to be in charge of the strategy. - * @dev Can only be called by the current `management`. The address is - * set to pending management and will then have to call {acceptManagement} - * in order for the 'management' to officially change. - * - * Cannot set `management` to address(0). - * - * @param _management New address to set `pendingManagement` to. - */ - function setPendingManagement(address _management) external onlyManagement { - require(_management != address(0), "ZERO ADDRESS"); - _strategyStorage().pendingManagement = _management; - - emit UpdatePendingManagement(_management); - } - - /** - * @notice Step two of two to set a new 'management' of the strategy. - * @dev Can only be called by the current `pendingManagement`. - */ - function acceptManagement() external { - StrategyData storage S = _strategyStorage(); - require(msg.sender == S.pendingManagement, "!pending"); - S.management = msg.sender; - S.pendingManagement = address(0); - - emit UpdateManagement(msg.sender); - } - - /** - * @notice Sets a new address to be in charge of tend and reports. - * @dev Can only be called by the current `management`. - * - * @param _keeper New address to set `keeper` to. - */ - function setKeeper(address _keeper) external onlyManagement { - _strategyStorage().keeper = _keeper; - - emit UpdateKeeper(_keeper); - } - - /** - * @notice Sets a new address to be able to shutdown the strategy. - * @dev Can only be called by the current `management`. - * - * @param _emergencyAdmin New address to set `emergencyAdmin` to. - */ - function setEmergencyAdmin( - address _emergencyAdmin - ) external onlyManagement { - _strategyStorage().emergencyAdmin = _emergencyAdmin; - - emit UpdateEmergencyAdmin(_emergencyAdmin); - } - - /** - * @notice Sets the performance fee to be charged on reported gains. - * @dev Can only be called by the current `management`. - * - * Denominated in Basis Points. So 100% == 10_000. - * Cannot set greater than to MAX_FEE. - * - * @param _performanceFee New performance fee. - */ - function setPerformanceFee(uint16 _performanceFee) external onlyManagement { - require(_performanceFee <= MAX_FEE, "MAX FEE"); - _strategyStorage().performanceFee = _performanceFee; - - emit UpdatePerformanceFee(_performanceFee); - } - - /** - * @notice Sets a new address to receive performance fees. - * @dev Can only be called by the current `management`. - * - * Cannot set to address(0). - * - * @param _performanceFeeRecipient New address to set `management` to. - */ - function setPerformanceFeeRecipient( - address _performanceFeeRecipient - ) external onlyManagement { - require(_performanceFeeRecipient != address(0), "ZERO ADDRESS"); - require(_performanceFeeRecipient != address(this), "Cannot be self"); - _strategyStorage().performanceFeeRecipient = _performanceFeeRecipient; - - emit UpdatePerformanceFeeRecipient(_performanceFeeRecipient); - } - - /** - * @notice Sets the time for profits to be unlocked over. - * @dev Can only be called by the current `management`. - * - * Denominated in seconds and cannot be greater than 1 year. - * - * NOTE: Setting to 0 will cause all currently locked profit - * to be unlocked instantly and should be done with care. - * - * `profitMaxUnlockTime` is stored as a uint32 for packing but can - * be passed in as uint256 for simplicity. - * - * @param _profitMaxUnlockTime New `profitMaxUnlockTime`. - */ - function setProfitMaxUnlockTime( - uint256 _profitMaxUnlockTime - ) external onlyManagement { - // Must be less than a year. - require(_profitMaxUnlockTime <= SECONDS_PER_YEAR, "too long"); - StrategyData storage S = _strategyStorage(); - - // If we are setting to 0 we need to adjust amounts. - if (_profitMaxUnlockTime == 0) { - uint256 shares = S.balances[address(this)]; - if (shares != 0) { - // Burn all shares if applicable. - _burn(S, address(this), shares); - } - // Reset unlocking variables - S.profitUnlockingRate = 0; - S.fullProfitUnlockDate = 0; - } - - S.profitMaxUnlockTime = uint32(_profitMaxUnlockTime); - - emit UpdateProfitMaxUnlockTime(_profitMaxUnlockTime); - } - - /** - * @notice Updates the name for the strategy. - * @param _name The new name for the strategy. - */ - function setName(string calldata _name) external onlyManagement { - _strategyStorage().name = _name; - } - - /*////////////////////////////////////////////////////////////// - ERC20 METHODS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Returns the name of the token. - * @return . The name the strategy is using for its token. - */ - function name() external view returns (string memory) { - return _strategyStorage().name; - } - - /** - * @notice Returns the symbol of the strategies token. - * @dev Will be 'ys + asset symbol'. - * @return . The symbol the strategy is using for its tokens. - */ - function symbol() external view returns (string memory) { - return - string(abi.encodePacked("ys", _strategyStorage().asset.symbol())); - } - - /** - * @notice Returns the number of decimals used to get its user representation. - * @return . The decimals used for the strategy and `asset`. - */ - function decimals() external view returns (uint8) { - return _strategyStorage().decimals; - } - - /** - * @notice Returns the current balance for a given '_account'. - * @dev If the '_account` is the strategy then this will subtract - * the amount of shares that have been unlocked since the last profit first. - * @param account the address to return the balance for. - * @return . The current balance in y shares of the '_account'. - */ - function balanceOf(address account) external view returns (uint256) { - return _balanceOf(_strategyStorage(), account); - } - - /// @dev Internal implementation of {balanceOf}. - function _balanceOf( - StrategyData storage S, - address account - ) internal view returns (uint256) { - if (account == address(this)) { - return S.balances[account] - _unlockedShares(S); - } - return S.balances[account]; - } - - /** - * @notice Transfer '_amount` of shares from `msg.sender` to `to`. - * @dev - * Requirements: - * - * - `to` cannot be the zero address. - * - `to` cannot be the address of the strategy. - * - the caller must have a balance of at least `_amount`. - * - * @param to The address shares will be transferred to. - * @param amount The amount of shares to be transferred from sender. - * @return . a boolean value indicating whether the operation succeeded. - */ - function transfer(address to, uint256 amount) external returns (bool) { - _transfer(_strategyStorage(), msg.sender, to, amount); - return true; - } - - /** - * @notice 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. - * @param owner The address who owns the shares. - * @param spender The address who would be moving the owners shares. - * @return . The remaining amount of shares of `owner` that could be moved by `spender`. - */ - function allowance( - address owner, - address spender - ) external view returns (uint256) { - return _allowance(_strategyStorage(), owner, spender); - } - - /// @dev Internal implementation of {allowance}. - function _allowance( - StrategyData storage S, - address owner, - address spender - ) internal view returns (uint256) { - return S.allowances[owner][spender]; - } - - /** - * @notice Sets `amount` as the allowance of `spender` over the caller's tokens. - * @dev - * - * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on - * `transferFrom`. This is semantically equivalent to an infinite approval. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - * 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. - * - * @param spender the address to allow the shares to be moved by. - * @param amount the amount of shares to allow `spender` to move. - * @return . a boolean value indicating whether the operation succeeded. - */ - function approve(address spender, uint256 amount) external returns (bool) { - _approve(_strategyStorage(), msg.sender, spender, amount); - return true; - } - - /** - * @notice `amount` tokens from `from` to `to` using the - * allowance mechanism. `amount` is then deducted from the caller's - * allowance. - * - * @dev - * Emits an {Approval} event indicating the updated allowance. This is not - * required by the EIP. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Requirements: - * - * - `from` and `to` cannot be the zero address. - * - `to` cannot be the address of the strategy. - * - `from` must have a balance of at least `amount`. - * - the caller must have allowance for ``from``'s tokens of at least - * `amount`. - * - * Emits a {Transfer} event. - * - * @param from the address to be moving shares from. - * @param to the address to be moving shares to. - * @param amount the quantity of shares to move. - * @return . a boolean value indicating whether the operation succeeded. - */ - function transferFrom( - address from, - address to, - uint256 amount - ) external returns (bool) { - StrategyData storage S = _strategyStorage(); - _spendAllowance(S, from, msg.sender, amount); - _transfer(S, from, to, amount); - return true; - } - - /** - * @dev Moves `amount` of tokens from `from` to `to`. - * - * 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: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `to` cannot be the strategies address - * - `from` must have a balance of at least `amount`. - * - */ - function _transfer( - StrategyData storage S, - address from, - address to, - uint256 amount - ) internal { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); - require(to != address(this), "ERC20 transfer to strategy"); - - S.balances[from] -= amount; - unchecked { - S.balances[to] += amount; - } - - emit Transfer(from, to, 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( - StrategyData storage S, - address account, - uint256 amount - ) internal { - require(account != address(0), "ERC20: mint to the zero address"); - - S.totalSupply += amount; - unchecked { - S.balances[account] += amount; - } - emit Transfer(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( - StrategyData storage S, - address account, - uint256 amount - ) internal { - require(account != address(0), "ERC20: burn from the zero address"); - - S.balances[account] -= amount; - unchecked { - S.totalSupply -= amount; - } - emit Transfer(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( - StrategyData storage S, - address owner, - address spender, - uint256 amount - ) internal { - require(owner != address(0), "ERC20: approve from the zero address"); - require(spender != address(0), "ERC20: approve to the zero address"); - - S.allowances[owner][spender] = amount; - emit Approval(owner, spender, amount); - } - - /** - * @dev Updates `owner` s allowance for `spender` based on spent `amount`. - * - * Does not update the allowance amount in case of infinite allowance. - * Revert if not enough allowance is available. - * - * Might emit an {Approval} event. - */ - function _spendAllowance( - StrategyData storage S, - address owner, - address spender, - uint256 amount - ) internal { - uint256 currentAllowance = _allowance(S, owner, spender); - if (currentAllowance != type(uint256).max) { - require( - currentAllowance >= amount, - "ERC20: insufficient allowance" - ); - unchecked { - _approve(S, owner, spender, currentAllowance - amount); - } - } - } - - /*////////////////////////////////////////////////////////////// - EIP-2612 LOGIC - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Returns the current nonce for `owner`. This value must be - * included whenever a signature is generated for {permit}. - * - * @dev Every successful call to {permit} increases ``owner``'s nonce by one. This - * prevents a signature from being used multiple times. - * - * @param _owner the address of the account to return the nonce for. - * @return . the current nonce for the account. - */ - function nonces(address _owner) external view returns (uint256) { - return _strategyStorage().nonces[_owner]; - } - - /** - * @notice Sets `value` as the allowance of `spender` over ``owner``'s tokens, - * given ``owner``'s signed approval. - * - * @dev IMPORTANT: The same issues {IERC20-approve} has related to transaction - * ordering also apply here. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `deadline` must be a timestamp in the future. - * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` - * over the EIP712-formatted function arguments. - * - the signature must use ``owner``'s current nonce (see {nonces}). - * - * For more information on the signature format, see the - * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP - * section]. - */ - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external { - require(deadline >= block.timestamp, "ERC20: PERMIT_DEADLINE_EXPIRED"); - - // Unchecked because the only math done is incrementing - // the owner's nonce which cannot realistically overflow. - unchecked { - address recoveredAddress = ecrecover( - keccak256( - abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR(), - keccak256( - abi.encode( - keccak256( - "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" - ), - owner, - spender, - value, - _strategyStorage().nonces[owner]++, - deadline - ) - ) - ) - ), - v, - r, - s - ); - - require( - recoveredAddress != address(0) && recoveredAddress == owner, - "ERC20: INVALID_SIGNER" - ); - - _approve(_strategyStorage(), recoveredAddress, spender, value); - } - } - - /** - * @notice Returns the domain separator used in the encoding of the signature - * for {permit}, as defined by {EIP712}. - * - * @return . The domain separator that will be used for any {permit} calls. - */ - function DOMAIN_SEPARATOR() public view returns (bytes32) { - return - keccak256( - abi.encode( - keccak256( - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - ), - keccak256("Yearn Vault"), - keccak256(bytes(API_VERSION)), - block.chainid, - address(this) - ) - ); - } - - /*////////////////////////////////////////////////////////////// - DEPLOYMENT - //////////////////////////////////////////////////////////////*/ - - /** - * @dev On contract creation we set `asset` for this contract to address(1). - * This prevents it from ever being initialized in the future. - * @param _factory Address of the factory of the same version for protocol fees. - */ - constructor(address _factory) { - FACTORY = _factory; - _strategyStorage().asset = ERC20(address(1)); - } - } diff --git a/contracts/yearn/Vault.vy b/contracts/yearn/Vault.vy index 72b69f3..12e67ed 100644 --- a/contracts/yearn/Vault.vy +++ b/contracts/yearn/Vault.vy @@ -130,6 +130,9 @@ event UpdateDefaultQueue: event UpdateUseDefaultQueue: use_default_queue: bool +event UpdateAutoAllocate: + auto_allocate: bool + event UpdatedMaxDebtForStrategy: sender: indexed(address) strategy: indexed(address) @@ -170,7 +173,7 @@ 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" +API_VERSION: constant(String[28]) = "3.0.3" # ENUMS # # Each permissioned function has its own Role. @@ -214,6 +217,8 @@ strategies: public(HashMap[address, StrategyParams]) default_queue: public(DynArray[address, MAX_QUEUE]) # Should the vault use the default_queue regardless whats passed in. use_default_queue: public(bool) +# Should the vault automatically allocate funds to the first strategy in queue. +auto_allocate: public(bool) ### ACCOUNTING ### # ERC20 - amount of shares per account @@ -457,15 +462,16 @@ def _convert_to_shares(assets: uint256, rounding: Rounding) -> uint256: return assets total_supply: uint256 = self._total_supply() + + # if total_supply is 0, price_per_share is 1 + if total_supply == 0: + return assets + total_assets: uint256 = self._total_assets() + # if total_Supply > 0 but total_assets == 0, price_per_share = 0 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 + return 0 numerator: uint256 = assets * total_supply shares: uint256 = numerator / total_assets @@ -499,31 +505,6 @@ def _issue_shares(shares: uint256, recipient: address): 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 @@ -602,16 +583,17 @@ def _max_withdraw( # Can't use an invalid strategy. assert self.strategies[strategy].activation != 0, "inactive strategy" + current_debt: uint256 = self.strategies[strategy].current_debt # 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 + current_debt ) # Get any unrealised loss for the strategy. - unrealised_loss: uint256 = self._assess_share_of_unrealised_losses(strategy, to_withdraw) + unrealised_loss: uint256 = self._assess_share_of_unrealised_losses(strategy, current_debt, to_withdraw) # See if any limit is enforced by the strategy. strategy_limit: uint256 = IStrategy(strategy).convertToAssets( @@ -656,41 +638,15 @@ def _max_withdraw( return max_assets @internal -def _deposit(sender: address, recipient: address, assets: uint256) -> uint256: +def _deposit(recipient: address, assets: uint256, shares: 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 + Used for `deposit` and `mint` 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" + assert shares > 0, "cannot mint zero" # Transfer the tokens to the vault first. self._erc20_safe_transfer_from(self.asset, msg.sender, self, assets) @@ -700,12 +656,14 @@ def _mint(sender: address, recipient: address, shares: uint256) -> uint256: # Issue the corresponding shares for assets. self._issue_shares(shares, recipient) - log Deposit(sender, recipient, assets, shares) - return assets + log Deposit(msg.sender, recipient, assets, shares) + + if self.auto_allocate: + self._update_debt(self.default_queue[0], max_value(uint256), 0) @view @internal -def _assess_share_of_unrealised_losses(strategy: address, assets_needed: uint256) -> uint256: +def _assess_share_of_unrealised_losses(strategy: address, strategy_current_debt: uint256, 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 @@ -714,8 +672,6 @@ def _assess_share_of_unrealised_losses(strategy: address, assets_needed: uint256 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) @@ -844,7 +800,7 @@ def _redeem( # 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) + unrealised_losses_share: uint256 = self._assess_share_of_unrealised_losses(strategy, current_debt, 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. @@ -1048,14 +1004,16 @@ def _update_debt(strategy: address, target_debt: uint256, max_loss: uint256) -> 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 assets_to_withdraw == 0: + return current_debt + # 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) + unrealised_losses_share: uint256 = self._assess_share_of_unrealised_losses(strategy, current_debt, assets_to_withdraw) assert unrealised_losses_share == 0, "strategy has unrealised losses" # Cache for repeated use. @@ -1088,12 +1046,18 @@ def _update_debt(strategy: address, target_debt: uint256, max_loss: uint256) -> 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" + # Respect the maximum amount allowed. + max_debt: uint256 = self.strategies[strategy].max_debt + if new_debt > max_debt: + new_debt = max_debt + # Possible for current to be greater than max from reports. + if new_debt < current_debt: + return current_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" + if max_deposit == 0: + return current_debt # Deposit the difference between desired and current. assets_to_deposit: uint256 = new_debt - current_debt @@ -1105,7 +1069,9 @@ def _update_debt(strategy: address, target_debt: uint256, max_loss: uint256) -> minimum_total_idle: uint256 = self.minimum_total_idle total_idle: uint256 = self.total_idle - assert total_idle > minimum_total_idle, "no funds to deposit" + if total_idle <= minimum_total_idle: + return current_debt + available_idle: uint256 = unsafe_sub(total_idle, minimum_total_idle) # If insufficient funds to deposit, transfer only what is free. @@ -1162,18 +1128,32 @@ def _process_report(strategy: address) -> (uint256, uint256): Any applicable fees are charged and distributed during the report as well to the specified recipients. + + Can update the vaults `totalIdle` to account for any airdropped tokens by + passing the vaults address in as the parameter. """ - # Make sure we have a valid strategy. - assert self.strategies[strategy].activation != 0, "inactive strategy" + # Cache `asset` for repeated use. + _asset: address = self.asset - # 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 + total_assets: uint256 = 0 + current_debt: uint256 = 0 + + if strategy != self: + # 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 = IStrategy(strategy).convertToAssets(strategy_shares) + # How much the vault had deposited to the strategy. + current_debt = self.strategies[strategy].current_debt + else: + # Accrue any airdropped `asset` into `total_idle` + total_assets = ERC20(_asset).balanceOf(self) + current_debt = self.total_idle gain: uint256 = 0 loss: uint256 = 0 @@ -1188,9 +1168,6 @@ def _process_report(strategy: address) -> (uint256, uint256): # 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. @@ -1276,14 +1253,20 @@ def _process_report(strategy: address) -> (uint256, uint256): 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 + if strategy != self: + self.strategies[strategy].current_debt = current_debt + self.total_debt += gain + else: + self.total_idle = total_assets # 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 + if strategy != self: + self.strategies[strategy].current_debt = current_debt + self.total_debt -= loss + else: + self.total_idle = total_assets # Issue shares for fees that were calculated above if applicable. if total_fees_shares > 0: @@ -1338,6 +1321,27 @@ def _process_report(strategy: address) -> (uint256, uint256): return (gain, loss) # SETTERS # + +@external +def setName(name: String[64]): + """ + @notice Change the vault name. + @dev Can only be called by the Role Manager. + @param name The new name for the vault. + """ + assert msg.sender == self.role_manager, "not allowed" + self.name = name + +@external +def setSymbol(symbol: String[32]): + """ + @notice Change the vault symbol. + @dev Can only be called by the Role Manager. + @param symbol The new name for the vault. + """ + assert msg.sender == self.role_manager, "not allowed" + self.symbol = symbol + @external def set_accountant(new_accountant: address): """ @@ -1382,6 +1386,21 @@ def set_use_default_queue(use_default_queue: bool): log UpdateUseDefaultQueue(use_default_queue) +@external +def set_auto_allocate(auto_allocate: bool): + """ + @notice Set new value for `auto_allocate` + @dev If `True` every {deposit} and {mint} call will + try and allocate the deposited amount to the strategy + at position 0 of the `default_queue` atomically. + NOTE: An empty `default_queue` will cause deposits to fail. + @param auto_allocate new value. + """ + self._enforce_role(msg.sender, Roles.DEBT_MANAGER) + self.auto_allocate = auto_allocate + + log UpdateAutoAllocate(auto_allocate) + @external def set_deposit_limit(deposit_limit: uint256, override: bool = False): """ @@ -1723,6 +1742,7 @@ def update_debt( ) -> uint256: """ @notice Update the debt for a strategy. + @dev Pass max uint256 to allocate as much idle as possible. @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. @@ -1763,11 +1783,19 @@ def shutdown_vault(): def deposit(assets: uint256, receiver: address) -> uint256: """ @notice Deposit assets into the vault. + @dev Pass max uint256 to deposit full asset balance. @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) + amount: uint256 = assets + # Deposit all if sent with max uint + if amount == max_value(uint256): + amount = ERC20(self.asset).balanceOf(msg.sender) + + shares: uint256 = self._convert_to_shares(amount, Rounding.ROUND_DOWN) + self._deposit(receiver, amount, shares) + return shares @external @nonreentrant("lock") @@ -1778,7 +1806,9 @@ def mint(shares: uint256, receiver: address) -> uint256: @param receiver The address to receive the shares. @return The amount of assets deposited. """ - return self._mint(msg.sender, receiver, shares) + assets: uint256 = self._convert_to_assets(shares, Rounding.ROUND_UP) + self._deposit(receiver, assets, shares) + return assets @external @nonreentrant("lock") @@ -2087,9 +2117,10 @@ def assess_share_of_unrealised_losses(strategy: address, assets_needed: uint256) @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 + current_debt: uint256 = self.strategies[strategy].current_debt + assert current_debt >= assets_needed - return self._assess_share_of_unrealised_losses(strategy, assets_needed) + return self._assess_share_of_unrealised_losses(strategy, current_debt, assets_needed) ## Profit locking getter functions ## diff --git a/contracts/yearn/VaultFactory.vy b/contracts/yearn/VaultFactory.vy index 19ef2b5..d03363f 100644 --- a/contracts/yearn/VaultFactory.vy +++ b/contracts/yearn/VaultFactory.vy @@ -29,54 +29,65 @@ fee recipient. """ + interface IVault: def initialize( asset: address, name: String[64], symbol: String[32], role_manager: address, - profit_max_unlock_time: uint256 + profit_max_unlock_time: uint256, ): nonpayable + event NewVault: vault_address: indexed(address) asset: indexed(address) + event UpdateProtocolFeeBps: old_fee_bps: uint16 new_fee_bps: uint16 + event UpdateProtocolFeeRecipient: old_fee_recipient: indexed(address) new_fee_recipient: indexed(address) + event UpdateCustomProtocolFee: vault: indexed(address) new_custom_protocol_fee: uint16 + event RemovedCustomProtocolFee: vault: indexed(address) + event FactoryShutdown: pass + event UpdateGovernance: governance: indexed(address) + event NewPendingGovernance: pending_governance: indexed(address) + struct PFConfig: # Percent of protocol's split of fees in Basis Points. fee_bps: uint16 # Address the protocol fees get paid to. fee_recipient: address + # Identifier for this version of the vault. API_VERSION: constant(String[28]) = "3.0.2" # The max amount the protocol fee can be set to. -MAX_FEE_BPS: constant(uint16) = 5_000 # 50% +MAX_FEE_BPS: constant(uint16) = 5_000 # 50% # The address that all newly deployed vaults are based from. VAULT_ORIGINAL: immutable(address) @@ -99,19 +110,21 @@ custom_protocol_fee: public(HashMap[address, uint16]) # Represents if a custom protocol fee should be used. use_custom_protocol_fee: public(HashMap[address, bool]) + @external def __init__(name: String[64], vault_original: address, governance: address): self.name = name VAULT_ORIGINAL = vault_original self.governance = governance + @external def deploy_new_vault( asset: address, name: String[64], symbol: String[32], role_manager: address, - profit_max_unlock_time: uint256 + profit_max_unlock_time: uint256, ) -> address: """ @notice Deploys a new clone of the original vault. @@ -127,10 +140,10 @@ def deploy_new_vault( # Clone a new version of the vault using create2. vault_address: address = create_minimal_proxy_to( - VAULT_ORIGINAL, - value=0, - salt=keccak256(_abi_encode(msg.sender, asset, name, symbol)) - ) + VAULT_ORIGINAL, + value=0, + salt=keccak256(_abi_encode(msg.sender, asset, name, symbol)), + ) IVault(vault_address).initialize( asset, @@ -143,15 +156,17 @@ def deploy_new_vault( log NewVault(vault_address, asset) return vault_address + @view @external -def vault_original()-> address: +def vault_original() -> address: """ @notice Get the address of the vault to clone from @return The address of the original vault. """ return VAULT_ORIGINAL + @view @external def apiVersion() -> String[28]: @@ -161,6 +176,7 @@ def apiVersion() -> String[28]: """ return API_VERSION + @view @external def protocol_fee_config(vault: address = msg.sender) -> PFConfig: @@ -174,14 +190,17 @@ def protocol_fee_config(vault: address = msg.sender) -> PFConfig: # If there is a custom protocol fee set we return it. if self.use_custom_protocol_fee[vault]: # Always use the default fee recipient even with custom fees. - return PFConfig({ - fee_bps: self.custom_protocol_fee[vault], - fee_recipient: self.default_protocol_fee_config.fee_recipient - }) + return PFConfig( + { + fee_bps: self.custom_protocol_fee[vault], + fee_recipient: self.default_protocol_fee_config.fee_recipient, + } + ) else: # Otherwise return the default config. return self.default_protocol_fee_config + @external def set_protocol_fee_bps(new_protocol_fee_bps: uint16): """ @@ -200,10 +219,7 @@ def set_protocol_fee_bps(new_protocol_fee_bps: uint16): # Set the new fee self.default_protocol_fee_config.fee_bps = new_protocol_fee_bps - log UpdateProtocolFeeBps( - default_config.fee_bps, - new_protocol_fee_bps - ) + log UpdateProtocolFeeBps(default_config.fee_bps, new_protocol_fee_bps) @external @@ -220,14 +236,13 @@ def set_protocol_fee_recipient(new_protocol_fee_recipient: address): self.default_protocol_fee_config.fee_recipient = new_protocol_fee_recipient - log UpdateProtocolFeeRecipient( - old_recipient, - new_protocol_fee_recipient - ) + log UpdateProtocolFeeRecipient(old_recipient, new_protocol_fee_recipient) @external -def set_custom_protocol_fee_bps(vault: address, new_custom_protocol_fee: uint16): +def set_custom_protocol_fee_bps( + vault: address, new_custom_protocol_fee: uint16 +): """ @notice Allows Governance to set custom protocol fees for a specific vault or strategy. @@ -238,7 +253,9 @@ def set_custom_protocol_fee_bps(vault: address, new_custom_protocol_fee: uint16) """ assert msg.sender == self.governance, "not governance" assert new_custom_protocol_fee <= MAX_FEE_BPS, "fee too high" - assert self.default_protocol_fee_config.fee_recipient != empty(address), "no recipient" + assert self.default_protocol_fee_config.fee_recipient != empty( + address + ), "no recipient" self.custom_protocol_fee[vault] = new_custom_protocol_fee @@ -249,6 +266,7 @@ def set_custom_protocol_fee_bps(vault: address, new_custom_protocol_fee: uint16) log UpdateCustomProtocolFee(vault, new_custom_protocol_fee) + @external def remove_custom_protocol_fee(vault: address): """ @@ -267,6 +285,7 @@ def remove_custom_protocol_fee(vault: address): log RemovedCustomProtocolFee(vault) + @external def shutdown_factory(): """ @@ -283,6 +302,7 @@ def shutdown_factory(): log FactoryShutdown() + @external def set_governance(new_governance: address): """ @@ -294,6 +314,7 @@ def set_governance(new_governance: address): log NewPendingGovernance(new_governance) + @external def accept_governance(): """ diff --git a/poetry.lock b/poetry.lock index 3ac895b..3847d5b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -401,35 +401,15 @@ webencodings = "*" [package.extras] css = ["tinycss2 (>=1.1.0,<1.3)"] -[[package]] -name = "boa-solidity" -version = "0.1.1" -description = "Solidity support for Titanoboa" -optional = false -python-versions = "^3.10" -files = [] -develop = false - -[package.dependencies] -eth-abi = "^5.0.1" -py-solc-x = "^2.0.2" -titanoboa = "^0.2.0" - -[package.source] -type = "git" -url = "https://github.com/AlbertoCentonze/boa-solidity" -reference = "500d2e9a6950eb3193c5a6a1d4637cd3e3befa77" -resolved_reference = "500d2e9a6950eb3193c5a6a1d4637cd3e3befa77" - [[package]] name = "build" -version = "1.2.2.post1" +version = "1.2.2" description = "A simple, correct Python build frontend" optional = false python-versions = ">=3.8" files = [ - {file = "build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5"}, - {file = "build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7"}, + {file = "build-1.2.2-py3-none-any.whl", hash = "sha256:277ccc71619d98afdd841a0e96ac9fe1593b823af481d3b0cea748e8894e0613"}, + {file = "build-1.2.2.tar.gz", hash = "sha256:119b2fb462adef986483438377a13b2f42064a2a3a4161f24a0cca698a07ac8c"}, ] [package.dependencies] @@ -1053,97 +1033,115 @@ titanoboa = ">=0.1.10b1" [[package]] name = "cytoolz" -version = "1.0.0" +version = "0.12.3" description = "Cython implementation of Toolz: High performance functional utilities" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "cytoolz-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ecf5a887acb8f079ab1b81612b1c889bcbe6611aa7804fd2df46ed310aa5a345"}, - {file = "cytoolz-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0ef30c1e091d4d59d14d8108a16d50bd227be5d52a47da891da5019ac2f8e4"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7df2dfd679f0517a96ced1cdd22f5c6c6aeeed28d928a82a02bf4c3fd6fd7ac4"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c51452c938e610f57551aa96e34924169c9100c0448bac88c2fb395cbd3538c"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6433f03910c5e5345d82d6299457c26bf33821224ebb837c6b09d9cdbc414a6c"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:389ec328bb535f09e71dfe658bf0041f17194ca4cedaacd39bafe7893497a819"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c64658e1209517ce4b54c1c9269a508b289d8d55fc742760e4b8579eacf09a33"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f6039a9bd5bb988762458b9ca82b39e60ca5e5baae2ba93913990dcc5d19fa88"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:85c9c8c4465ed1b2c8d67003809aec9627b129cb531d2f6cf0bbfe39952e7e4d"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:49375aad431d76650f94877afb92f09f58b6ff9055079ef4f2cd55313f5a1b39"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4c45106171c824a61e755355520b646cb35a1987b34bbf5789443823ee137f63"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3b319a7f0fed5db07d189db4046162ebc183c108df3562a65ba6ebe862d1f634"}, - {file = "cytoolz-1.0.0-cp310-cp310-win32.whl", hash = "sha256:9770e1b09748ad0d751853d994991e2592a9f8c464a87014365f80dac2e83faa"}, - {file = "cytoolz-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:20194dd02954c00c1f0755e636be75a20781f91a4ac9270c7f747e82d3c7f5a5"}, - {file = "cytoolz-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dffc22fd2c91be64dbdbc462d0786f8e8ac9a275cfa1869a1084d1867d4f67e0"}, - {file = "cytoolz-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a99e7e29274e293f4ffe20e07f76c2ac753a78f1b40c1828dfc54b2981b2f6c4"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c507a3e0a45c41d66b43f96797290d75d1e7a8549aa03a4a6b8854fdf3f7b8d8"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:643a593ec272ef7429099e1182a22f64ec2696c00d295d2a5be390db1b7ff176"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6ce38e2e42cbae30446190c59b92a8a9029e1806fd79eaf88f48b0fe33003893"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810a6a168b8c5ecb412fbae3dd6f7ed6c6253a63caf4174ee9794ebd29b2224f"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ce8a2a85c0741c1b19b16e6782c4a5abc54c3caecda66793447112ab2fa9884"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ea4ac72e6b830861035c4c7999af8e55813f57c6d1913a3d93cc4a6babc27bf7"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a09cdfb21dfb38aa04df43e7546a41f673377eb5485da88ceb784e327ec7603b"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:658dd85deb375ff7af990a674e5c9058cef1c9d1f5dc89bc87b77be499348144"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9715d1ff5576919d10b68f17241375f6a1eec8961c25b78a83e6ef1487053f39"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f370a1f1f1afc5c1c8cc5edc1cfe0ba444263a0772af7ce094be8e734f41769d"}, - {file = "cytoolz-1.0.0-cp311-cp311-win32.whl", hash = "sha256:dbb2ec1177dca700f3db2127e572da20de280c214fc587b2a11c717fc421af56"}, - {file = "cytoolz-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:0983eee73df86e54bb4a79fcc4996aa8b8368fdbf43897f02f9c3bf39c4dc4fb"}, - {file = "cytoolz-1.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:10e3986066dc379e30e225b230754d9f5996aa8d84c2accc69c473c21d261e46"}, - {file = "cytoolz-1.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:16576f1bb143ee2cb9f719fcc4b845879fb121f9075c7c5e8a5ff4854bd02fc6"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3faa25a1840b984315e8b3ae517312375f4273ffc9a2f035f548b7f916884f37"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:781fce70a277b20fd95dc66811d1a97bb07b611ceea9bda8b7dd3c6a4b05d59a"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a562c25338eb24d419d1e80a7ae12133844ce6fdeb4ab54459daf250088a1b2"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f29d8330aaf070304f7cd5cb7e73e198753624eb0aec278557cccd460c699b5b"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98a96c54aa55ed9c7cdb23c2f0df39a7b4ee518ac54888480b5bdb5ef69c7ef0"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:287d6d7f475882c2ddcbedf8da9a9b37d85b77690779a2d1cdceb5ae3998d52e"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:05a871688df749b982839239fcd3f8ec3b3b4853775d575ff9cd335fa7c75035"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:28bb88e1e2f7d6d4b8e0890b06d292c568984d717de3e8381f2ca1dd12af6470"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:576a4f1fc73d8836b10458b583f915849da6e4f7914f4ecb623ad95c2508cad5"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:509ed3799c47e4ada14f63e41e8f540ac6e2dab97d5d7298934e6abb9d3830ec"}, - {file = "cytoolz-1.0.0-cp312-cp312-win32.whl", hash = "sha256:9ce25f02b910630f6dc2540dd1e26c9326027ddde6c59f8cab07c56acc70714c"}, - {file = "cytoolz-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e53cfcce87e05b7f0ae2fb2b3e5820048cd0bb7b701e92bd8f75c9fbb7c9ae9"}, - {file = "cytoolz-1.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7d56569dfe67a39ce74ffff0dc12cf0a3d1aae709667a303fe8f2dd5fd004fdf"}, - {file = "cytoolz-1.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:035c8bb4706dcf93a89fb35feadff67e9301935bf6bb864cd2366923b69d9a29"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27c684799708bdc7ee7acfaf464836e1b4dec0996815c1d5efd6a92a4356a562"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44ab57cfc922b15d94899f980d76759ef9e0256912dfab70bf2561bea9cd5b19"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:478af5ecc066da093d7660b23d0b465a7f44179739937afbded8af00af412eb6"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da1f82a7828a42468ea2820a25b6e56461361390c29dcd4d68beccfa1b71066b"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c371b3114d38ee717780b239179e88d5d358fe759a00dcf07691b8922bbc762"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:90b343b2f3b3e77c3832ba19b0b17e95412a5b2e715b05c23a55ba525d1fca49"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89a554a9ba112403232a54e15e46ff218b33020f3f45c4baf6520ab198b7ad93"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:0d603f5e2b1072166745ecdd81384a75757a96a704a5642231eb51969f919d5f"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:122ef2425bd3c0419e6e5260d0b18cd25cf74de589cd0184e4a63b24a4641e2e"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8819f1f97ebe36efcaf4b550e21677c46ac8a41bed482cf66845f377dd20700d"}, - {file = "cytoolz-1.0.0-cp38-cp38-win32.whl", hash = "sha256:fcddbb853770dd6e270d89ea8742f0aa42c255a274b9e1620eb04e019b79785e"}, - {file = "cytoolz-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:ca526905a014a38cc23ae78635dc51d0462c5c24425b22c08beed9ff2ee03845"}, - {file = "cytoolz-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:05df5ff1cdd198fb57e7368623662578c950be0b14883cadfb9ee4098415e1e5"}, - {file = "cytoolz-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04a84778f48ebddb26948971dc60948907c876ba33b13f9cbb014fe65b341fc2"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f65283b618b4c4df759f57bcf8483865a73f7f268e6d76886c743407c8d26c1c"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388cd07ee9a9e504c735a0a933e53c98586a1c301a64af81f7aa7ff40c747520"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:06d09e9569cfdfc5c082806d4b4582db8023a3ce034097008622bcbac7236f38"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9502bd9e37779cc9893cbab515a474c2ab6af61ed22ac2f7e16033db18fcaa85"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:364c2fda148def38003b2c86e8adde1d2aab12411dd50872c244a815262e2fda"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9b2e945617325242687189966335e785dc0fae316f4c1825baacf56e5a97e65f"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0f16907fdc724c55b16776bdb7e629deae81d500fe48cfc3861231753b271355"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d3206c81ca3ba2d7b8fe78f2e116e3028e721148be753308e88dcbbc370bca52"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:becce4b13e110b5ac6b23753dcd0c977f4fdccffa31898296e13fd1109e517e3"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69a7e5e98fd446079b8b8ec5987aec9a31ec3570a6f494baefa6800b783eaf22"}, - {file = "cytoolz-1.0.0-cp39-cp39-win32.whl", hash = "sha256:b1707b6c3a91676ac83a28a231a14b337dbb4436b937e6b3e4fd44209852a48b"}, - {file = "cytoolz-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:11d48b8521ef5fe92e099f4fc00717b5d0789c3c90d5d84031b6d3b17dee1700"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e672712d5dc3094afc6fb346dd4e9c18c1f3c69608ddb8cf3b9f8428f9c26a5c"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86fb208bfb7420e1d0d20065d661310e4a8a6884851d4044f47d37ed4cd7410e"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6dbe5fe3b835859fc559eb59bf2775b5a108f7f2cfab0966f3202859d787d8fd"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cace092dfda174eed09ed871793beb5b65633963bcda5b1632c73a5aceea1ce"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f7a9d816af3be9725c70efe0a6e4352a45d3877751b395014b8eb2f79d7d8d9d"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:caa7ef840847a23b379e6146760e3a22f15f445656af97e55a435c592125cfa5"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:921082fff09ff6e40c12c87b49be044492b2d6bb01d47783995813b76680c7b2"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a32f1356f3b64dda883583383966948604ac69ca0b7fbcf5f28856e5f9133b4e"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9af793b1738e4191d15a92e1793f1ffea9f6461022c7b2442f3cb1ea0a4f758a"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:51dfda3983fcc59075c534ce54ca041bb3c80e827ada5d4f25ff7b4049777f94"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:acfb8780c04d29423d14aaab74cd1b7b4beaba32f676e7ace02c9acfbf532aba"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99f39dcc46416dca3eb23664b73187b77fb52cd8ba2ddd8020a292d8f449db67"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0d56b3721977806dcf1a68b0ecd56feb382fdb0f632af1a9fc5ab9b662b32c6"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d346620abc8c83ae634136e700432ad6202faffcc24c5ab70b87392dcda8a1"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:df0c81197fc130de94c09fc6f024a6a19c98ba8fe55c17f1e45ebba2e9229079"}, - {file = "cytoolz-1.0.0.tar.gz", hash = "sha256:eb453b30182152f9917a5189b7d99046b6ce90cdf8aeb0feff4b2683e600defd"}, + {file = "cytoolz-0.12.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bbe58e26c84b163beba0fbeacf6b065feabc8f75c6d3fe305550d33f24a2d346"}, + {file = "cytoolz-0.12.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c51b66ada9bfdb88cf711bf350fcc46f82b83a4683cf2413e633c31a64df6201"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e70d9c615e5c9dc10d279d1e32e846085fe1fd6f08d623ddd059a92861f4e3dd"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83f4532707963ae1a5108e51fdfe1278cc8724e3301fee48b9e73e1316de64f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d028044524ee2e815f36210a793c414551b689d4f4eda28f8bbb0883ad78bf5f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c2875bcd1397d0627a09a4f9172fa513185ad302c63758efc15b8eb33cc2a98"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:131ff4820e5d64a25d7ad3c3556f2d8aa65c66b3f021b03f8a8e98e4180dd808"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04afa90d9d9d18394c40d9bed48c51433d08b57c042e0e50c8c0f9799735dcbd"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dc1ca9c610425f9854323669a671fc163300b873731584e258975adf50931164"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa3f8e01bc423a933f2e1c510cbb0632c6787865b5242857cc955cae220d1bf"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f702e295dddef5f8af4a456db93f114539b8dc2a7a9bc4de7c7e41d169aa6ec3"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0fbad1fb9bb47e827d00e01992a099b0ba79facf5e5aa453be066033232ac4b5"}, + {file = "cytoolz-0.12.3-cp310-cp310-win32.whl", hash = "sha256:8587c3c3dbe78af90c5025288766ac10dc2240c1e76eb0a93a4e244c265ccefd"}, + {file = "cytoolz-0.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e45803d9e75ef90a2f859ef8f7f77614730f4a8ce1b9244375734567299d239"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3ac4f2fb38bbc67ff1875b7d2f0f162a247f43bd28eb7c9d15e6175a982e558d"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cf1e1e96dd86829a0539baf514a9c8473a58fbb415f92401a68e8e52a34ecd5"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08a438701c6141dd34eaf92e9e9a1f66e23a22f7840ef8a371eba274477de85d"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b6f11b0d7ed91be53166aeef2a23a799e636625675bb30818f47f41ad31821"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7fde09384d23048a7b4ac889063761e44b89a0b64015393e2d1d21d5c1f534a"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d3bfe45173cc8e6c76206be3a916d8bfd2214fb2965563e288088012f1dabfc"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27513a5d5b6624372d63313574381d3217a66e7a2626b056c695179623a5cb1a"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d294e5e81ff094fe920fd545052ff30838ea49f9e91227a55ecd9f3ca19774a0"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:727b01a2004ddb513496507a695e19b5c0cfebcdfcc68349d3efd92a1c297bf4"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:fe1e1779a39dbe83f13886d2b4b02f8c4b10755e3c8d9a89b630395f49f4f406"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:de74ef266e2679c3bf8b5fc20cee4fc0271ba13ae0d9097b1491c7a9bcadb389"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e04d22049233394e0b08193aca9737200b4a2afa28659d957327aa780ddddf2"}, + {file = "cytoolz-0.12.3-cp311-cp311-win32.whl", hash = "sha256:20d36430d8ac809186736fda735ee7d595b6242bdb35f69b598ef809ebfa5605"}, + {file = "cytoolz-0.12.3-cp311-cp311-win_amd64.whl", hash = "sha256:780c06110f383344d537f48d9010d79fa4f75070d214fc47f389357dd4f010b6"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:86923d823bd19ce35805953b018d436f6b862edd6a7c8b747a13d52b39ed5716"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3e61acfd029bfb81c2c596249b508dfd2b4f72e31b7b53b62e5fb0507dd7293"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd728f4e6051af6af234651df49319da1d813f47894d4c3c8ab7455e01703a37"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe8c6267caa7ec67bcc37e360f0d8a26bc3bdce510b15b97f2f2e0143bdd3673"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99462abd8323c52204a2a0ce62454ce8fa0f4e94b9af397945c12830de73f27e"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da125221b1fa25c690fcd030a54344cecec80074df018d906fc6a99f46c1e3a6"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c18e351956f70db9e2d04ff02f28e9a41839250d3f936a4c8a1eabd1c3094d2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:921e6d2440ac758c4945c587b1d1d9b781b72737ac0c0ca5d5e02ca1db8bded2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1651a9bd591a8326329ce1d6336f3129161a36d7061a4d5ea9e5377e033364cf"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8893223b87c2782bd59f9c4bd5c7bf733edd8728b523c93efb91d7468b486528"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:e4d2961644153c5ae186db964aa9f6109da81b12df0f1d3494b4e5cf2c332ee2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:71b6eb97f6695f7ba8ce69c49b707a351c5f46fd97f5aeb5f6f2fb0d6e72b887"}, + {file = "cytoolz-0.12.3-cp312-cp312-win32.whl", hash = "sha256:cee3de65584e915053412cd178729ff510ad5f8f585c21c5890e91028283518f"}, + {file = "cytoolz-0.12.3-cp312-cp312-win_amd64.whl", hash = "sha256:9eef0d23035fa4dcfa21e570961e86c375153a7ee605cdd11a8b088c24f707f6"}, + {file = "cytoolz-0.12.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9a38332cfad2a91e89405b7c18b3f00e2edc951c225accbc217597d3e4e9fde"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f501ae1353071fa5d6677437bbeb1aeb5622067dce0977cedc2c5ec5843b202"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f899758146a52e2f8cfb3fb6f4ca19c1e5814178c3d584de35f9e4d7166d91"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800f0526adf9e53d3c6acda748f4def1f048adaa780752f154da5cf22aa488a2"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0976a3fcb81d065473173e9005848218ce03ddb2ec7d40dd6a8d2dba7f1c3ae"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c835eab01466cb67d0ce6290601ebef2d82d8d0d0a285ed0d6e46989e4a7a71a"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4fba0616fcd487e34b8beec1ad9911d192c62e758baa12fcb44448b9b6feae22"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6f6e8207d732651e0204779e1ba5a4925c93081834570411f959b80681f8d333"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8119bf5961091cfe644784d0bae214e273b3b3a479f93ee3baab97bbd995ccfe"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7ad1331cb68afeec58469c31d944a2100cee14eac221553f0d5218ace1a0b25d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:92c53d508fb8a4463acc85b322fa24734efdc66933a5c8661bdc862103a3373d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win32.whl", hash = "sha256:2c6dd75dae3d84fa8988861ab8b1189d2488cb8a9b8653828f9cd6126b5e7abd"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win_amd64.whl", hash = "sha256:caf07a97b5220e6334dd32c8b6d8b2bd255ca694eca5dfe914bb5b880ee66cdb"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed0cfb9326747759e2ad81cb6e45f20086a273b67ac3a4c00b19efcbab007c60"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:96a5a0292575c3697121f97cc605baf2fd125120c7dcdf39edd1a135798482ca"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b76f2f50a789c44d6fd7f773ec43d2a8686781cd52236da03f7f7d7998989bee"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2905fdccacc64b4beba37f95cab9d792289c80f4d70830b70de2fc66c007ec01"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ebe23028eac51251f22ba01dba6587d30aa9c320372ca0c14eeab67118ec3f"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96c715404a3825e37fe3966fe84c5f8a1f036e7640b2a02dbed96cac0c933451"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bac0adffc1b6b6a4c5f1fd1dd2161afb720bcc771a91016dc6bdba59af0a5d3"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:37441bf4a2a4e2e0fe9c3b0ea5e72db352f5cca03903977ffc42f6f6c5467be9"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f04037302049cb30033f7fa4e1d0e44afe35ed6bfcf9b380fc11f2a27d3ed697"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f37b60e66378e7a116931d7220f5352186abfcc950d64856038aa2c01944929c"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ec9be3e4b6f86ea8b294d34c990c99d2ba6c526ef1e8f46f1d52c263d4f32cd7"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e9199c9e3fbf380a92b8042c677eb9e7ed4bccb126de5e9c0d26f5888d96788"}, + {file = "cytoolz-0.12.3-cp38-cp38-win32.whl", hash = "sha256:18cd61e078bd6bffe088e40f1ed02001387c29174750abce79499d26fa57f5eb"}, + {file = "cytoolz-0.12.3-cp38-cp38-win_amd64.whl", hash = "sha256:765b8381d4003ceb1a07896a854eee2c31ebc950a4ae17d1e7a17c2a8feb2a68"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4a52dd2a36b0a91f7aa50ca6c8509057acc481a24255f6cb07b15d339a34e0f"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:581f1ce479769fe7eeb9ae6d87eadb230df8c7c5fff32138162cdd99d7fb8fc3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46f505d4c6eb79585c8ad0b9dc140ef30a138c880e4e3b40230d642690e36366"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59276021619b432a5c21c01cda8320b9cc7dbc40351ffc478b440bfccd5bbdd3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e44f4c25e1e7cf6149b499c74945a14649c8866d36371a2c2d2164e4649e7755"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c64f8e60c1dd69e4d5e615481f2d57937746f4a6be2d0f86e9e7e3b9e2243b5e"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33c63186f3bf9d7ef1347bc0537bb9a0b4111a0d7d6e619623cabc18fef0dc3b"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fdddb9d988405f24035234f1e8d1653ab2e48cc2404226d21b49a129aefd1d25"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6986632d8a969ea1e720990c818dace1a24c11015fd7c59b9fea0b65ef71f726"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0ba1cbc4d9cd7571c917f88f4a069568e5121646eb5d82b2393b2cf84712cf2a"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7d267ffc9a36c0a9a58c7e0adc9fa82620f22e4a72533e15dd1361f57fc9accf"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95e878868a172a41fbf6c505a4b967309e6870e22adc7b1c3b19653d062711fa"}, + {file = "cytoolz-0.12.3-cp39-cp39-win32.whl", hash = "sha256:8e21932d6d260996f7109f2a40b2586070cb0a0cf1d65781e156326d5ebcc329"}, + {file = "cytoolz-0.12.3-cp39-cp39-win_amd64.whl", hash = "sha256:0d8edfbc694af6c9bda4db56643fb8ed3d14e47bec358c2f1417de9a12d6d1fb"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:55f9bd1ae6c2a27eda5abe2a0b65a83029d2385c5a1da7b8ef47af5905d7e905"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d271393c378282727f1231d40391ae93b93ddc0997448acc21dd0cb6a1e56d"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee98968d6a66ee83a8ceabf31182189ab5d8598998c8ce69b6d5843daeb2db60"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01cfb8518828c1189200c02a5010ea404407fb18fd5589e29c126e84bbeadd36"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:456395d7aec01db32bf9e6db191d667347c78d8d48e77234521fa1078f60dabb"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cd88028bb897fba99ddd84f253ca6bef73ecb7bdf3f3cf25bc493f8f97d3c7c5"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b19223e7f7bd7a73ec3aa6fdfb73b579ff09c2bc0b7d26857eec2d01a58c76"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a79d72b08048a0980a59457c239555f111ac0c8bdc140c91a025f124104dbb4"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dd70141b32b717696a72b8876e86bc9c6f8eff995c1808e299db3541213ff82"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a1445c91009eb775d479e88954c51d0b4cf9a1e8ce3c503c2672d17252882647"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ca6a9a9300d5bda417d9090107c6d2b007683efc59d63cc09aca0e7930a08a85"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be6feb903d2a08a4ba2e70e950e862fd3be9be9a588b7c38cee4728150a52918"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b6f43f086e5a965d33d62a145ae121b4ccb6e0789ac0acc895ce084fec8c65"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:534fa66db8564d9b13872d81d54b6b09ae592c585eb826aac235bd6f1830f8ad"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fea649f979def23150680de1bd1d09682da3b54932800a0f90f29fc2a6c98ba8"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a447247ed312dd64e3a8d9483841ecc5338ee26d6e6fbd29cd373ed030db0240"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba3f843aa89f35467b38c398ae5b980a824fdbdb94065adc6ec7c47a0a22f4c7"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:582c22f97a380211fb36a7b65b1beeb84ea11d82015fa84b054be78580390082"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47feb089506fc66e1593cd9ade3945693a9d089a445fbe9a11385cab200b9f22"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba9002d2f043943744a9dc8e50a47362bcb6e6f360dc0a1abcb19642584d87bb"}, + {file = "cytoolz-0.12.3.tar.gz", hash = "sha256:4503dc59f4ced53a54643272c61dc305d1dbbfbd7d6bdf296948de9f34c3a282"}, ] [package.dependencies] @@ -1674,13 +1672,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "hypothesis" -version = "6.112.4" +version = "6.112.2" description = "A library for property-based testing" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.112.4-py3-none-any.whl", hash = "sha256:6d3e3038968925069d1a7e7ebfa2ed0b65b22eff6800d1e88b687b3c6d2f57b5"}, - {file = "hypothesis-6.112.4.tar.gz", hash = "sha256:8fe64e4a6d0862e209e3c36b42037aee9665cb839d619d9281be45345ab7d856"}, + {file = "hypothesis-6.112.2-py3-none-any.whl", hash = "sha256:914b55f75b7c6f653cd36fef66b61a773a51c1e363939fcbc0216773ff4ee0d9"}, + {file = "hypothesis-6.112.2.tar.gz", hash = "sha256:90cd62d9487eaf294bf0dceb47dbaca6432408b2e9417cfa6e3409313dbde95b"}, ] [package.dependencies] @@ -1689,10 +1687,10 @@ exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} sortedcontainers = ">=2.1.0,<3.0.0" [package.extras] -all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.73)", "django (>=3.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.14)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.2)"] +all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.72)", "django (>=3.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.14)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.2)"] cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] codemods = ["libcst (>=0.3.16)"] -crosshair = ["crosshair-tool (>=0.0.73)", "hypothesis-crosshair (>=0.0.14)"] +crosshair = ["crosshair-tool (>=0.0.72)", "hypothesis-crosshair (>=0.0.14)"] dateutil = ["python-dateutil (>=1.4)"] django = ["django (>=3.2)"] dpcontracts = ["dpcontracts (>=0.4)"] @@ -3143,28 +3141,6 @@ eth = ["cached-property (>=1.5.1)", "ckzg (>=2.0.0)", "eth-bloom (>=1.0.3)", "et eth-extra = ["blake2b-py (>=0.2.0)", "coincurve (>=18.0.0)"] test = ["factory-boy (>=3.0.0)", "hypothesis (>=6,<7)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.20.0)", "pytest-cov (>=4.0.0)", "pytest-timeout (>=2.0.0)", "pytest-xdist (>=3.0)"] -[[package]] -name = "py-solc-x" -version = "2.0.3" -description = "Python wrapper and version management tool for the solc Solidity compiler." -optional = false -python-versions = "<4,>=3.8" -files = [ - {file = "py-solc-x-2.0.3.tar.gz", hash = "sha256:1b5fe26d5f4976b1fcc2b3276fb2fe182633bc35757a3abf3b9af5ac57ec2544"}, - {file = "py_solc_x-2.0.3-py3-none-any.whl", hash = "sha256:ef131f2f0e708fcaf92068a38c7569920839d259fdaa9946af3393f1dc86c5b1"}, -] - -[package.dependencies] -packaging = ">=23.1,<24" -requests = ">=2.19.0,<3" - -[package.extras] -dev = ["IPython", "Sphinx (>=6.1.3,<7)", "black (>=23.11.0,<24)", "commitizen", "flake8 (>=6.1.0,<7)", "hypothesis (>=6.2.0,<7.0)", "ipdb", "isort (>=5.10.1,<6)", "mdformat (>=0.7.17)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mypy (>=1.6.1,<2)", "myst-parser (>=1.0.0,<2)", "pre-commit", "pytest (>=6.0)", "pytest-cov", "pytest-mock", "pytest-watch", "pytest-xdist", "setuptools", "setuptools-scm", "sphinx-click (>=4.4.0,<5)", "sphinx-plausible (>=0.1.2,<0.2)", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)", "twine", "types-requests", "types-setuptools", "wheel"] -doc = ["Sphinx (>=6.1.3,<7)", "myst-parser (>=1.0.0,<2)", "sphinx-click (>=4.4.0,<5)", "sphinx-plausible (>=0.1.2,<0.2)", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)"] -lint = ["black (>=23.11.0,<24)", "flake8 (>=6.1.0,<7)", "isort (>=5.10.1,<6)", "mdformat (>=0.7.17)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mypy (>=1.6.1,<2)", "types-requests", "types-setuptools"] -release = ["setuptools", "setuptools-scm", "twine", "wheel"] -test = ["hypothesis (>=6.2.0,<7.0)", "pytest (>=6.0)", "pytest-cov", "pytest-mock", "pytest-xdist"] - [[package]] name = "pycparser" version = "2.22" @@ -3467,29 +3443,25 @@ files = [ [[package]] name = "pywin32" -version = "307" +version = "306" description = "Python for Window Extensions" optional = false python-versions = "*" files = [ - {file = "pywin32-307-cp310-cp310-win32.whl", hash = "sha256:f8f25d893c1e1ce2d685ef6d0a481e87c6f510d0f3f117932781f412e0eba31b"}, - {file = "pywin32-307-cp310-cp310-win_amd64.whl", hash = "sha256:36e650c5e5e6b29b5d317385b02d20803ddbac5d1031e1f88d20d76676dd103d"}, - {file = "pywin32-307-cp310-cp310-win_arm64.whl", hash = "sha256:0c12d61e0274e0c62acee79e3e503c312426ddd0e8d4899c626cddc1cafe0ff4"}, - {file = "pywin32-307-cp311-cp311-win32.whl", hash = "sha256:fec5d27cc893178fab299de911b8e4d12c5954e1baf83e8a664311e56a272b75"}, - {file = "pywin32-307-cp311-cp311-win_amd64.whl", hash = "sha256:987a86971753ed7fdd52a7fb5747aba955b2c7fbbc3d8b76ec850358c1cc28c3"}, - {file = "pywin32-307-cp311-cp311-win_arm64.whl", hash = "sha256:fd436897c186a2e693cd0437386ed79f989f4d13d6f353f8787ecbb0ae719398"}, - {file = "pywin32-307-cp312-cp312-win32.whl", hash = "sha256:07649ec6b01712f36debf39fc94f3d696a46579e852f60157a729ac039df0815"}, - {file = "pywin32-307-cp312-cp312-win_amd64.whl", hash = "sha256:00d047992bb5dcf79f8b9b7c81f72e0130f9fe4b22df613f755ab1cc021d8347"}, - {file = "pywin32-307-cp312-cp312-win_arm64.whl", hash = "sha256:b53658acbfc6a8241d72cc09e9d1d666be4e6c99376bc59e26cdb6223c4554d2"}, - {file = "pywin32-307-cp313-cp313-win32.whl", hash = "sha256:ea4d56e48dc1ab2aa0a5e3c0741ad6e926529510516db7a3b6981a1ae74405e5"}, - {file = "pywin32-307-cp313-cp313-win_amd64.whl", hash = "sha256:576d09813eaf4c8168d0bfd66fb7cb3b15a61041cf41598c2db4a4583bf832d2"}, - {file = "pywin32-307-cp313-cp313-win_arm64.whl", hash = "sha256:b30c9bdbffda6a260beb2919f918daced23d32c79109412c2085cbc513338a0a"}, - {file = "pywin32-307-cp37-cp37m-win32.whl", hash = "sha256:5101472f5180c647d4525a0ed289ec723a26231550dbfd369ec19d5faf60e511"}, - {file = "pywin32-307-cp37-cp37m-win_amd64.whl", hash = "sha256:05de55a7c110478dc4b202230e98af5e0720855360d2b31a44bb4e296d795fba"}, - {file = "pywin32-307-cp38-cp38-win32.whl", hash = "sha256:13d059fb7f10792542082f5731d5d3d9645320fc38814759313e5ee97c3fac01"}, - {file = "pywin32-307-cp38-cp38-win_amd64.whl", hash = "sha256:7e0b2f93769d450a98ac7a31a087e07b126b6d571e8b4386a5762eb85325270b"}, - {file = "pywin32-307-cp39-cp39-win32.whl", hash = "sha256:55ee87f2f8c294e72ad9d4261ca423022310a6e79fb314a8ca76ab3f493854c6"}, - {file = "pywin32-307-cp39-cp39-win_amd64.whl", hash = "sha256:e9d5202922e74985b037c9ef46778335c102b74b95cec70f629453dbe7235d87"}, + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, ] [[package]] @@ -4363,13 +4335,13 @@ files = [ [[package]] name = "toolz" -version = "1.0.0" +version = "0.12.1" description = "List processing tools and functional utilities" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236"}, - {file = "toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02"}, + {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, + {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, ] [[package]] @@ -4746,4 +4718,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "56213d4f55a678eff7ad38becf9b71860f077b14c1e9f98bb41bd556896e4f2b" +content-hash = "f46a23d64c5dae1cdb90c80bcd5e9f4fa3e558b560036945324e7e3c59c767f7" diff --git a/pyproject.toml b/pyproject.toml index 3e70546..4f514fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,6 @@ authors = ["Curve.fi"] readme = "README.md" package-mode = false - [tool.black] line-length = 100 target_version = ['py312'] @@ -15,9 +14,8 @@ target_version = ['py312'] python = "^3.10" poetry = "^1.8.3" vyper = "0.4.0" -snekmate = "0.1.0" titanoboa = { git = "https://github.com/vyperlang/titanoboa.git", rev = "4768207288ec8fb23a4817ae193bd707ab9d1e8f" } -boa-solidity = { git = "https://github.com/AlbertoCentonze/boa-solidity", rev = "500d2e9a6950eb3193c5a6a1d4637cd3e3befa77" } +snekmate = "0.1.0" [tool.poetry.group.dev.dependencies] mamushi = "0.0.4a3" diff --git a/tests/unitary/conftest.py b/tests/unitary/conftest.py index 45c1a85..2a3dacd 100644 --- a/tests/unitary/conftest.py +++ b/tests/unitary/conftest.py @@ -1,5 +1,4 @@ import boa -import boa_solidity # noqa: F401 import pytest MOCK_CRV_USD_CIRCULATING_SUPPLY = 69_420_000 * 10**18 @@ -46,21 +45,12 @@ def role_manager(): @pytest.fixture(scope="module") -def vault(vault_factory, crvusd, role_manager, dummy_strategy): +def vault(vault_factory, crvusd, role_manager): vault_deployer = boa.load_partial("contracts/yearn/Vault.vy") address = vault_factory.deploy_new_vault(crvusd, "Staked crvUSD", "st-crvUSD", role_manager, 0) - vault = vault_deployer.at(address) - - # creata a local god to avoid recursive dependencies in fixtures - _god = boa.env.generate_address() - - vault.set_role(_god, int("11111111111111", 2), sender=role_manager) - - vault.add_strategy(dummy_strategy, sender=_god) # TODO figure out queue - - return vault + return vault_deployer.at(address) @pytest.fixture(scope="module") @@ -116,7 +106,6 @@ def mock_peg_keeper(): def rewards_handler( vault, crvusd, role_manager, minimum_weight, scaling_factor, mock_controller_factory, curve_dao ): - print(crvusd, vault, minimum_weight, scaling_factor, mock_controller_factory, curve_dao) rh = boa.load( "contracts/RewardsHandler.vy", crvusd, @@ -126,30 +115,7 @@ def rewards_handler( mock_controller_factory, curve_dao, ) + vault.set_role(rh, 2**11 | 2**5 | 2**0, sender=role_manager) return rh - - -@pytest.fixture(scope="module") -def solc_args(): - return { - "optimize": True, - "optimize_runs": 200, - } - - -@pytest.fixture(scope="module") -def tokenized_strategy(solc_args, vault_factory): - deployer = boa.load_partial_solc( - "contracts/yearn/TokenizedStrategy.sol", compiler_args=solc_args - ) - return deployer.deploy( - vault_factory.address, override_address="0x2e234DAe75C793f67A35089C9d99245E1C58470b" - ) - - -@pytest.fixture(scope="module") -def dummy_strategy(solc_args, crvusd, tokenized_strategy): - deployer = boa.load_partial_solc("contracts/yearn/DummyStrategy.sol", compiler_args=solc_args) - return deployer.deploy(crvusd.address, "dummy")