Skip to content

Commit

Permalink
feat: use allocator (#30)
Browse files Browse the repository at this point in the history
* build: yield manager

* fix: check loss and outsource reporting

* fix: yield not apr

* test: yield manager

* feat: add events

* fix: remove generic call

* fix: permissions

* fix: deployments

* fix: workflow

* fix: rebase tests

* feat: use allocator

* feat: manage strategy

* test: update tests

* fix: fixes

* fix: remove event

* feat: just a keeper contract

* fix: removal

* feat: report when going to 0

* feat: script and changes

* fix: make virtual

* fix: formatting

* feat: check max redeem and deposit

* feat: dont duplicate vaults

* chore: change dep

* fix: versions

* chore: pin ape version

* chore: downgrade ape

* chore: pin all versions

* feat: increase and decrease

* fix: formatting

* fix: naming

* build: use central allocator factory (#31)

* build: use central factory

* chore: comments

* test: update for factory

* fix: bump by 20%

* chore: renaming

* chore: comments

* fix: black
  • Loading branch information
Schlagonia authored Jan 9, 2024
1 parent 17dce26 commit bd7c8c4
Show file tree
Hide file tree
Showing 20 changed files with 3,013 additions and 601 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 16
- uses: ApeWorX/github-action@v2.4
- uses: ApeWorX/github-action@v2.0
with:
python-version: '3.10'
ape-version-pin: "==0.6.27"
ape-plugins-list: 'solidity==0.6.11 vyper==0.6.13 infura==0.6.5 hardhat==0.6.13 etherscan==0.6.11'

- name: install vyper
run: pip install git+https://github.com/vyperlang/vyper
Expand Down
7 changes: 6 additions & 1 deletion ape-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@ name: yearn-v3-vault-periphery

plugins:
- name: solidity
version: 0.6.11
- name: vyper
version: 0.6.13
- name: etherscan
version: 0.6.11
- name: hardhat
version: 0.6.13
- name: infura
version: 0.6.5

default_ecosystem: ethereum

dependencies:
- name: openzeppelin
github: OpenZeppelin/openzeppelin-contracts
version: 4.8.2
ref: 4.8.2
- name: yearn-vaults
github: yearn/yearn-vaults-v3
ref: v3.0.1
Expand Down
105 changes: 81 additions & 24 deletions contracts/Managers/RoleManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,46 @@ import {IVault} from "@yearn-vaults/interfaces/IVault.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Governance2Step} from "@periphery/utils/Governance2Step.sol";
import {HealthCheckAccountant} from "../accountants/HealthCheckAccountant.sol";
import {GenericDebtAllocatorFactory} from "../debtAllocators/GenericDebtAllocatorFactory.sol";
import {DebtAllocatorFactory} from "../debtAllocators/DebtAllocatorFactory.sol";

/// @title Yearn V3 Vault Role Manager.
contract RoleManager is Governance2Step {
/// @notice Revert message for when a vault has already been deployed.
error AlreadyDeployed(address _vault);

/// @notice Emitted when a new vault has been deployed or added.
event AddedNewVault(
address indexed vault,
address indexed debtAllocator,
uint256 rating
);

/// @notice Emitted when a vaults debt allocator is updated.
event UpdateDebtAllocator(
address indexed vault,
address indexed debtAllocator
);

/// @notice Emitted when a new address is set for a position.
event UpdatePositionHolder(
bytes32 indexed position,
address indexed newAddress
);

/// @notice Emitted when a vault is removed.
event RemovedVault(address indexed vault);

/// @notice Emitted when a new set of roles is set for a position
event UpdatePositionRoles(bytes32 indexed position, uint256 newRoles);

/// @notice Emitted when the defaultProfitMaxUnlock variable is updated.
event UpdateDefaultProfitMaxUnlock(uint256 newDefaultProfitMaxUnlock);

/// @notice Emitted when a new vault has been deployed or added.
event AddedNewVault(
address indexed vault,
address indexed debtAllocator,
uint256 rating
);

/// @notice Emitted when a vault is removed.
event RemovedVault(address indexed vault);
/// @notice Position struct
struct Position {
address holder;
uint96 roles;
}

/// @notice Config that holds all vault info.
struct VaultConfig {
Expand All @@ -41,12 +56,6 @@ contract RoleManager is Governance2Step {
uint256 index;
}

/// @notice Position struct
struct Position {
address holder;
uint96 roles;
}

/// @notice Only allow either governance or the position holder to call.
modifier onlyPositionHolder(bytes32 _positionId) {
_isPositionHolder(_positionId);
Expand Down Expand Up @@ -109,6 +118,9 @@ contract RoleManager is Governance2Step {
mapping(bytes32 => Position) internal _positions;
/// @notice Mapping of vault addresses to its config.
mapping(address => VaultConfig) public vaultConfig;
/// @notice Mapping of underlying asset, api version and rating to vault.
mapping(address => mapping(string => mapping(uint256 => address)))
public _assetToVault;

constructor(
address _governance,
Expand Down Expand Up @@ -261,6 +273,12 @@ contract RoleManager is Governance2Step {
_profitMaxUnlockTime
);

// Check that a vault does not exist for that asset, api and rating.
// This reverts late to not waste gas when used correctly.
string memory _apiVersion = IVault(_vault).apiVersion();
if (_assetToVault[_asset][_apiVersion][_rating] != address(0))
revert AlreadyDeployed(_assetToVault[_asset][_apiVersion][_rating]);

// Deploy a new debt allocator for the vault.
address _debtAllocator = _deployAllocator(_vault);

Expand All @@ -282,6 +300,9 @@ contract RoleManager is Governance2Step {
index: vaults.length
});

// Add the vault to the mapping.
_assetToVault[_asset][_apiVersion][_rating] = _vault;

// Add the vault to the array.
vaults.push(_vault);

Expand All @@ -302,8 +323,9 @@ contract RoleManager is Governance2Step {
// If we have a factory set.
if (factory != address(0)) {
// Deploy a new debt allocator for the vault with Brain as the gov.
_debtAllocator = GenericDebtAllocatorFactory(factory)
.newGenericDebtAllocator(_vault, getPositionHolder(BRAIN));
_debtAllocator = DebtAllocatorFactory(factory).newDebtAllocator(
_vault
);
} else {
// If no factory is set we should be using one central allocator.
_debtAllocator = getPositionHolder(DEBT_ALLOCATOR);
Expand Down Expand Up @@ -438,6 +460,12 @@ contract RoleManager is Governance2Step {
) public virtual onlyPositionHolder(DADDY) {
require(_rating > 0 && _rating < 6, "rating out of range");

// Check that a vault does not exist for that asset, api and rating.
address _asset = IVault(_vault).asset();
string memory _apiVersion = IVault(_vault).apiVersion();
if (_assetToVault[_asset][_apiVersion][_rating] != address(0))
revert AlreadyDeployed(_assetToVault[_asset][_apiVersion][_rating]);

// If not the current role manager.
if (IVault(_vault).role_manager() != address(this)) {
// Accept the position of role manager.
Expand All @@ -450,6 +478,7 @@ contract RoleManager is Governance2Step {
// Check if the vault has been endorsed yet in the registry.
if (!Registry(registry).isEndorsed(_vault)) {
// If not endorse it.
// NOTE: This will revert if adding a vault of an older version.
Registry(registry).endorseMultiStrategyVault(_vault);
}

Expand All @@ -463,12 +492,15 @@ contract RoleManager is Governance2Step {

// Add the vault config to the mapping.
vaultConfig[_vault] = VaultConfig({
asset: IVault(_vault).asset(),
asset: _asset,
rating: _rating,
debtAllocator: _debtAllocator,
index: vaults.length
});

// Add the vault to the mapping.
_assetToVault[_asset][_apiVersion][_rating] = _vault;

// Add the vault to the array.
vaults.push(_vault);

Expand Down Expand Up @@ -512,6 +544,9 @@ contract RoleManager is Governance2Step {

// Update the vaults config.
vaultConfig[_vault].debtAllocator = _debtAllocator;

// Emit event.
emit UpdateDebtAllocator(_vault, _debtAllocator);
}

/**
Expand All @@ -522,24 +557,29 @@ contract RoleManager is Governance2Step {
function removeVault(
address _vault
) external virtual onlyPositionHolder(BRAIN) {
// Get the vault specific config.
VaultConfig memory config = vaultConfig[_vault];
// Make sure the vault has been added to the role manager.
require(vaultConfig[_vault].asset != address(0), "vault not added");
require(config.asset != address(0), "vault not added");

// Transfer the role manager position.
IVault(_vault).transfer_role_manager(chad);

// Index that the vault is in the array.
uint256 index = vaultConfig[_vault].index;
// Address of the vault to replace it with.
address vaultToMove = vaults[vaults.length - 1];

// Move the last vault to the index of `_vault`
vaults[index] = vaultToMove;
vaultConfig[vaultToMove].index = index;
vaults[config.index] = vaultToMove;
vaultConfig[vaultToMove].index = config.index;

// Remove the last item.
vaults.pop();

// Delete the vault from the mapping.
delete _assetToVault[config.asset][IVault(_vault).apiVersion()][
config.rating
];

// Delete the config for `_vault`.
delete vaultConfig[_vault];

Expand Down Expand Up @@ -611,6 +651,23 @@ contract RoleManager is Governance2Step {
return vaults;
}

/**
* @notice Get the vault for a specific asset, api and rating.
* @dev This will return address(0) if one has not been added or deployed.
*
* @param _asset The underlying asset used.
* @param _apiVersion The version of the vault.
* @param _rating The rating of the vault.
* @return The vault for the specified `_asset`, `_apiVersion` and `_rating`.
*/
function getVault(
address _asset,
string memory _apiVersion,
uint256 _rating
) external view virtual returns (address) {
return _assetToVault[_asset][_apiVersion][_rating];
}

/**
* @notice Check if a vault is managed by this contract.
* @dev This will check if the `asset` variable in the struct has been
Expand Down
6 changes: 6 additions & 0 deletions contracts/Mocks/MockOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.18;

import {AprOracle} from "@periphery/AprOracle/AprOracle.sol";

contract MockOracle is AprOracle {}
54 changes: 54 additions & 0 deletions contracts/Mocks/MockTokenizedStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.18;

import {MockTokenizedStrategy} from "@yearn-vaults/test/mocks/ERC4626/MockTokenizedStrategy.sol";

contract MockTokenized is MockTokenizedStrategy {
uint256 public apr;
uint256 public loss;
uint256 public limit;

constructor(
address _asset,
string memory _name,
address _management,
address _keeper,
uint256 _apr
) MockTokenizedStrategy(_asset, _name, _management, _keeper) {
apr = _apr;
}

function aprAfterDebtChange(
address,
int256
) external view returns (uint256) {
return apr;
}

function setApr(uint256 _apr) external {
apr = _apr;
}

function realizeLoss(uint256 _amount) external {
strategyStorage().asset.transfer(msg.sender, _amount);
strategyStorage().totalIdle -= _amount;
strategyStorage().totalDebt += _amount;
}

function tendThis(uint256) external {}

function availableWithdrawLimit(
address _owner
) public view virtual override returns (uint256) {
if (limit != 0) {
uint256 _totalAssets = strategyStorage().totalIdle;
return _totalAssets > limit ? _totalAssets - limit : 0;
} else {
return super.availableWithdrawLimit(_owner);
}
}

function setLimit(uint256 _limit) external {
limit = _limit;
}
}
Loading

0 comments on commit bd7c8c4

Please sign in to comment.