Skip to content

Commit

Permalink
test: use lossy tokenized
Browse files Browse the repository at this point in the history
  • Loading branch information
Schlagonia committed Oct 18, 2023
1 parent cc77711 commit 2d2fc3d
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 64 deletions.
104 changes: 58 additions & 46 deletions contracts/test/mocks/ERC4626/LossyStrategy.sol
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.18;

import {ERC4626BaseStrategyMock, IERC20} from "./BaseStrategyMock.sol";
import {MockTokenizedStrategy, ERC20} from "./MockTokenizedStrategy.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract ERC4626LossyStrategy is ERC4626BaseStrategyMock {
using SafeERC20 for IERC20;
contract YieldSource {
ERC20 public asset;

constructor(address _asset) {
asset = ERC20(_asset);
asset.approve(msg.sender, type(uint256).max);
}

function deposit(uint256 _amount) external {
asset.transferFrom(msg.sender, address(this), _amount);
}

function withdraw(uint256 _amount) external {
asset.transfer(msg.sender, _amount);
}
}

contract ERC4626LossyStrategy is MockTokenizedStrategy {
using SafeERC20 for ERC20;

int256 public withdrawingLoss;
uint256 public lockedFunds;
address public vault;

address public yieldSource;

constructor(
address _vault,
address _asset
) ERC4626BaseStrategyMock(_vault, _asset) {}
address _asset,
string memory _name,
address _management,
address _keeper,
address _vault
) MockTokenizedStrategy(_asset, _name, _management, _keeper) {
yieldSource = address(new YieldSource(_asset));
ERC20(_asset).safeApprove(yieldSource, type(uint256).max);
strategyStorage().management = address(this);
vault = _vault;
}

// used to generate losses, accepts single arg to send losses to
function setLoss(address _target, uint256 _loss) external {
IERC20(asset()).safeTransfer(_target, _loss);
strategyStorage().asset.safeTransferFrom(yieldSource, _target, _loss);
MockTokenizedStrategy(address(this)).report();
}

function setWithdrawingLoss(int256 _loss) external {
Expand All @@ -28,53 +57,36 @@ contract ERC4626LossyStrategy is ERC4626BaseStrategyMock {
lockedFunds = _lockedFunds;
}

function totalAssets() public view override returns (uint256) {
if (withdrawingLoss < 0) {
return
uint256(
int256(IERC20(asset()).balanceOf(address(this))) +
withdrawingLoss
);
} else {
return super.totalAssets();
}
function deployFunds(uint256 _amount) external override {
YieldSource(yieldSource).deposit(_amount);
}

function _withdraw(
address _caller,
address _receiver,
address _owner,
uint256 _assets,
uint256 _shares
) internal override {
if (_caller != _owner) {
_spendAllowance(_owner, _caller, _shares);
}
function freeFunds(uint256 _amount) external override {
// Adjust the amount to withdraw.
uint256 toWithdraw = uint256(int256(_amount) - withdrawingLoss);
YieldSource(yieldSource).withdraw(toWithdraw);

uint256 toWithdraw = uint256(int256(_assets) - withdrawingLoss);
_burn(_owner, _shares);
// Withdrawing loss simulates a loss while withdrawing
IERC20(asset()).safeTransfer(_receiver, toWithdraw);
if (withdrawingLoss > 0) {
// burns (to simulate loss while withdrawing)
IERC20(asset()).safeTransfer(asset(), uint256(withdrawingLoss));
if (withdrawingLoss < 0) {
// Over withdraw to the vault
strategyStorage().asset.safeTransfer(
vault,
uint256(-withdrawingLoss)
);
}

emit Withdraw(_caller, _receiver, _owner, toWithdraw, _shares);
}

function _freeFunds(
uint256 _amount
) internal override returns (uint256 _amountFreed) {}

function maxWithdraw(address) public view override returns (uint256) {
return IERC20(asset()).balanceOf(address(this)) - lockedFunds;
function harvestAndReport() external override returns (uint256) {
return
strategyStorage().asset.balanceOf(address(this)) +
strategyStorage().asset.balanceOf(yieldSource);
}

function maxRedeem(address) public view override returns (uint256) {
function availableWithdrawLimit(
address
) public view override returns (uint256) {
return
convertToShares(
IERC20(asset()).balanceOf(address(this)) - lockedFunds
);
strategyStorage().asset.balanceOf(address(this)) +
strategyStorage().asset.balanceOf(yieldSource) -
lockedFunds;
}
}
12 changes: 7 additions & 5 deletions contracts/test/mocks/ERC4626/MockTokenizedStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,23 +59,25 @@ contract MockTokenizedStrategy is TokenizedStrategy {
maxDebt = _maxDebt;
}

function availableDepositLimit(address) public view returns (uint256) {
function availableDepositLimit(
address
) public view virtual returns (uint256) {
uint256 _totalAssets = strategyStorage().totalIdle;
uint256 _maxDebt = maxDebt;
return _maxDebt > _totalAssets ? _maxDebt - _totalAssets : 0;
}

function availableWithdrawLimit(
address /*_owner*/
) public view returns (uint256) {
) public view virtual returns (uint256) {
return type(uint256).max;
}

function deployFunds(uint256 _amount) external {}
function deployFunds(uint256 _amount) external virtual {}

function freeFunds(uint256 _amount) external {}
function freeFunds(uint256 _amount) external virtual {}

function harvestAndReport() external returns (uint256) {
function harvestAndReport() external virtual returns (uint256) {
return strategyStorage().asset.balanceOf(address(this));
}
}
13 changes: 10 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,16 @@ def create_locked_strategy(vault):

# create lossy strategy with 0 fee
@pytest.fixture(scope="session")
def create_lossy_strategy(project, strategist):
def create_lossy_strategy(project, strategist, gov):
def create_lossy_strategy(vault):
return strategist.deploy(project.ERC4626LossyStrategy, vault, vault.asset())
return strategist.deploy(
project.ERC4626LossyStrategy,
vault.asset(),
"Mock Tokenized Strategy",
strategist,
gov,
vault,
)

yield create_lossy_strategy

Expand Down Expand Up @@ -474,7 +481,7 @@ def user_deposit(user, vault, token, amount) -> ContractLog:
@pytest.fixture(scope="session")
def airdrop_asset():
def airdrop_asset(gov, asset, target, amount):
asset.mint(target.address, amount, sender=gov)
asset.mint(target, amount, sender=gov)

return airdrop_asset

Expand Down
3 changes: 3 additions & 0 deletions tests/e2e/test_profitable_strategy_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ def test_profitable_strategy_flow(
# we simulate profit on strategy
total_fee = first_profit * (performance_fee / MAX_BPS_ACCOUNTANT)
asset.transfer(strategy, first_profit, sender=whale)
strategy.report(sender=gov)

tx = vault.process_report(strategy.address, sender=gov)
event = list(tx.decode_logs(vault.StrategyReported))
assert event[0].gain == first_profit
Expand All @@ -84,6 +86,7 @@ def test_profitable_strategy_flow(

# We generate second profit
asset.transfer(strategy, second_profit, sender=whale)
strategy.report(sender=gov)
assets_before_profit = vault.totalAssets()
tx = vault.process_report(strategy.address, sender=gov)
event = list(tx.decode_logs(vault.StrategyReported))
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/strategy/test_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ def test_lossy_strategy__with_multiple_losses(
loss = 10**18

mint_and_deposit_into_strategy(strategy, vault, amount)
assert asset.balanceOf(strategy) == amount
assert strategy.totalAssets() == amount
assert strategy.maxWithdraw(vault) == amount

strategy.setLoss(fish.address, loss, sender=gov)
initial_loss = amount - loss
assert asset.balanceOf(strategy) == initial_loss
assert strategy.totalAssets() == initial_loss
assert strategy.maxWithdraw(vault) == initial_loss

strategy.setLoss(fish.address, loss, sender=gov)
secondary_loss = initial_loss - loss
assert asset.balanceOf(strategy) == secondary_loss
assert strategy.totalAssets() == secondary_loss
assert strategy.maxWithdraw(vault) == secondary_loss
8 changes: 4 additions & 4 deletions tests/unit/vault/test_debt_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ def test_update_debt__with_faulty_strategy_that_withdraws_more_than_requested__o
new_debt = target_debt - extra
difference = current_debt - new_debt

airdrop_asset(gov, asset, lossy_strategy, extra)
airdrop_asset(gov, asset, lossy_strategy.yieldSource(), extra)
lossy_strategy.setWithdrawingLoss(-extra, sender=gov)

initial_pps = vault.pricePerShare()
Expand All @@ -526,7 +526,7 @@ def test_update_debt__with_faulty_strategy_that_withdraws_more_than_requested__o
# assert we recorded correctly
assert vault.pricePerShare() == initial_pps
assert vault.strategies(lossy_strategy.address).current_debt == new_debt
assert asset.balanceOf(lossy_strategy) == target_debt
assert lossy_strategy.totalAssets() == target_debt
assert asset.balanceOf(vault) == difference
assert vault.totalIdle() == difference
assert vault.totalDebt() == new_debt
Expand All @@ -546,7 +546,7 @@ def test_update_debt__with_faulty_strategy_that_withdraws_more_than_requested(
new_debt = 0
difference = current_debt

airdrop_asset(gov, asset, lossy_strategy, extra)
airdrop_asset(gov, asset, lossy_strategy.yieldSource(), extra)
lossy_strategy.setWithdrawingLoss(-extra, sender=gov)

initial_pps = vault.pricePerShare()
Expand All @@ -561,7 +561,7 @@ def test_update_debt__with_faulty_strategy_that_withdraws_more_than_requested(

assert vault.pricePerShare() == initial_pps
assert vault.strategies(lossy_strategy.address).current_debt == new_debt
assert asset.balanceOf(lossy_strategy) == new_debt
assert lossy_strategy.totalAssets() == new_debt
assert asset.balanceOf(vault) == (vault_balance + extra)
assert vault.totalIdle() == vault_balance
assert vault.totalDebt() == new_debt
Expand Down
4 changes: 1 addition & 3 deletions tests/unit/vault/test_strategy_withdraw.py
Original file line number Diff line number Diff line change
Expand Up @@ -1413,7 +1413,6 @@ def test_withdraw__from_lossy_strategy_with_unrealised_losses_and_max_redeem(
assert vault.totalIdle() == 0
assert vault.totalDebt() == amount - amount_to_withdraw
assert asset.balanceOf(vault) == 0
assert asset.balanceOf(lossy_strategy) == amount_to_lock
assert lossy_strategy.totalAssets() == amount_to_lock
assert asset.balanceOf(strategy) == 0
assert liquid_strategy.totalAssets() == 0
Expand Down Expand Up @@ -1510,7 +1509,6 @@ def test_redeem__from_lossy_strategy_with_unrealised_losses_and_max_redeem(
assert vault.totalIdle() == 0
assert vault.totalDebt() == amount - amount_to_withdraw
assert asset.balanceOf(vault) == 0
assert asset.balanceOf(lossy_strategy) == amount_to_lock
assert lossy_strategy.totalAssets() == amount_to_lock
assert asset.balanceOf(strategy) == 0
assert liquid_strategy.totalAssets() == 0
Expand Down Expand Up @@ -1793,7 +1791,7 @@ def test_withdraw__half_of_strategy_assets_from_lossy_strategy_with_unrealised_l
assert asset.balanceOf(vault) == 0
assert asset.balanceOf(liquid_strategy) == amount_per_strategy
assert (
asset.balanceOf(lossy_strategy)
lossy_strategy.totalAssets()
== amount_per_strategy - amount_to_lose - amount_to_lose // 2
) # withdrawn from strategy
assert (
Expand Down

0 comments on commit 2d2fc3d

Please sign in to comment.