diff --git a/ape-config.yaml b/ape-config.yaml index 752b8806..7f5685e4 100644 --- a/ape-config.yaml +++ b/ape-config.yaml @@ -13,10 +13,15 @@ dependencies: - name: openzeppelin github: OpenZeppelin/openzeppelin-contracts version: 4.7.3 + - name: tokenized-strategy + github: yearn/tokenized-strategy + branch: master + contracts_folder: src solidity: import_remapping: - "@openzeppelin/contracts=openzeppelin/v4.7.3" + - "@tokenized-strategy=tokenized-strategy/master" ethereum: local: diff --git a/contracts/test/ERC4626BaseStrategy.sol b/contracts/test/ERC4626BaseStrategy.sol index f550eac2..5f47ff94 100644 --- a/contracts/test/ERC4626BaseStrategy.sol +++ b/contracts/test/ERC4626BaseStrategy.sol @@ -15,6 +15,7 @@ abstract contract ERC4626BaseStrategy is ERC4626 { address public vault; uint8 private _decimals; + address public keeper; constructor( address _vault, @@ -58,4 +59,8 @@ abstract contract ERC4626BaseStrategy is ERC4626 { ) internal virtual returns (uint256 amountFreed); function sweep(address _token) external {} + + function report() external virtual returns (uint256, uint256) { + return (0, 0); + } } diff --git a/contracts/test/mocks/ERC4626/MockTokenizedStrategy.sol b/contracts/test/mocks/ERC4626/MockTokenizedStrategy.sol new file mode 100644 index 00000000..21ecd196 --- /dev/null +++ b/contracts/test/mocks/ERC4626/MockTokenizedStrategy.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.18; + +import {TokenizedStrategy, ERC20} from "@tokenized-strategy/TokenizedStrategy.sol"; + +contract MockTokenizedStrategy is TokenizedStrategy { + uint256 public minDebt; + uint256 public maxDebt = type(uint256).max; + + // Private variables and functions used in this mock. + bytes32 public constant BASE_STRATEGY_STORAGE = + bytes32(uint256(keccak256("yearn.base.strategy.storage")) - 1); + + 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 + } + } + + constructor( + address _asset, + string memory _name, + address _management, + address _keeper + ) { + // Cache storage pointer + StrategyData storage S = strategyStorage(); + + // 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 an instant profit unlock period + S.profitMaxUnlockTime = 0; + // set to a 0% performance fee. + S.performanceFee = 0; + // Set last report to this block. + S.lastReport = uint128(block.timestamp); + + // Set the default management address. Can't be 0. + require(_management != address(0), "ZERO ADDRESS"); + S.management = _management; + S.performanceFeeRecipient = _management; + // Set the keeper address + S.keeper = _keeper; + } + + function setMinDebt(uint256 _minDebt) external { + minDebt = _minDebt; + } + + function setMaxDebt(uint256 _maxDebt) external { + maxDebt = _maxDebt; + } + + function availableDepositLimit(address) public view returns (uint256) { + uint256 _totalAssets = strategyStorage().totalIdle; + uint256 _maxDebt = maxDebt; + return _maxDebt > _totalAssets ? _maxDebt - _totalAssets : 0; + } + + function availableWithdrawLimit( + address /*_owner*/ + ) public view returns (uint256) { + return type(uint256).max; + } + + function deployFunds(uint256 _amount) external {} + + function freeFunds(uint256 _amount) external {} + + function harvestAndReport() external returns (uint256) { + return strategyStorage().asset.balanceOf(address(this)); + } +} diff --git a/tests/conftest.py b/tests/conftest.py index 5c929590..05bc0a26 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -233,9 +233,15 @@ def create_vault( # create default liquid strategy with 0 fee @pytest.fixture(scope="session") -def create_strategy(project, strategist): +def create_strategy(project, strategist, gov): def create_strategy(vault): - return strategist.deploy(project.ERC4626LiquidStrategy, vault, vault.asset()) + return strategist.deploy( + project.MockTokenizedStrategy, + vault.asset(), + "Mock Tokenized Strategy", + strategist, + gov, + ) yield create_strategy diff --git a/tests/unit/vault/test_profit_unlocking.py b/tests/unit/vault/test_profit_unlocking.py index 983649eb..f5a89471 100644 --- a/tests/unit/vault/test_profit_unlocking.py +++ b/tests/unit/vault/test_profit_unlocking.py @@ -35,6 +35,8 @@ def create_and_check_profit( # We create a virtual profit initial_debt = vault.strategies(strategy).current_debt asset.transfer(strategy, profit, sender=gov) + # Record profits at the strategy level. + strategy.report(sender=gov) tx = vault.process_report(strategy, sender=gov) event = list(tx.decode_logs(vault.StrategyReported)) @@ -1199,6 +1201,7 @@ def test_gain_fees_no_refunds_not_enough_buffer( assert accountant.fees(strategy).performance_fee == second_performance_fee asset.transfer(strategy, second_profit, sender=gov) + strategy.report(sender=gov) price_per_share_before_2nd_profit = vault.pricePerShare() / 10 ** vault.decimals() accountant_shares_before_2nd_profit = vault.balanceOf(accountant) diff --git a/tests/unit/vault/test_protocol_fees.py b/tests/unit/vault/test_protocol_fees.py index e231b0af..9dc85135 100644 --- a/tests/unit/vault/test_protocol_fees.py +++ b/tests/unit/vault/test_protocol_fees.py @@ -73,6 +73,7 @@ def test__report_gain_with_protocol_fees__accountant_fees( # Create a profit airdrop_asset(gov, asset, strategy, profit) + strategy.report(sender=gov) expected_accountant_fee = profit * performance_fee / MAX_BPS_ACCOUNTANT expected_protocol_fee = expected_accountant_fee * protocol_fee / MAX_BPS_ACCOUNTANT diff --git a/tests/unit/vault/test_role_base_access.py b/tests/unit/vault/test_role_base_access.py index 9310ec0d..0ddcf4c8 100644 --- a/tests/unit/vault/test_role_base_access.py +++ b/tests/unit/vault/test_role_base_access.py @@ -374,6 +374,7 @@ def test_process_report__reporting_manager( add_debt_to_strategy(gov, strategy, vault, 2) # airdrop gain to strategy airdrop_asset(gov, asset, strategy, 1) + strategy.report(sender=gov) tx = vault.process_report(strategy.address, sender=bunny) diff --git a/tests/unit/vault/test_role_permissioned_access.py b/tests/unit/vault/test_role_permissioned_access.py index 7dff5116..e72f30d3 100644 --- a/tests/unit/vault/test_role_permissioned_access.py +++ b/tests/unit/vault/test_role_permissioned_access.py @@ -236,6 +236,8 @@ def test_process_report__set_reporting_role_open( assert event[0].status == RoleStatusChange.OPENED asset.mint(new_strategy, fish_amount, sender=gov) + new_strategy.report(sender=gov) + tx = vault.process_report(new_strategy, sender=bunny) event = list(tx.decode_logs(vault.StrategyReported)) assert len(event) == 1 @@ -267,6 +269,8 @@ def test_process_report__set_reporting_role_open_then_close__reverts( assert event[0].status == RoleStatusChange.OPENED asset.mint(new_strategy, fish_amount, sender=gov) + new_strategy.report(sender=gov) + tx = vault.process_report(new_strategy, sender=bunny) event = list(tx.decode_logs(vault.StrategyReported)) assert len(event) == 1 diff --git a/tests/unit/vault/test_shares.py b/tests/unit/vault/test_shares.py index 36dbb2b1..ff26749f 100644 --- a/tests/unit/vault/test_shares.py +++ b/tests/unit/vault/test_shares.py @@ -457,6 +457,7 @@ def create_profit( # We create a virtual profit initial_debt = vault.strategies(strategy).current_debt asset.transfer(strategy, profit, sender=gov) + strategy.report(sender=gov) tx = vault.process_report(strategy, sender=gov) event = list(tx.decode_logs(vault.StrategyReported)) diff --git a/tests/unit/vault/test_strategy_accounting.py b/tests/unit/vault/test_strategy_accounting.py index 8c375465..0b067e78 100644 --- a/tests/unit/vault/test_strategy_accounting.py +++ b/tests/unit/vault/test_strategy_accounting.py @@ -42,6 +42,8 @@ def test_process_report__with_gain_and_zero_fees( # add debt to strategy add_debt_to_strategy(gov, strategy, vault, new_debt) airdrop_asset(gov, asset, strategy, gain) + # record gain + strategy.report(sender=gov) strategy_params = vault.strategies(strategy.address) initial_debt = strategy_params.current_debt @@ -87,6 +89,8 @@ def test_process_report__with_gain_and_zero_management_fees( add_debt_to_strategy(gov, strategy, vault, new_debt) # airdrop gain to strategy airdrop_asset(gov, asset, strategy, gain) + # record gain + strategy.report(sender=gov) # set up accountant set_fees_for_strategy(gov, strategy, accountant, management_fee, performance_fee) @@ -145,6 +149,8 @@ def test_process_report__with_gain_and_zero_performance_fees( add_debt_to_strategy(gov, strategy, vault, new_debt) # airdrop gain to strategy airdrop_asset(gov, asset, strategy, gain) + # record gain + strategy.report(sender=gov) # set up accountant set_fees_for_strategy(gov, strategy, accountant, management_fee, performance_fee) @@ -202,6 +208,8 @@ def test_process_report__with_gain_and_both_fees( add_debt_to_strategy(gov, strategy, vault, new_debt) # airdrop gain to strategy airdrop_asset(gov, asset, strategy, gain) + # record gain + strategy.report(sender=gov) # set up accountant set_fees_for_strategy(gov, strategy, accountant, management_fee, performance_fee) @@ -256,6 +264,8 @@ def test_process_report__with_fees_exceeding_fee_cap( add_debt_to_strategy(gov, strategy, vault, new_debt) # airdrop gain to strategy airdrop_asset(gov, asset, strategy, gain) + # record gain + strategy.report(sender=gov) # set up accountant set_fees_for_strategy(gov, strategy, accountant, management_fee, performance_fee) diff --git a/tests/unit/vault/test_strategy_withdraw.py b/tests/unit/vault/test_strategy_withdraw.py index 0efe8aa5..a355546b 100644 --- a/tests/unit/vault/test_strategy_withdraw.py +++ b/tests/unit/vault/test_strategy_withdraw.py @@ -1994,6 +1994,7 @@ def test_withdraw__with_multiple_liquid_strategies_more_assets_than_debt__withdr airdrop_asset(gov, asset, gov, fish_amount) asset.transfer(first_strategy, profit, sender=gov) + first_strategy.report(sender=gov) tx = vault.withdraw( shares,