Skip to content

Commit

Permalink
fix: api version (#184)
Browse files Browse the repository at this point in the history
* fix: api version

* fix: factory interface

* test: add tokenized strategy tests (#185)

* test: add tokenized strategy tests

* test: use lossy tokenized

* fix: use safe approve

* fix: cleanup
  • Loading branch information
Schlagonia authored Oct 19, 2023
1 parent 45b5d6b commit 9a25833
Show file tree
Hide file tree
Showing 20 changed files with 255 additions and 125 deletions.
5 changes: 5 additions & 0 deletions ape-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion contracts/VaultFactory.vy
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def vault_blueprint()-> address:

@view
@external
def api_version() -> String[28]:
def apiVersion() -> String[28]:
"""
@notice Get the API version of the factory.
@return The API version of the factory.
Expand Down
19 changes: 14 additions & 5 deletions contracts/VaultV3.vy
Original file line number Diff line number Diff line change
Expand Up @@ -254,14 +254,14 @@ open_roles: public(HashMap[Roles, bool])
role_manager: public(address)
# Temporary variable to store the address of the next role_manager until the role is accepted.
future_role_manager: public(address)
# State of the vault - if set to true, only withdrawals will be available. It can't be reverted.
shutdown: public(bool)

# ERC20 - name of the vaults token.
name: public(String[64])
# ERC20 - symbol of the vaults token.
symbol: public(String[32])

# State of the vault - if set to true, only withdrawals will be available. It can't be reverted.
shutdown: bool
# The amount of time profits will unlock over.
profit_max_unlock_time: uint256
# The timestamp of when the current unlocking period ends.
Expand Down Expand Up @@ -1439,7 +1439,7 @@ def set_minimum_total_idle(minimum_total_idle: uint256):
log UpdateMinimumTotalIdle(minimum_total_idle)

@external
def set_profit_max_unlock_time(new_profit_max_unlock_time: uint256):
def setProfitMaxUnlockTime(new_profit_max_unlock_time: uint256):
"""
@notice Set the new profit max unlock time.
@dev The time is denominated in seconds and must be less than 1 year.
Expand Down Expand Up @@ -1562,9 +1562,18 @@ def accept_role_manager():
log UpdateRoleManager(msg.sender)

# VAULT STATUS VIEWS

@view
@external
def isShutdown() -> bool:
"""
@notice Get if the vault is shutdown.
@return Bool representing the shutdown status
"""
return self.shutdown
@view
@external
def unlocked_shares() -> uint256:
def unlockedShares() -> uint256:
"""
@notice Get the amount of shares that have been unlocked.
@return The amount of shares that are have been unlocked.
Expand Down Expand Up @@ -2076,7 +2085,7 @@ def previewRedeem(shares: uint256) -> uint256:

@view
@external
def api_version() -> String[28]:
def apiVersion() -> String[28]:
"""
@notice Get the API version of the vault.
@return The API version of the vault.
Expand Down
8 changes: 4 additions & 4 deletions contracts/interfaces/IVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ interface IVault is IERC4626 {

function future_role_manager() external view returns (address);

function shutdown() external view returns (bool);
function isShutdown() external view returns (bool);

function nonces(address) external view returns (uint256);

Expand All @@ -97,7 +97,7 @@ interface IVault is IERC4626 {

function set_minimum_total_idle(uint256 minimum_total_idle) external;

function set_profit_max_unlock_time(
function setProfitMaxUnlockTime(
uint256 new_profit_max_unlock_time
) external;

Expand All @@ -115,7 +115,7 @@ interface IVault is IERC4626 {

function accept_role_manager() external;

function unlocked_shares() external view returns (uint256);
function unlockedShares() external view returns (uint256);

function pricePerShare() external view returns (uint256);

Expand Down Expand Up @@ -149,7 +149,7 @@ interface IVault is IERC4626 {

function totalDebt() external view returns (uint256);

function api_version() external view returns (string memory);
function apiVersion() external view returns (string memory);

function assess_share_of_unrealised_losses(
address strategy,
Expand Down
20 changes: 18 additions & 2 deletions contracts/interfaces/IVaultFactory.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.18;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

interface IVaultFactory {
event NewVault(address indexed vaultAddress, address indexed asset);
event UpdateProtocolFeeBps(
Expand All @@ -17,8 +19,22 @@ interface IVaultFactory {
event NewPendingGovernance(address newPendingGovernance);
event UpdateGovernance(address newGovernance);

function shutdown() external view returns (bool);

function governance() external view returns (address);

function pending_governance() external view returns (address);

function name() external view returns (string memory);

function default_protocol_fee_config() external view returns (uint256);

function custom_protocol_fee(address) external view returns (uint16);

function use_custom_protocol_fee(address) external view returns (bool);

function deploy_new_vault(
address asset,
ERC20 asset,
string memory name,
string memory symbol,
address role_manager,
Expand All @@ -27,7 +43,7 @@ interface IVaultFactory {

function vault_blueprint() external view returns (address);

function api_version() external view returns (string memory);
function apiVersion() external view returns (string memory);

function protocol_fee_config()
external
Expand Down
11 changes: 5 additions & 6 deletions contracts/test/ERC4626BaseStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ abstract contract ERC4626BaseStrategy is ERC4626 {

address public vault;
uint8 private _decimals;
address public keeper;

constructor(
address _vault,
Expand All @@ -27,11 +28,8 @@ abstract contract ERC4626BaseStrategy is ERC4626 {
_decimals = IERC20Metadata(address(_asset)).decimals();

vault = _vault;
// // using approve since initialization is only called once
// IERC20(_asset).approve(_vault, type(uint256).max); // Give Vault unlimited access (might save gas)
}

/** @dev See {IERC20Metadata-decimals}. */
function decimals()
public
view
Expand All @@ -42,9 +40,6 @@ abstract contract ERC4626BaseStrategy is ERC4626 {
return _decimals;
}

// TODO: add roles (including vault)
// TODO: should we force invest and freeFunds to be in deposit and withdraw functions?

function invest() external virtual {}

function freeFunds(
Expand All @@ -58,4 +53,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);
}
}
28 changes: 0 additions & 28 deletions contracts/test/mocks/ERC4626/LiquidStrategy.sol

This file was deleted.

107 changes: 61 additions & 46 deletions contracts/test/mocks/ERC4626/LossyStrategy.sol
Original file line number Diff line number Diff line change
@@ -1,23 +1,55 @@
// 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 {
using SafeERC20 for ERC20;

ERC20 public asset;

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

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

function withdraw(uint256 _amount) external {
asset.safeTransfer(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);
// So we can record losses when it happens.
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);
// Record the loss
MockTokenizedStrategy(address(this)).report();
}

function setWithdrawingLoss(int256 _loss) external {
Expand All @@ -28,53 +60,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;
}
}
Loading

0 comments on commit 9a25833

Please sign in to comment.