diff --git a/brownie-config.yml b/brownie-config.yml index c10185c..85a0d92 100644 --- a/brownie-config.yml +++ b/brownie-config.yml @@ -8,7 +8,7 @@ autofetch_sources: True # require OpenZepplin Contracts dependencies: - - yearn/yearn-vaults@0.4.2 + - yearn/yearn-vaults@0.3.0 - OpenZeppelin/openzeppelin-contracts@3.1.0 # path remapping to support imports from GitHub/NPM @@ -16,7 +16,7 @@ compiler: solc: version: 0.6.12 remappings: - - "@yearnvaults=yearn/yearn-vaults@0.4.2" + - "@yearnvaults=yearn/yearn-vaults@0.3.0" - "@openzeppelin=OpenZeppelin/openzeppelin-contracts@3.1.0" reports: diff --git a/contracts/RouterStrategy.sol b/contracts/RouterStrategy030.sol similarity index 88% rename from contracts/RouterStrategy.sol rename to contracts/RouterStrategy030.sol index 709f0e9..f1acdbc 100644 --- a/contracts/RouterStrategy.sol +++ b/contracts/RouterStrategy030.sol @@ -28,7 +28,7 @@ interface IVault is IERC20 { ) external returns (uint256); } -contract RouterStrategy is BaseStrategy { +contract RouterStrategy030 is BaseStrategy { using SafeERC20 for IERC20; using Address for address; using SafeMath for uint256; @@ -46,7 +46,11 @@ contract RouterStrategy is BaseStrategy { } modifier onlyManagement() { - require(msg.sender == governance() || msg.sender == vault.management()); + require( + msg.sender == governance() || + msg.sender == + address(0x16388463d60FFE0661Cf7F1f31a7D658aC790ff7) + ); _; } @@ -65,10 +69,6 @@ contract RouterStrategy is BaseStrategy { return balanceOfWant().add(valueOfInvestment()); } - function delegatedAssets() external view override returns (uint256) { - return vault.strategies(address(this)).totalDebt; - } - function prepareReturn(uint256 _debtOutstanding) internal override @@ -160,19 +160,6 @@ contract RouterStrategy is BaseStrategy { yVault.withdraw(sharesToWithdraw, address(this), maxLoss); } - function liquidateAllPositions() - internal - override - returns (uint256 _amountFreed) - { - return - yVault.withdraw( - yVault.balanceOf(address(this)), - address(this), - maxLoss - ); - } - function prepareMigration(address _newStrategy) internal override { IERC20(yVault).safeTransfer( _newStrategy, @@ -191,16 +178,6 @@ contract RouterStrategy is BaseStrategy { return ret; } - function ethToWant(uint256 _amtInWei) - public - view - virtual - override - returns (uint256) - { - return _amtInWei; - } - function setMaxLoss(uint256 _maxLoss) public onlyManagement { maxLoss = _maxLoss; } diff --git a/tests/conftest.py b/tests/conftest.py index de9fd6b..4c46128 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,6 +37,16 @@ def keeper(accounts): yield accounts[5] +@pytest.fixture +def yvdai_030(): + yield Contract("0x19D3364A399d251E894aC732651be8B0E4e85001") + + +@pytest.fixture +def yvdai_042(): + yield Contract("0x63739d137EEfAB1001245A8Bd1F3895ef3e186E7") + + @pytest.fixture def yvweth_032(): yield Contract("0xa9fE4601811213c340e850ea305481afF02f5b28") @@ -47,6 +57,11 @@ def yvweth_042(): yield Contract("0xa258C4606Ca8206D8aA700cE2143D7db854D168c") +@pytest.fixture +def dai_whale(accounts): + yield accounts.at("0x5d3a536e4d6dbd6114cc1ead35777bab948e3643", True) + + @pytest.fixture def weth_whale(accounts): yield accounts.at("0xc1aae9d18bbe386b102435a8632c8063d31e747c", True) @@ -68,6 +83,11 @@ def amount(accounts, token, user): yield amount +@pytest.fixture +def dai(): + yield Contract("0x6b175474e89094c44da98b954eedeac495271d0f") + + @pytest.fixture def weth(): yield Contract("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") @@ -91,23 +111,23 @@ def vault(pm, gov, rewards, guardian, management, token): @pytest.fixture -def strategy(strategist, keeper, yvweth_032, yvweth_042, RouterStrategy, gov): +def dai_strategy(strategist, keeper, yvdai_030, yvdai_042, RouterStrategy030, gov): strategy = strategist.deploy( - RouterStrategy, yvweth_032, yvweth_042, "Route yvWETH 042" + RouterStrategy030, yvdai_030, yvdai_042, "Route yvDAI 042" ) strategy.setKeeper(keeper) for i in range(0, 20): - strat_address = yvweth_032.withdrawalQueue(i) + strat_address = yvdai_030.withdrawalQueue(i) if ZERO_ADDRESS == strat_address: break - yvweth_032.updateStrategyDebtRatio(strat_address, 0, {"from": gov}) + yvdai_030.updateStrategyDebtRatio(strat_address, 0, {"from": gov}) - yvweth_032.setPerformanceFee(0, {"from": gov}) - yvweth_032.setManagementFee(0, {"from": gov}) - yvweth_032.addStrategy(strategy, 10_000, 0, 2 ** 256 - 1, 0, {"from": gov}) - yvweth_032.setDepositLimit(0, {"from": gov}) + yvdai_030.setPerformanceFee(0, {"from": gov}) + yvdai_030.setManagementFee(0, {"from": gov}) + yvdai_030.addStrategy(strategy, 9_924, 0, 0, {"from": gov}) + yvdai_030.setDepositLimit(0, {"from": gov}) yield strategy diff --git a/tests/test_dai_profit_emergency.py b/tests/test_dai_profit_emergency.py new file mode 100644 index 0000000..e0be001 --- /dev/null +++ b/tests/test_dai_profit_emergency.py @@ -0,0 +1,31 @@ +import pytest +from brownie import Contract, ZERO_ADDRESS, Wei, chain + + +def test_dai_profit_emergency(yvdai_030, yvdai_042, dai_strategy, gov, dai, dai_whale): + + strategy = dai_strategy + # Move only gen lender funds to the new strat + strat = Contract(yvdai_030.withdrawalQueue(0)) + strat.harvest({"from": gov}) + print(f"dai balance in vault {dai.balanceOf(yvdai_030)/1e18}") + + strategy.harvest({"from": gov}) + assert strategy.balanceOfWant() == 0 + assert strategy.valueOfInvestment() > 0 + + # Send profit to 042 + prev_value = strategy.valueOfInvestment() + dai.transfer(yvdai_042, Wei("100_000 ether"), {"from": dai_whale}) + assert strategy.valueOfInvestment() > prev_value + + strategy.setEmergencyExit({"from": gov}) + strategy.harvest({"from": gov}) + chain.sleep(3600 * 8) + chain.mine(1) + + total_gain = yvdai_030.strategies(strategy).dict()["totalGain"] + assert total_gain > 0 + assert yvdai_030.strategies(strategy).dict()["totalLoss"] == 0 + assert strategy.balanceOfWant() < Wei("1 ether") + assert strategy.valueOfInvestment() < Wei("1 ether") diff --git a/tests/test_dai_profit_revoke.py b/tests/test_dai_profit_revoke.py new file mode 100644 index 0000000..b5fb44b --- /dev/null +++ b/tests/test_dai_profit_revoke.py @@ -0,0 +1,32 @@ +import pytest +from brownie import Contract, ZERO_ADDRESS, Wei, chain + + +def test_dai_profit_revoke(yvdai_030, yvdai_042, dai_strategy, gov, dai, dai_whale): + + strategy = dai_strategy + + # Move only gen lender funds to the new strat + strat = Contract(yvdai_030.withdrawalQueue(0)) + strat.harvest({"from": gov}) + print(f"dai balance in vault {dai.balanceOf(yvdai_030)/1e18}") + + strategy.harvest({"from": gov}) + assert strategy.balanceOfWant() == 0 + assert strategy.valueOfInvestment() > 0 + + # Send profit to 042 + prev_value = strategy.valueOfInvestment() + dai.transfer(yvdai_042, Wei("10 ether"), {"from": dai_whale}) + assert strategy.valueOfInvestment() > prev_value + + yvdai_030.revokeStrategy(strategy, {"from": gov}) + strategy.harvest({"from": gov}) + chain.sleep(3600 * 8) + chain.mine(1) + + total_gain = yvdai_030.strategies(strategy).dict()["totalGain"] + assert total_gain > 0 + assert yvdai_030.strategies(strategy).dict()["totalLoss"] == 0 + assert strategy.balanceOfWant() < Wei("1 ether") + assert strategy.valueOfInvestment() < Wei("1 ether") diff --git a/tests/test_migration.py b/tests/test_migration.py deleted file mode 100644 index 6e2d0f5..0000000 --- a/tests/test_migration.py +++ /dev/null @@ -1,33 +0,0 @@ -# TODO: Add tests that show proper migration of the strategy to a newer one -# Use another copy of the strategy to simulate the migration -# Show that nothing is lost! - -import pytest - - -def test_migration( - chain, - token, - vault, - strategy, - amount, - Strategy, - strategist, - gov, - user, - RELATIVE_APPROX, -): - # Deposit to the vault and harvest - token.approve(vault.address, amount, {"from": user}) - vault.deposit(amount, {"from": user}) - chain.sleep(1) - strategy.harvest() - assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount - - # migrate to a new strategy - new_strategy = strategist.deploy(Strategy, vault) - vault.migrateStrategy(strategy, new_strategy, {"from": gov}) - assert ( - pytest.approx(new_strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) - == amount - ) diff --git a/tests/test_move_dai_funds_to_042.py b/tests/test_move_dai_funds_to_042.py new file mode 100644 index 0000000..ae1ea37 --- /dev/null +++ b/tests/test_move_dai_funds_to_042.py @@ -0,0 +1,36 @@ +import pytest +from brownie import Contract, ZERO_ADDRESS, Wei, chain + + +def test_move_dai_funds_to_042(yvdai_030, yvdai_042, dai_strategy, gov, dai, dai_whale): + # hack :) + strategy = dai_strategy + + # Move only gen lender funds to the new strat + strat = Contract(yvdai_030.withdrawalQueue(0)) + strat.harvest({"from": gov}) + print(f"dai balance in vault {dai.balanceOf(yvdai_030)/1e18}") + + strategy.harvest({"from": gov}) + assert strategy.balanceOfWant() == 0 + assert strategy.valueOfInvestment() > 0 + + # Send profit to 042 + prev_value = strategy.valueOfInvestment() + dai.transfer(yvdai_042, Wei("1000 ether"), {"from": dai_whale}) + assert strategy.valueOfInvestment() > prev_value + + strategy.harvest({"from": gov}) + + total_gain = yvdai_030.strategies(strategy).dict()["totalGain"] + assert total_gain > 0 + assert yvdai_030.strategies(strategy).dict()["totalLoss"] == 0 + + yvdai_030.revokeStrategy(strategy, {"from": gov}) + strategy.harvest({"from": gov}) + + assert yvdai_030.strategies(strategy).dict()["totalGain"] - total_gain < Wei( + "1 ether" + ) + assert yvdai_030.strategies(strategy).dict()["totalLoss"] == 0 + assert yvdai_030.strategies(strategy).dict()["totalDebt"] == 0 diff --git a/tests/test_move_funds_to_042.py b/tests/test_move_funds_to_042.py deleted file mode 100644 index f557bc5..0000000 --- a/tests/test_move_funds_to_042.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from brownie import Contract, ZERO_ADDRESS, Wei, chain - - -def test_move_funds_to_042(yvweth_032, yvweth_042, strategy, gov, weth, weth_whale): - - # Move all funds to the new strat - for i in range(0, 20): - strat_address = yvweth_032.withdrawalQueue(i) - if ZERO_ADDRESS == strat_address: - break - - if strategy == strat_address or i == 1: - continue - - strat = Contract(strat_address) - print(f"harvesting {strat.name()}") - strat.harvest({"from": gov}) - - strategy.harvest({"from": gov}) - assert strategy.balanceOfWant() == 0 - assert strategy.valueOfInvestment() > 0 - - # Send profit to 042 - prev_value = strategy.valueOfInvestment() - weth.transfer(yvweth_042, Wei("10 ether"), {"from": weth_whale}) - assert strategy.valueOfInvestment() > prev_value - - strategy.harvest({"from": gov}) - chain.sleep(3600 * 11) - chain.mine(1) - - total_gain = yvweth_032.strategies(strategy).dict()["totalGain"] - assert total_gain > 0 - assert yvweth_032.strategies(strategy).dict()["totalLoss"] == 0 - - yvweth_032.revokeStrategy(strategy, {"from": gov}) - strategy.harvest({"from": gov}) - chain.sleep(3600 * 8) - chain.mine(1) - - assert yvweth_032.strategies(strategy).dict()["totalGain"] == total_gain - assert yvweth_032.strategies(strategy).dict()["totalLoss"] == 0 - assert yvweth_032.strategies(strategy).dict()["totalDebt"] == 0 diff --git a/tests/test_operation.py b/tests/test_operation.py deleted file mode 100644 index 136e714..0000000 --- a/tests/test_operation.py +++ /dev/null @@ -1,135 +0,0 @@ -import brownie -from brownie import Contract -import pytest - - -def test_operation( - chain, accounts, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX -): - # Deposit to the vault - user_balance_before = token.balanceOf(user) - token.approve(vault.address, amount, {"from": user}) - vault.deposit(amount, {"from": user}) - assert token.balanceOf(vault.address) == amount - - # harvest - chain.sleep(1) - strategy.harvest() - assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount - - # tend() - strategy.tend() - - # withdrawal - vault.withdraw({"from": user}) - assert ( - pytest.approx(token.balanceOf(user), rel=RELATIVE_APPROX) == user_balance_before - ) - - -def test_emergency_exit( - chain, accounts, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX -): - # Deposit to the vault - token.approve(vault.address, amount, {"from": user}) - vault.deposit(amount, {"from": user}) - chain.sleep(1) - strategy.harvest() - assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount - - # set emergency and exit - strategy.setEmergencyExit() - chain.sleep(1) - strategy.harvest() - assert strategy.estimatedTotalAssets() < amount - - -def test_profitable_harvest( - chain, accounts, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX -): - # Deposit to the vault - token.approve(vault.address, amount, {"from": user}) - vault.deposit(amount, {"from": user}) - assert token.balanceOf(vault.address) == amount - - # Harvest 1: Send funds through the strategy - chain.sleep(1) - strategy.harvest() - assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount - - # TODO: Add some code before harvest #2 to simulate earning yield - - # Harvest 2: Realize profit - chain.sleep(1) - strategy.harvest() - chain.sleep(3600 * 6) # 6 hrs needed for profits to unlock - chain.mine(1) - profit = token.balanceOf(vault.address) # Profits go to vault - # TODO: Uncomment the lines below - # assert token.balanceOf(strategy) + profit > amount - # assert vault.pricePerShare() > before_pps - - -def test_change_debt( - chain, gov, token, vault, strategy, user, strategist, amount, RELATIVE_APPROX -): - # Deposit to the vault and harvest - token.approve(vault.address, amount, {"from": user}) - vault.deposit(amount, {"from": user}) - vault.updateStrategyDebtRatio(strategy.address, 5_000, {"from": gov}) - chain.sleep(1) - strategy.harvest() - half = int(amount / 2) - - assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == half - - vault.updateStrategyDebtRatio(strategy.address, 10_000, {"from": gov}) - chain.sleep(1) - strategy.harvest() - assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount - - # In order to pass this tests, you will need to implement prepareReturn. - # TODO: uncomment the following lines. - # vault.updateStrategyDebtRatio(strategy.address, 5_000, {"from": gov}) - # chain.sleep(1) - # strategy.harvest() - # assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == half - - -def test_sweep(gov, vault, strategy, token, user, amount, weth, weth_amout): - # Strategy want token doesn't work - token.transfer(strategy, amount, {"from": user}) - assert token.address == strategy.want() - assert token.balanceOf(strategy) > 0 - with brownie.reverts("!want"): - strategy.sweep(token, {"from": gov}) - - # Vault share token doesn't work - with brownie.reverts("!shares"): - strategy.sweep(vault.address, {"from": gov}) - - # TODO: If you add protected tokens to the strategy. - # Protected token doesn't work - # with brownie.reverts("!protected"): - # strategy.sweep(strategy.protectedToken(), {"from": gov}) - - before_balance = weth.balanceOf(gov) - weth.transfer(strategy, weth_amout, {"from": user}) - assert weth.address != strategy.want() - assert weth.balanceOf(user) == 0 - strategy.sweep(weth, {"from": gov}) - assert weth.balanceOf(gov) == weth_amout + before_balance - - -def test_triggers( - chain, gov, vault, strategy, token, amount, user, weth, weth_amout, strategist -): - # Deposit to the vault and harvest - token.approve(vault.address, amount, {"from": user}) - vault.deposit(amount, {"from": user}) - vault.updateStrategyDebtRatio(strategy.address, 5_000, {"from": gov}) - chain.sleep(1) - strategy.harvest() - - strategy.harvestTrigger(0) - strategy.tendTrigger(0) diff --git a/tests/test_profit_emergency.py b/tests/test_profit_emergency.py deleted file mode 100644 index 2b57f1c..0000000 --- a/tests/test_profit_emergency.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest -from brownie import Contract, ZERO_ADDRESS, Wei, chain - - -def test_profit_emergency(yvweth_032, yvweth_042, strategy, gov, weth, weth_whale): - - # Move all funds to the new strat - for i in range(0, 20): - strat_address = yvweth_032.withdrawalQueue(i) - if ZERO_ADDRESS == strat_address: - break - - if strategy == strat_address or i == 1: - continue - - strat = Contract(strat_address) - print(f"harvesting {strat.name()}") - strat.harvest({"from": gov}) - - strategy.harvest({"from": gov}) - assert strategy.balanceOfWant() == 0 - assert strategy.valueOfInvestment() > 0 - - # Send profit to 042 - prev_value = strategy.valueOfInvestment() - weth.transfer(yvweth_042, Wei("100 ether"), {"from": weth_whale}) - assert strategy.valueOfInvestment() > prev_value - - strategy.setEmergencyExit({"from": gov}) - strategy.harvest({"from": gov}) - chain.sleep(3600 * 8) - chain.mine(1) - - total_gain = yvweth_032.strategies(strategy).dict()["totalGain"] - assert total_gain > 0 - assert yvweth_032.strategies(strategy).dict()["totalLoss"] == 0 - assert strategy.balanceOfWant() == 0 - assert strategy.valueOfInvestment() == 0 diff --git a/tests/test_profit_revoke.py b/tests/test_profit_revoke.py deleted file mode 100644 index db71fe7..0000000 --- a/tests/test_profit_revoke.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest -from brownie import Contract, ZERO_ADDRESS, Wei, chain - - -def test_profit_revoke(yvweth_032, yvweth_042, strategy, gov, weth, weth_whale): - - # Move all funds to the new strat - for i in range(0, 20): - strat_address = yvweth_032.withdrawalQueue(i) - if ZERO_ADDRESS == strat_address: - break - - if strategy == strat_address or i == 1: - continue - - strat = Contract(strat_address) - print(f"harvesting {strat.name()}") - strat.harvest({"from": gov}) - - strategy.harvest({"from": gov}) - assert strategy.balanceOfWant() == 0 - assert strategy.valueOfInvestment() > 0 - - # Send profit to 042 - prev_value = strategy.valueOfInvestment() - weth.transfer(yvweth_042, Wei("10 ether"), {"from": weth_whale}) - assert strategy.valueOfInvestment() > prev_value - - yvweth_032.revokeStrategy(strategy, {"from": gov}) - strategy.harvest({"from": gov}) - chain.sleep(3600 * 8) - chain.mine(1) - - total_gain = yvweth_032.strategies(strategy).dict()["totalGain"] - assert total_gain > 0 - assert yvweth_032.strategies(strategy).dict()["totalLoss"] == 0 - assert strategy.balanceOfWant() == 0 - assert strategy.valueOfInvestment() == 0 diff --git a/tests/test_revoke.py b/tests/test_revoke.py deleted file mode 100644 index eb1c522..0000000 --- a/tests/test_revoke.py +++ /dev/null @@ -1,35 +0,0 @@ -import pytest - - -def test_revoke_strategy_from_vault( - chain, token, vault, strategy, amount, user, gov, RELATIVE_APPROX -): - # Deposit to the vault and harvest - token.approve(vault.address, amount, {"from": user}) - vault.deposit(amount, {"from": user}) - chain.sleep(1) - strategy.harvest() - assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount - - # In order to pass this tests, you will need to implement prepareReturn. - # TODO: uncomment the following lines. - # vault.revokeStrategy(strategy.address, {"from": gov}) - # chain.sleep(1) - # strategy.harvest() - # assert pytest.approx(token.balanceOf(vault.address), rel=RELATIVE_APPROX) == amount - - -def test_revoke_strategy_from_strategy( - chain, token, vault, strategy, amount, gov, user, RELATIVE_APPROX -): - # Deposit to the vault and harvest - token.approve(vault.address, amount, {"from": user}) - vault.deposit(amount, {"from": user}) - chain.sleep(1) - strategy.harvest() - assert pytest.approx(strategy.estimatedTotalAssets(), rel=RELATIVE_APPROX) == amount - - strategy.setEmergencyExit() - chain.sleep(1) - strategy.harvest() - assert pytest.approx(token.balanceOf(vault.address), rel=RELATIVE_APPROX) == amount