diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 165e029..4f86406 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Install node.js dependencies run: yarn --frozen-lockfile - name: Run linter on *.sol and *.json diff --git a/README.md b/README.md index 1002529..db5fe6d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ cd tokenized-strategy-ape-mix -### Set up your virtual enviorment +### Set up your virtual environment python3 -m venv venv @@ -30,7 +30,7 @@ ape test -### Set your enviorment Variables +### Set your environment Variables export WEB3_INFURA_PROJECT_ID=your_infura_api_key @@ -43,11 +43,10 @@ Deployment of periphery contracts such as the [Registry Factory](https://github. This can be done permissionlessly if the most recent contract has not yet been deployed on a chain you would like to use it on. 1. [Add an Ape account](https://docs.apeworx.io/ape/stable/commands/accounts.html) -2. Go to the contracts specific deployment script under `scripts/` and add your account name to the `accounts.load("you_acct_name")` at the top of the script. -3. Run the deployment script +2. Run the deployment the contracts specific deployment script under `scripts/` ```sh ape run scripts/deploy_contract_name.py --network YOUR_RPC_URL ``` - For chains that don't support 1559 tx's you may need to add a `type="0x0"` argument at the end of the deployment tx. - - ie `tx = deployer_contract.deploy(bytecode, salt, sender=deployer, type="0x0")` + - ie `tx = deployer_contract.deployCreate2(salt, init_code, sender=deployer, type="0x0")` 3. The address the contract was deployed at will print in the console and should match any other chain the same version has been deployed on. \ No newline at end of file diff --git a/ape-config.yaml b/ape-config.yaml index 1485aab..b4085b8 100644 --- a/ape-config.yaml +++ b/ape-config.yaml @@ -17,7 +17,7 @@ default_ecosystem: ethereum dependencies: - name: openzeppelin github: OpenZeppelin/openzeppelin-contracts - ref: 4.8.2 + ref: 4.7.3 - name: yearn-vaults github: yearn/yearn-vaults-v3 @@ -41,7 +41,7 @@ dependencies: solidity: import_remapping: - - "@openzeppelin/contracts=openzeppelin/v4.8.2" + - "@openzeppelin/contracts=openzeppelin/v4.7.3" - "@yearn-vaults=yearn-vaults/v3.0.1" - "@tokenized-strategy=tokenized-strategy/v3.0.1" - "@periphery=periphery/master" diff --git a/contracts/Keeper.sol b/contracts/Keeper.sol new file mode 100644 index 0000000..16e8b8f --- /dev/null +++ b/contracts/Keeper.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity 0.8.18; + +interface IStrategy { + function report() external returns (uint256, uint256); + + function tend() external; +} + +interface IVault { + function process_report(address) external returns (uint256, uint256); +} + +/** + * @title Keeper + * @notice + * To allow permissionless reporting on V3 vaults and strategies. + * + * This will do low level calls so that in can be used without reverting + * it the roles have not been set or the functions are not available. + */ +contract Keeper { + /** + * @notice Reports on a strategy. + */ + function report(address _strategy) external returns (uint256, uint256) { + // Call the target with the provided calldata. + (bool success, bytes memory result) = _strategy.call( + abi.encodeWithSelector(IStrategy.report.selector) + ); + + if (success) { + return abi.decode(result, (uint256, uint256)); + } + } + + /** + * @notice Tends a strategy. + */ + function tend(address _strategy) external { + _strategy.call(abi.encodeWithSelector(IStrategy.tend.selector)); + } + + /** + * @notice Report strategy profits on a vault. + */ + function process_report( + address _vault, + address _strategy + ) external returns (uint256, uint256) { + // Call the target with the provided calldata. + (bool success, bytes memory result) = _vault.call( + abi.encodeCall(IVault.process_report, _strategy) + ); + + if (success) { + return abi.decode(result, (uint256, uint256)); + } + } +} diff --git a/contracts/Managers/RoleManager.sol b/contracts/Managers/RoleManager.sol index e961bb9..83d391a 100644 --- a/contracts/Managers/RoleManager.sol +++ b/contracts/Managers/RoleManager.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.18; import {Roles} from "../libraries/Roles.sol"; import {Registry} from "../registry/Registry.sol"; import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Governance2Step} from "@periphery/utils/Governance2Step.sol"; import {HealthCheckAccountant} from "../accountants/HealthCheckAccountant.sol"; @@ -18,7 +19,7 @@ contract RoleManager is Governance2Step { event AddedNewVault( address indexed vault, address indexed debtAllocator, - uint256 rating + uint256 category ); /// @notice Emitted when a vaults debt allocator is updated. @@ -51,7 +52,7 @@ contract RoleManager is Governance2Step { /// @notice Config that holds all vault info. struct VaultConfig { address asset; - uint256 rating; + uint256 category; address debtAllocator; uint256 index; } @@ -112,13 +113,11 @@ contract RoleManager is Governance2Step { /// @notice Default time until profits are fully unlocked for new vaults. uint256 public defaultProfitMaxUnlock = 10 days; - /// @notice Mapping of a numerical rating to its string equivalent. - mapping(uint256 => string) public ratingToString; /// @notice Mapping of position ID to position information. 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. + /// @notice Mapping of underlying asset, api version and category to vault. mapping(address => mapping(string => mapping(uint256 => address))) internal _assetToVault; @@ -128,7 +127,8 @@ contract RoleManager is Governance2Step { address _brain, address _security, address _keeper, - address _strategyManager + address _strategyManager, + address _registry ) Governance2Step(_governance) { require(_daddy != address(0), "ZERO ADDRESS"); // Set the immutable address that will take over role manager @@ -143,13 +143,14 @@ contract RoleManager is Governance2Step { roles: uint96(Roles.ALL) }); - // Brain can process reports, update debt and adjust the queue. + // Setup default roles for Brain. _positions[BRAIN] = Position({ holder: _brain, roles: uint96( Roles.REPORTING_MANAGER | Roles.DEBT_MANAGER | Roles.QUEUE_MANAGER | + Roles.DEPOSIT_LIMIT_MANAGER | Roles.DEBT_PURCHASER ) }); @@ -160,13 +161,13 @@ contract RoleManager is Governance2Step { roles: uint96(Roles.MAX_DEBT_MANAGER) }); - // The keeper can process reports and update debt. + // The keeper can process reports. _positions[KEEPER] = Position({ holder: _keeper, roles: uint96(Roles.REPORTING_MANAGER) }); - // Set just the roles for a debt allocator. + // Debt allocators manage debt and also need to process reports. _positions[DEBT_ALLOCATOR].roles = uint96( Roles.REPORTING_MANAGER | Roles.DEBT_MANAGER ); @@ -179,12 +180,8 @@ contract RoleManager is Governance2Step { ) }); - // Set up the ratingToString mapping. - ratingToString[1] = "A"; - ratingToString[2] = "B"; - ratingToString[3] = "C"; - ratingToString[4] = "D"; - ratingToString[5] = "F"; + // Set the registry + _positions[REGISTRY].holder = _registry; } /*////////////////////////////////////////////////////////////// @@ -195,79 +192,78 @@ contract RoleManager is Governance2Step { * @notice Creates a new endorsed vault with default profit max * unlock time and doesn't set the deposit limit. * @param _asset Address of the underlying asset. - * @param _rating Rating of the vault. + * @param _category Category of the vault. * @return _vault Address of the newly created vault. */ function newVault( address _asset, - uint256 _rating + uint256 _category ) external virtual onlyPositionHolder(DADDY) returns (address) { - return _newVault(_asset, _rating, 0, defaultProfitMaxUnlock); + return _newVault(_asset, _category, 0, defaultProfitMaxUnlock); } /** * @notice Creates a new endorsed vault with default profit max unlock time. * @param _asset Address of the underlying asset. - * @param _rating Rating of the vault. + * @param _category Category of the vault. * @param _depositLimit The deposit limit to start the vault with. * @return _vault Address of the newly created vault. */ function newVault( address _asset, - uint256 _rating, + uint256 _category, uint256 _depositLimit ) external virtual onlyPositionHolder(DADDY) returns (address) { return - _newVault(_asset, _rating, _depositLimit, defaultProfitMaxUnlock); + _newVault(_asset, _category, _depositLimit, defaultProfitMaxUnlock); } /** * @notice Creates a new endorsed vault. * @param _asset Address of the underlying asset. - * @param _rating Rating of the vault. + * @param _category Category of the vault. * @param _depositLimit The deposit limit to start the vault with. * @param _profitMaxUnlockTime Time until profits are fully unlocked. * @return _vault Address of the newly created vault. */ function newVault( address _asset, - uint256 _rating, + uint256 _category, uint256 _depositLimit, uint256 _profitMaxUnlockTime ) external virtual onlyPositionHolder(DADDY) returns (address) { - return _newVault(_asset, _rating, _depositLimit, _profitMaxUnlockTime); + return + _newVault(_asset, _category, _depositLimit, _profitMaxUnlockTime); } /** * @notice Creates a new endorsed vault. * @param _asset Address of the underlying asset. - * @param _rating Rating of the vault. + * @param _category Category of the vault. * @param _depositLimit The deposit limit to start the vault with. * @param _profitMaxUnlockTime Time until profits are fully unlocked. * @return _vault Address of the newly created vault. */ function _newVault( address _asset, - uint256 _rating, + uint256 _category, uint256 _depositLimit, uint256 _profitMaxUnlockTime ) internal virtual returns (address _vault) { - require(_rating > 0 && _rating < 6, "rating out of range"); + string memory _categoryString = Strings.toString(_category); - // Create the name and symbol to be standardized based on rating. - string memory ratingString = ratingToString[_rating]; - // Name is "{SYMBOL}-{RATING} yVault" + // Name is "{SYMBOL}-{CATEGORY} yVault" string memory _name = string( abi.encodePacked( ERC20(_asset).symbol(), "-", - ratingString, + _categoryString, " yVault" ) ); - // Symbol is "yv{SYMBOL}-{RATING}". + // Symbol is "yv{SYMBOL}-{CATEGORY}". string memory _symbol = string( - abi.encodePacked("yv", ERC20(_asset).symbol(), "-", ratingString) + abi.encodePacked("yv", ERC20(_asset).symbol(), "-", _categoryString) ); // Deploy through the registry so it is automatically endorsed. @@ -279,11 +275,13 @@ contract RoleManager is Governance2Step { _profitMaxUnlockTime ); - // Check that a vault does not exist for that asset, api and rating. + // Check that a vault does not exist for that asset, api and category. // 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]); + if (_assetToVault[_asset][_apiVersion][_category] != address(0)) + revert AlreadyDeployed( + _assetToVault[_asset][_apiVersion][_category] + ); // Deploy a new debt allocator for the vault. address _debtAllocator = _deployAllocator(_vault); @@ -301,19 +299,19 @@ contract RoleManager is Governance2Step { // Add the vault config to the mapping. vaultConfig[_vault] = VaultConfig({ asset: _asset, - rating: _rating, + category: _category, debtAllocator: _debtAllocator, index: vaults.length }); // Add the vault to the mapping. - _assetToVault[_asset][_apiVersion][_rating] = _vault; + _assetToVault[_asset][_apiVersion][_category] = _vault; // Add the vault to the array. vaults.push(_vault); // Emit event for new vault. - emit AddedNewVault(_vault, _debtAllocator, _rating); + emit AddedNewVault(_vault, _debtAllocator, _category); } /** @@ -442,36 +440,36 @@ contract RoleManager is Governance2Step { //////////////////////////////////////////////////////////////*/ /** - * @notice Adds a new vault to the RoleManager with the specified rating. + * @notice Adds a new vault to the RoleManager with the specified category. * @dev If not already endorsed this function will endorse the vault. * A new debt allocator will be deployed and configured. * @param _vault Address of the vault to be added. - * @param _rating Rating associated with the vault. + * @param _category Category associated with the vault. */ - function addNewVault(address _vault, uint256 _rating) external virtual { + function addNewVault(address _vault, uint256 _category) external virtual { address _debtAllocator = _deployAllocator(_vault); - addNewVault(_vault, _rating, _debtAllocator); + addNewVault(_vault, _category, _debtAllocator); } /** - * @notice Adds a new vault to the RoleManager with the specified rating and debt allocator. + * @notice Adds a new vault to the RoleManager with the specified category and debt allocator. * @dev If not already endorsed this function will endorse the vault. * @param _vault Address of the vault to be added. - * @param _rating Rating associated with the vault. + * @param _category Category associated with the vault. * @param _debtAllocator Address of the debt allocator for the vault. */ function addNewVault( address _vault, - uint256 _rating, + uint256 _category, address _debtAllocator ) 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. + // Check that a vault does not exist for that asset, api and category. 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 (_assetToVault[_asset][_apiVersion][_category] != address(0)) + revert AlreadyDeployed( + _assetToVault[_asset][_apiVersion][_category] + ); // If not the current role manager. if (IVault(_vault).role_manager() != address(this)) { @@ -500,19 +498,19 @@ contract RoleManager is Governance2Step { // Add the vault config to the mapping. vaultConfig[_vault] = VaultConfig({ asset: _asset, - rating: _rating, + category: _category, debtAllocator: _debtAllocator, index: vaults.length }); // Add the vault to the mapping. - _assetToVault[_asset][_apiVersion][_rating] = _vault; + _assetToVault[_asset][_apiVersion][_category] = _vault; // Add the vault to the array. vaults.push(_vault); // Emit event. - emit AddedNewVault(_vault, _debtAllocator, _rating); + emit AddedNewVault(_vault, _debtAllocator, _category); } /** @@ -556,6 +554,30 @@ contract RoleManager is Governance2Step { emit UpdateDebtAllocator(_vault, _debtAllocator); } + /** + * @notice Update a `_vault`s keeper to a specified `_keeper`. + * @param _vault Address of the vault to update the keeper for. + * @param _keeper Address of the new keeper. + */ + function updateKeeper( + address _vault, + address _keeper + ) external virtual onlyPositionHolder(BRAIN) { + // Make sure the vault has been added to the role manager. + require(vaultConfig[_vault].asset != address(0), "vault not added"); + + // Remove the roles from the old keeper if active. + address defaultKeeper = getPositionHolder(KEEPER); + if ( + _keeper != defaultKeeper && IVault(_vault).roles(defaultKeeper) != 0 + ) { + _setRole(_vault, Position(defaultKeeper, 0)); + } + + // Give the new keeper the relevant roles. + _setRole(_vault, Position(_keeper, _positions[KEEPER].roles)); + } + /** * @notice Removes a vault from the RoleManager. * @dev This will NOT un-endorse the vault from the registry. @@ -584,7 +606,7 @@ contract RoleManager is Governance2Step { // Delete the vault from the mapping. delete _assetToVault[config.asset][IVault(_vault).apiVersion()][ - config.rating + config.category ]; // Delete the config for `_vault`. @@ -629,8 +651,11 @@ contract RoleManager is Governance2Step { bytes32 _position, uint256 _newRoles ) external virtual onlyGovernance { - // Cannot change the debt allocator roles since it can be updated - require(_position != DEBT_ALLOCATOR, "cannot update"); + // Cannot change the debt allocator or keeper roles since holder can be updated. + require( + _position != DEBT_ALLOCATOR && _position != KEEPER, + "cannot update" + ); _positions[_position].roles = uint96(_newRoles); emit UpdatePositionRoles(_position, _newRoles); @@ -682,20 +707,20 @@ contract RoleManager is Governance2Step { } /** - * @notice Get the vault for a specific asset, api and rating. + * @notice Get the vault for a specific asset, api and category. * @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`. + * @param _category The category of the vault. + * @return The vault for the specified `_asset`, `_apiVersion` and `_category`. */ function getVault( address _asset, string memory _apiVersion, - uint256 _rating + uint256 _category ) external view virtual returns (address) { - return _assetToVault[_asset][_apiVersion][_rating]; + return _assetToVault[_asset][_apiVersion][_category]; } /** @@ -728,13 +753,15 @@ contract RoleManager is Governance2Step { } /** - * @notice Get the rating for a specific vault. + * @notice Get the category for a specific vault. * @dev Will return 0 if the vault is not managed by this contract. * @param _vault Address of the vault. - * @return . The rating of the vault if any. + * @return . The category of the vault if any. */ - function getRating(address _vault) external view virtual returns (uint256) { - return vaultConfig[_vault].rating; + function getCategory( + address _vault + ) external view virtual returns (uint256) { + return vaultConfig[_vault].category; } /** diff --git a/contracts/accountants/HealthCheckAccountant.sol b/contracts/accountants/HealthCheckAccountant.sol index b6afab9..d361302 100644 --- a/contracts/accountants/HealthCheckAccountant.sol +++ b/contracts/accountants/HealthCheckAccountant.sol @@ -35,17 +35,10 @@ contract HealthCheckAccountant { ); /// @notice An event emitted when a custom fee configuration is updated. - event UpdateCustomFeeConfig( - address indexed vault, - address indexed strategy, - Fee custom_config - ); + event UpdateCustomFeeConfig(address indexed vault, Fee custom_config); /// @notice An event emitted when a custom fee configuration is removed. - event RemovedCustomFeeConfig( - address indexed vault, - address indexed strategy - ); + event RemovedCustomFeeConfig(address indexed vault); /// @notice An event emitted when the `maxLoss` parameter is updated. event UpdateMaxLoss(uint256 maxLoss); @@ -80,6 +73,11 @@ contract HealthCheckAccountant { _; } + modifier onlyFeeManagerOrRecipient() { + _checkFeeManagerOrRecipient(); + _; + } + modifier onlyAddedVaults() { _checkVaultIsAdded(); _; @@ -96,6 +94,13 @@ contract HealthCheckAccountant { ); } + function _checkFeeManagerOrRecipient() internal view virtual { + require( + msg.sender == feeRecipient || msg.sender == feeManager, + "!recipient" + ); + } + function _checkVaultIsAdded() internal view virtual { require(vaults[msg.sender], "vault not added"); } @@ -106,38 +111,41 @@ contract HealthCheckAccountant { /// @notice Constant defining the number of seconds in a year. uint256 internal constant SECS_PER_YEAR = 31_556_952; + /// @notice Constant defining the management fee threshold. + uint16 public constant MANAGEMENT_FEE_THRESHOLD = 200; + /// @notice Constant defining the performance fee threshold. - uint16 internal constant PERFORMANCE_FEE_THRESHOLD = 5_000; + uint16 public constant PERFORMANCE_FEE_THRESHOLD = 5_000; - /// @notice Constant defining the management fee threshold. - uint16 internal constant MANAGEMENT_FEE_THRESHOLD = 200; + /// @notice The amount of max loss to use when redeeming from vaults. + uint256 public maxLoss; /// @notice The address of the fee manager. address public feeManager; - /// @notice The address of the future fee manager. - address public futureFeeManager; - /// @notice The address of the fee recipient. address public feeRecipient; - /// @notice The amount of max loss to use when redeeming from vaults. - uint256 public maxLoss; - /// @notice An address that can add or remove vaults. address public vaultManager; - /// @notice Mapping to track added vaults. - mapping(address => bool) public vaults; + /// @notice The address of the future fee manager. + address public futureFeeManager; /// @notice The default fee configuration. Fee public defaultConfig; - /// @notice Mapping vault => strategy => custom Fee config if any. - mapping(address => mapping(address => Fee)) public customConfig; + /// @notice Mapping to track added vaults. + mapping(address => bool) public vaults; + + /// @notice Mapping vault => custom Fee config if any. + mapping(address => Fee) public customConfig; + + /// @notice Mapping vault => flag to use a custom config. + mapping(address => uint256) internal _useCustomConfig; - /// @notice Mapping vault => strategy => flag to use a custom config. - mapping(address => mapping(address => uint256)) internal _useCustomConfig; + /// @notice Mapping vault => strategy => flag for one time healthcheck skips. + mapping(address => mapping(address => bool)) skipHealthCheck; constructor( address _feeManager, @@ -188,8 +196,8 @@ contract HealthCheckAccountant { Fee memory fee; // Check if there is a custom config to use. - if (_useCustomConfig[msg.sender][strategy] != 0) { - fee = customConfig[msg.sender][strategy]; + if (_useCustomConfig[msg.sender] != 0) { + fee = customConfig[msg.sender]; } else { // Otherwise use the default. fee = defaultConfig; @@ -213,8 +221,13 @@ contract HealthCheckAccountant { // Only charge performance fees if there is a gain. if (gain > 0) { - // Setting `maxGain` to 0 will disable the healthcheck on profits. - if (fee.maxGain > 0) { + // If we are skipping the healthcheck this report + if (skipHealthCheck[msg.sender][strategy]) { + // Make sure it is reset for the next one. + skipHealthCheck[msg.sender][strategy] = false; + + // Setting `maxGain` to 0 will disable the healthcheck on profits. + } else if (fee.maxGain > 0) { require( gain <= (strategyParams.current_debt * (fee.maxGain)) / MAX_BPS, @@ -224,8 +237,13 @@ contract HealthCheckAccountant { totalFees += (gain * (fee.performanceFee)) / MAX_BPS; } else { - // Setting `maxLoss` to 10_000 will disable the healthcheck on losses. - if (fee.maxLoss < MAX_BPS) { + // If we are skipping the healthcheck this report + if (skipHealthCheck[msg.sender][strategy]) { + // Make sure it is reset for the next one. + skipHealthCheck[msg.sender][strategy] = false; + + // Setting `maxLoss` to 10_000 will disable the healthcheck on losses. + } else if (fee.maxLoss < MAX_BPS) { require( loss <= (strategyParams.current_debt * (fee.maxLoss)) / MAX_BPS, @@ -357,9 +375,8 @@ contract HealthCheckAccountant { } /** - * @notice Function to set a custom fee configuration for a specific strategy in a specific vault. + * @notice Function to set a custom fee configuration for a specific vault. * @param vault The vault the strategy is hooked up to. - * @param strategy The strategy to customize. * @param customManagement Custom annual management fee to charge. * @param customPerformance Custom performance fee to charge. * @param customRefund Custom refund ratio to give back on losses. @@ -369,7 +386,6 @@ contract HealthCheckAccountant { */ function setCustomConfig( address vault, - address strategy, uint16 customManagement, uint16 customPerformance, uint16 customRefund, @@ -390,8 +406,8 @@ contract HealthCheckAccountant { ); require(customMaxLoss <= MAX_BPS, "too high"); - // Set the strategy's custom config. - customConfig[vault][strategy] = Fee({ + // Create the vault's custom config. + Fee memory _config = Fee({ managementFee: customManagement, performanceFee: customPerformance, refundRatio: customRefund, @@ -400,36 +416,47 @@ contract HealthCheckAccountant { maxLoss: customMaxLoss }); + // Store the config. + customConfig[vault] = _config; + // Set the custom flag. - _useCustomConfig[vault][strategy] = 1; + _useCustomConfig[vault] = 1; - emit UpdateCustomFeeConfig( - vault, - strategy, - customConfig[vault][strategy] - ); + emit UpdateCustomFeeConfig(vault, _config); } /** - * @notice Function to remove a previously set custom fee configuration for a strategy. + * @notice Function to remove a previously set custom fee configuration for a vault. * @param vault The vault to remove custom setting for. - * @param strategy The strategy to remove custom setting for. */ - function removeCustomConfig( - address vault, - address strategy - ) external virtual onlyFeeManager { - // Ensure custom fees are set for the specified vault and strategy. - require(_useCustomConfig[vault][strategy] != 0, "No custom fees set"); + function removeCustomConfig(address vault) external virtual onlyFeeManager { + // Ensure custom fees are set for the specified vault. + require(_useCustomConfig[vault] != 0, "No custom fees set"); - // Set all the strategy's custom fees to 0. - delete customConfig[vault][strategy]; + // Set all the vaults's custom fees to 0. + delete customConfig[vault]; // Clear the custom flag. - _useCustomConfig[vault][strategy] = 0; + _useCustomConfig[vault] = 0; // Emit relevant event. - emit RemovedCustomFeeConfig(vault, strategy); + emit RemovedCustomFeeConfig(vault); + } + + /** + * @notice Turn off the health check for a specific `vault` `strategy` combo. + * @dev This will only last for one report and get automatically turned back on. + * @param vault Address of the vault. + * @param strategy Address of the strategy. + */ + function turnOffHealthCheck( + address vault, + address strategy + ) external virtual onlyFeeManager { + // Ensure the vault has been added. + require(vaults[vault], "vault not added"); + + skipHealthCheck[vault][strategy] = true; } /** @@ -438,29 +465,25 @@ contract HealthCheckAccountant { * will convert it to a bool for easy view functions. * * @param vault Address of the vault. - * @param strategy Address of the strategy * @return If a custom fee config is set. */ function useCustomConfig( - address vault, - address strategy + address vault ) external view virtual returns (bool) { - return _useCustomConfig[vault][strategy] != 0; + return _useCustomConfig[vault] != 0; } /** - * @notice Get the full config used for a specific `strategy` and `vault` combo. + * @notice Get the full config used for a specific `vault`. * @param vault Address of the vault. - * @param strategy Address of the strategy. * @return fee The config that would be used during the report. */ - function getStrategyConfig( - address vault, - address strategy + function getVaultConfig( + address vault ) external view returns (Fee memory fee) { // Check if custom config is set. - if (_useCustomConfig[vault][strategy] != 0) { - fee = customConfig[vault][strategy]; + if (_useCustomConfig[vault] != 0) { + fee = customConfig[vault]; } else { // Otherwise use the default. fee = defaultConfig; @@ -518,7 +541,7 @@ contract HealthCheckAccountant { function distribute( address token, uint256 amount - ) public virtual onlyFeeManager { + ) public virtual onlyFeeManagerOrRecipient { ERC20(token).safeTransfer(feeRecipient, amount); emit DistributeRewards(token, amount); @@ -596,22 +619,4 @@ contract HealthCheckAccountant { ERC20(_token).safeApprove(_contract, _amount); } } - - /** - * @notice View function to get the max a performance fee can be. - * @dev This function provides the maximum performance fee that the accountant can charge. - * @return The maximum performance fee. - */ - function performanceFeeThreshold() external pure virtual returns (uint16) { - return PERFORMANCE_FEE_THRESHOLD; - } - - /** - * @notice View function to get the max a management fee can be. - * @dev This function provides the maximum management fee that the accountant can charge. - * @return The maximum management fee. - */ - function managementFeeThreshold() external pure virtual returns (uint16) { - return MANAGEMENT_FEE_THRESHOLD; - } } diff --git a/contracts/debtAllocators/DebtAllocatorFactory.sol b/contracts/debtAllocators/DebtAllocatorFactory.sol index 37b69b0..d927a62 100644 --- a/contracts/debtAllocators/DebtAllocatorFactory.sol +++ b/contracts/debtAllocators/DebtAllocatorFactory.sol @@ -5,6 +5,10 @@ import {DebtAllocator} from "./DebtAllocator.sol"; import {Clonable} from "@periphery/utils/Clonable.sol"; import {Governance} from "@periphery/utils/Governance.sol"; +interface IBaseFee { + function basefee_global() external view returns (uint256); +} + /** * @title YearnV3 Debt Allocator Factory * @author yearn.finance @@ -15,6 +19,9 @@ contract DebtAllocatorFactory is Governance, Clonable { /// @notice Revert message for when a debt allocator already exists. error AlreadyDeployed(address _allocator); + /// @notice An event emitted when the base fee provider is set. + event UpdatedBaseFeeProvider(address baseFeeProvider); + /// @notice An event emitted when a keeper is added or removed. event UpdateKeeper(address indexed keeper, bool allowed); @@ -24,6 +31,9 @@ contract DebtAllocatorFactory is Governance, Clonable { /// @notice An event emitted when a new debt allocator is added or deployed. event NewDebtAllocator(address indexed allocator, address indexed vault); + /// @notice Provider to read current block's base fee. + address public baseFeeProvider; + /// @notice Max the chains base fee can be during debt update. // Will default to max uint256 and need to be set to be used. uint256 public maxAcceptableBaseFee; @@ -77,6 +87,22 @@ contract DebtAllocatorFactory is Governance, Clonable { emit NewDebtAllocator(newAllocator, _vault); } + /** + * @notice + * Used to set our baseFeeProvider, which checks the network's current base + * fee price to determine whether it is an optimal time to harvest or tend. + * + * This may only be called by governance. + * @param _baseFeeProvider Address of our baseFeeProvider + */ + function setBaseFeeOracle( + address _baseFeeProvider + ) external virtual onlyGovernance { + baseFeeProvider = _baseFeeProvider; + + emit UpdatedBaseFeeProvider(_baseFeeProvider); + } + /** * @notice Set the max acceptable base fee. * @dev This defaults to max uint256 and will need to @@ -114,6 +140,9 @@ contract DebtAllocatorFactory is Governance, Clonable { * @return . If the current base fee is acceptable. */ function isCurrentBaseFeeAcceptable() external view virtual returns (bool) { - return maxAcceptableBaseFee >= block.basefee; + address _baseFeeProvider = baseFeeProvider; + if (_baseFeeProvider == address(0)) return true; + return + maxAcceptableBaseFee >= IBaseFee(_baseFeeProvider).basefee_global(); } } diff --git a/tests/accountants/test_healthcheck_accountant.py b/tests/accountants/test_healthcheck_accountant.py index bb871d1..540300a 100644 --- a/tests/accountants/test_healthcheck_accountant.py +++ b/tests/accountants/test_healthcheck_accountant.py @@ -15,13 +15,13 @@ def test_setup(daddy, vault, strategy, healthcheck_accountant, fee_recipient): assert accountant.defaultConfig().maxGain == 10_000 assert accountant.defaultConfig().maxLoss == 0 assert accountant.vaults(vault.address) == False - assert accountant.useCustomConfig(vault.address, strategy.address) == False - assert accountant.customConfig(vault.address, strategy.address).managementFee == 0 - assert accountant.customConfig(vault.address, strategy.address).performanceFee == 0 - assert accountant.customConfig(vault.address, strategy.address).refundRatio == 0 - assert accountant.customConfig(vault.address, strategy.address).maxFee == 0 - assert accountant.customConfig(vault.address, strategy.address).maxGain == 0 - assert accountant.customConfig(vault.address, strategy.address).maxLoss == 0 + assert accountant.useCustomConfig(vault.address) == False + assert accountant.customConfig(vault.address).managementFee == 0 + assert accountant.customConfig(vault.address).performanceFee == 0 + assert accountant.customConfig(vault.address).refundRatio == 0 + assert accountant.customConfig(vault.address).maxFee == 0 + assert accountant.customConfig(vault.address).maxGain == 0 + assert accountant.customConfig(vault.address).maxLoss == 0 def test_add_vault( @@ -428,7 +428,7 @@ def test_set_custom_config(daddy, vault, strategy, healthcheck_accountant): accountant = healthcheck_accountant accountant.addVault(vault.address, sender=daddy) - assert accountant.customConfig(vault.address, strategy.address) == ( + assert accountant.customConfig(vault.address) == ( 0, 0, 0, @@ -446,7 +446,6 @@ def test_set_custom_config(daddy, vault, strategy, healthcheck_accountant): tx = accountant.setCustomConfig( vault.address, - strategy.address, new_management, new_performance, new_refund, @@ -460,7 +459,7 @@ def test_set_custom_config(daddy, vault, strategy, healthcheck_accountant): assert len(event) == 1 assert event[0].vault == vault.address - assert event[0].strategy == strategy.address + config = list(event[0].custom_config) assert config[0] == new_management @@ -470,11 +469,8 @@ def test_set_custom_config(daddy, vault, strategy, healthcheck_accountant): assert config[4] == new_max_gain assert config[5] == new_max_loss - assert ( - accountant.customConfig(vault.address, strategy.address) - != accountant.defaultConfig() - ) - assert accountant.customConfig(vault.address, strategy.address) == ( + assert accountant.customConfig(vault.address) != accountant.defaultConfig() + assert accountant.customConfig(vault.address) == ( new_management, new_performance, new_refund, @@ -488,7 +484,7 @@ def test_remove_custom_config(daddy, vault, strategy, healthcheck_accountant): accountant = healthcheck_accountant accountant.addVault(vault.address, sender=daddy) - assert accountant.customConfig(vault.address, strategy.address) == ( + assert accountant.customConfig(vault.address) == ( 0, 0, 0, @@ -498,7 +494,7 @@ def test_remove_custom_config(daddy, vault, strategy, healthcheck_accountant): ) with ape.reverts("No custom fees set"): - accountant.removeCustomConfig(vault.address, strategy.address, sender=daddy) + accountant.removeCustomConfig(vault.address, sender=daddy) new_management = 20 new_performance = 2_000 @@ -509,7 +505,6 @@ def test_remove_custom_config(daddy, vault, strategy, healthcheck_accountant): accountant.setCustomConfig( vault.address, - strategy.address, new_management, new_performance, new_refund, @@ -519,12 +514,9 @@ def test_remove_custom_config(daddy, vault, strategy, healthcheck_accountant): sender=daddy, ) - assert accountant.useCustomConfig(vault.address, strategy.address) == True - assert ( - accountant.customConfig(vault.address, strategy.address) - != accountant.defaultConfig() - ) - assert accountant.customConfig(vault.address, strategy.address) == ( + assert accountant.useCustomConfig(vault.address) == True + assert accountant.customConfig(vault.address) != accountant.defaultConfig() + assert accountant.customConfig(vault.address) == ( new_management, new_performance, new_refund, @@ -533,15 +525,14 @@ def test_remove_custom_config(daddy, vault, strategy, healthcheck_accountant): new_max_loss, ) - tx = accountant.removeCustomConfig(vault.address, strategy.address, sender=daddy) + tx = accountant.removeCustomConfig(vault.address, sender=daddy) event = list(tx.decode_logs(accountant.RemovedCustomFeeConfig)) - assert event[0].strategy == strategy.address assert event[0].vault == vault.address assert len(event) == 1 - assert accountant.customConfig(vault.address, strategy.address) == ( + assert accountant.customConfig(vault.address) == ( 0, 0, 0, @@ -641,7 +632,7 @@ def test_distribute( assert vault.balanceOf(daddy.address) == 0 assert vault.balanceOf(fee_recipient.address) == 0 - with ape.reverts("!fee manager"): + with ape.reverts("!recipient"): accountant.distribute(vault.address, sender=user) tx = accountant.distribute(vault.address, sender=daddy) @@ -949,10 +940,8 @@ def test_report_profit__custom_config( ): accountant = healthcheck_accountant accountant.addVault(vault.address, sender=daddy) - accountant.setCustomConfig( - vault.address, strategy.address, 200, 2_000, 0, 0, 10_000, 0, sender=daddy - ) - config = list(accountant.customConfig(vault.address, strategy.address)) + accountant.setCustomConfig(vault.address, 200, 2_000, 0, 0, 10_000, 0, sender=daddy) + config = list(accountant.customConfig(vault.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -996,10 +985,8 @@ def test_report_no_profit__custom_config( ): accountant = healthcheck_accountant accountant.addVault(vault.address, sender=daddy) - accountant.setCustomConfig( - vault.address, strategy.address, 200, 2_000, 0, 0, 10_000, 0, sender=daddy - ) - config = list(accountant.customConfig(vault.address, strategy.address)) + accountant.setCustomConfig(vault.address, 200, 2_000, 0, 0, 10_000, 0, sender=daddy) + config = list(accountant.customConfig(vault.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -1046,9 +1033,9 @@ def test_report_max_fee__custom_config( accountant.addVault(vault.address, sender=daddy) # SEt max fee of 10% of gain accountant.setCustomConfig( - vault.address, strategy.address, 200, 2_000, 0, 100, 10_000, 0, sender=daddy + vault.address, 200, 2_000, 0, 100, 10_000, 0, sender=daddy ) - config = list(accountant.customConfig(vault.address, strategy.address)) + config = list(accountant.customConfig(vault.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -1096,10 +1083,8 @@ def test_report_profit__custom_zero_max_gain__reverts( accountant = healthcheck_accountant accountant.addVault(vault.address, sender=daddy) # SEt max gain to 1% - accountant.setCustomConfig( - vault.address, strategy.address, 200, 2_000, 0, 100, 1, 0, sender=daddy - ) - config = list(accountant.customConfig(vault.address, strategy.address)) + accountant.setCustomConfig(vault.address, 200, 2_000, 0, 100, 1, 0, sender=daddy) + config = list(accountant.customConfig(vault.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -1136,10 +1121,8 @@ def test_report_loss__custom_zero_max_loss__reverts( accountant = healthcheck_accountant accountant.addVault(vault.address, sender=daddy) # SEt max gain to 0% - accountant.setCustomConfig( - vault.address, strategy.address, 200, 2_000, 0, 100, 0, 0, sender=daddy - ) - config = list(accountant.customConfig(vault.address, strategy.address)) + accountant.setCustomConfig(vault.address, 200, 2_000, 0, 100, 0, 0, sender=daddy) + config = list(accountant.customConfig(vault.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -1178,7 +1161,6 @@ def test_report_refund__custom_config( # SEt refund ratio to 100% accountant.setCustomConfig( vault.address, - strategy.address, 200, 2_000, 10_000, @@ -1187,7 +1169,7 @@ def test_report_refund__custom_config( 10_000, sender=daddy, ) - config = list(accountant.customConfig(vault.address, strategy.address)) + config = list(accountant.customConfig(vault.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -1242,7 +1224,6 @@ def test_report_refund_not_enough_asset__custom_config( # SEt refund ratio to 100% accountant.setCustomConfig( vault.address, - strategy.address, 200, 2_000, 10_000, @@ -1251,7 +1232,7 @@ def test_report_refund_not_enough_asset__custom_config( 10_000, sender=daddy, ) - config = list(accountant.customConfig(vault.address, strategy.address)) + config = list(accountant.customConfig(vault.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) diff --git a/tests/accountants/test_refund_accountant.py b/tests/accountants/test_refund_accountant.py index 49fbf17..79dc9de 100644 --- a/tests/accountants/test_refund_accountant.py +++ b/tests/accountants/test_refund_accountant.py @@ -15,13 +15,13 @@ def test_setup(daddy, vault, strategy, refund_accountant, fee_recipient): assert accountant.defaultConfig().maxGain == 10_000 assert accountant.defaultConfig().maxLoss == 0 assert accountant.vaults(vault.address) == False - assert accountant.useCustomConfig(vault.address, strategy.address) == False - assert accountant.customConfig(vault.address, strategy.address).managementFee == 0 - assert accountant.customConfig(vault.address, strategy.address).performanceFee == 0 - assert accountant.customConfig(vault.address, strategy.address).refundRatio == 0 - assert accountant.customConfig(vault.address, strategy.address).maxFee == 0 - assert accountant.customConfig(vault.address, strategy.address).maxGain == 0 - assert accountant.customConfig(vault.address, strategy.address).maxLoss == 0 + assert accountant.useCustomConfig(vault.address) == False + assert accountant.customConfig(vault.address).managementFee == 0 + assert accountant.customConfig(vault.address).performanceFee == 0 + assert accountant.customConfig(vault.address).refundRatio == 0 + assert accountant.customConfig(vault.address).maxFee == 0 + assert accountant.customConfig(vault.address).maxGain == 0 + assert accountant.customConfig(vault.address).maxLoss == 0 assert accountant.refund(vault.address, strategy.address) == 0 @@ -440,7 +440,7 @@ def test_set_custom_config(daddy, vault, strategy, refund_accountant): accountant = refund_accountant accountant.addVault(vault.address, sender=daddy) - assert accountant.customConfig(vault.address, strategy.address) == ( + assert accountant.customConfig(vault.address) == ( 0, 0, 0, @@ -458,7 +458,6 @@ def test_set_custom_config(daddy, vault, strategy, refund_accountant): tx = accountant.setCustomConfig( vault.address, - strategy.address, new_management, new_performance, new_refund, @@ -472,7 +471,6 @@ def test_set_custom_config(daddy, vault, strategy, refund_accountant): assert len(event) == 1 assert event[0].vault == vault.address - assert event[0].strategy == strategy.address config = list(event[0].custom_config) assert config[0] == new_management @@ -482,11 +480,8 @@ def test_set_custom_config(daddy, vault, strategy, refund_accountant): assert config[4] == new_max_gain assert config[5] == new_max_loss - assert ( - accountant.customConfig(vault.address, strategy.address) - != accountant.defaultConfig() - ) - assert accountant.customConfig(vault.address, strategy.address) == ( + assert accountant.customConfig(vault.address) != accountant.defaultConfig() + assert accountant.customConfig(vault.address) == ( new_management, new_performance, new_refund, @@ -500,7 +495,7 @@ def test_remove_custom_config(daddy, vault, strategy, refund_accountant): accountant = refund_accountant accountant.addVault(vault.address, sender=daddy) - assert accountant.customConfig(vault.address, strategy.address) == ( + assert accountant.customConfig(vault.address) == ( 0, 0, 0, @@ -510,7 +505,7 @@ def test_remove_custom_config(daddy, vault, strategy, refund_accountant): ) with ape.reverts("No custom fees set"): - accountant.removeCustomConfig(vault.address, strategy.address, sender=daddy) + accountant.removeCustomConfig(vault.address, sender=daddy) new_management = 20 new_performance = 2_000 @@ -521,7 +516,6 @@ def test_remove_custom_config(daddy, vault, strategy, refund_accountant): accountant.setCustomConfig( vault.address, - strategy.address, new_management, new_performance, new_refund, @@ -531,12 +525,9 @@ def test_remove_custom_config(daddy, vault, strategy, refund_accountant): sender=daddy, ) - assert accountant.useCustomConfig(vault.address, strategy.address) == True - assert ( - accountant.customConfig(vault.address, strategy.address) - != accountant.defaultConfig() - ) - assert accountant.customConfig(vault.address, strategy.address) == ( + assert accountant.useCustomConfig(vault.address) == True + assert accountant.customConfig(vault.address) != accountant.defaultConfig() + assert accountant.customConfig(vault.address) == ( new_management, new_performance, new_refund, @@ -545,15 +536,14 @@ def test_remove_custom_config(daddy, vault, strategy, refund_accountant): new_max_loss, ) - tx = accountant.removeCustomConfig(vault.address, strategy.address, sender=daddy) + tx = accountant.removeCustomConfig(vault.address, sender=daddy) event = list(tx.decode_logs(accountant.RemovedCustomFeeConfig)) - assert event[0].strategy == strategy.address assert event[0].vault == vault.address assert len(event) == 1 - assert accountant.customConfig(vault.address, strategy.address) == ( + assert accountant.customConfig(vault.address) == ( 0, 0, 0, @@ -653,7 +643,7 @@ def test_distribute( assert vault.balanceOf(daddy.address) == 0 assert vault.balanceOf(fee_recipient.address) == 0 - with ape.reverts("!fee manager"): + with ape.reverts("!recipient"): accountant.distribute(vault.address, sender=user) tx = accountant.distribute(vault.address, sender=daddy) @@ -961,10 +951,8 @@ def test_report_profit__custom_config( ): accountant = refund_accountant accountant.addVault(vault.address, sender=daddy) - accountant.setCustomConfig( - vault.address, strategy.address, 200, 2_000, 0, 0, 10_000, 0, sender=daddy - ) - config = list(accountant.customConfig(vault.address, strategy.address)) + accountant.setCustomConfig(vault.address, 200, 2_000, 0, 0, 10_000, 0, sender=daddy) + config = list(accountant.customConfig(vault.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -1008,10 +996,8 @@ def test_report_no_profit__custom_config( ): accountant = refund_accountant accountant.addVault(vault.address, sender=daddy) - accountant.setCustomConfig( - vault.address, strategy.address, 200, 2_000, 0, 0, 10_000, 0, sender=daddy - ) - config = list(accountant.customConfig(vault.address, strategy.address)) + accountant.setCustomConfig(vault.address, 200, 2_000, 0, 0, 10_000, 0, sender=daddy) + config = list(accountant.customConfig(vault.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -1058,9 +1044,9 @@ def test_report_max_fee__custom_config( accountant.addVault(vault.address, sender=daddy) # SEt max fee of 10% of gain accountant.setCustomConfig( - vault.address, strategy.address, 200, 2_000, 0, 100, 10_000, 0, sender=daddy + vault.address, 200, 2_000, 0, 100, 10_000, 0, sender=daddy ) - config = list(accountant.customConfig(vault.address, strategy.address)) + config = list(accountant.customConfig(vault.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -1108,10 +1094,8 @@ def test_report_profit__custom_zero_max_gain__reverts( accountant = refund_accountant accountant.addVault(vault.address, sender=daddy) # SEt max gain to 1% - accountant.setCustomConfig( - vault.address, strategy.address, 200, 2_000, 0, 100, 1, 0, sender=daddy - ) - config = list(accountant.customConfig(vault.address, strategy.address)) + accountant.setCustomConfig(vault.address, 200, 2_000, 0, 100, 1, 0, sender=daddy) + config = list(accountant.customConfig(vault.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -1148,10 +1132,8 @@ def test_report_loss__custom_zero_max_loss__reverts( accountant = refund_accountant accountant.addVault(vault.address, sender=daddy) # SEt max gain to 0% - accountant.setCustomConfig( - vault.address, strategy.address, 200, 2_000, 0, 100, 0, 0, sender=daddy - ) - config = list(accountant.customConfig(vault.address, strategy.address)) + accountant.setCustomConfig(vault.address, 200, 2_000, 0, 100, 0, 0, sender=daddy) + config = list(accountant.customConfig(vault.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -1190,7 +1172,6 @@ def test_report_refund__custom_config( # SEt refund ratio to 100% accountant.setCustomConfig( vault.address, - strategy.address, 200, 2_000, 10_000, @@ -1199,7 +1180,7 @@ def test_report_refund__custom_config( 10_000, sender=daddy, ) - config = list(accountant.customConfig(vault.address, strategy.address)) + config = list(accountant.customConfig(vault.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) @@ -1254,7 +1235,6 @@ def test_report_refund_not_enough_asset__custom_config( # SEt refund ratio to 100% accountant.setCustomConfig( vault.address, - strategy.address, 200, 2_000, 10_000, @@ -1263,7 +1243,7 @@ def test_report_refund_not_enough_asset__custom_config( 10_000, sender=daddy, ) - config = list(accountant.customConfig(vault.address, strategy.address)) + config = list(accountant.customConfig(vault.address)) vault.add_strategy(strategy.address, sender=daddy) vault.update_max_debt_for_strategy(strategy.address, MAX_INT, sender=daddy) diff --git a/tests/conftest.py b/tests/conftest.py index e4bc436..4c8a90e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,11 +35,6 @@ def fee_recipient(accounts): return accounts[4] -@pytest.fixture(scope="session") -def keeper(accounts): - yield accounts[5] - - @pytest.fixture(scope="session") def user(accounts): return accounts[6] @@ -423,16 +418,19 @@ def debt_allocator(debt_allocator_factory, project, vault, daddy): @pytest.fixture(scope="session") -def deploy_role_manager(project, daddy, brain, security, keeper, strategy_manager): +def deploy_role_manager( + project, daddy, brain, security, keeper, strategy_manager, registry +): def deploy_role_manager( gov=daddy, sms=brain, sec=security, keep=keeper, strategy_manage=strategy_manager, + reg=registry, ): role_manager = daddy.deploy( - project.RoleManager, gov, daddy, sms, sec, keep, strategy_manage + project.RoleManager, gov, daddy, sms, sec, keep, strategy_manage, reg ) return role_manager @@ -442,21 +440,20 @@ def deploy_role_manager( @pytest.fixture(scope="session") def role_manager( - deploy_role_manager, - daddy, - brain, - healthcheck_accountant, - debt_allocator_factory, - registry, + deploy_role_manager, daddy, brain, healthcheck_accountant, debt_allocator_factory ): role_manager = deploy_role_manager() role_manager.setPositionHolder( role_manager.ACCOUNTANT(), healthcheck_accountant, sender=daddy ) - role_manager.setPositionHolder(role_manager.REGISTRY(), registry, sender=daddy) role_manager.setPositionHolder( role_manager.ALLOCATOR_FACTORY(), debt_allocator_factory, sender=daddy ) return role_manager + + +@pytest.fixture(scope="session") +def keeper(daddy): + yield daddy.deploy(project.Keeper) diff --git a/tests/manager/test_role_manager.py b/tests/manager/test_role_manager.py index b5ea725..1117935 100644 --- a/tests/manager/test_role_manager.py +++ b/tests/manager/test_role_manager.py @@ -9,6 +9,7 @@ ROLES.REPORTING_MANAGER | ROLES.DEBT_MANAGER | ROLES.QUEUE_MANAGER + | ROLES.DEPOSIT_LIMIT_MANAGER | ROLES.DEBT_PURCHASER ) security_roles = ROLES.MAX_DEBT_MANAGER @@ -32,13 +33,6 @@ def test_role_manager_setup( asset, ): assert role_manager.governance() == daddy - assert role_manager.ratingToString(0) == "" - assert role_manager.ratingToString(1) == "A" - assert role_manager.ratingToString(2) == "B" - assert role_manager.ratingToString(3) == "C" - assert role_manager.ratingToString(4) == "D" - assert role_manager.ratingToString(5) == "F" - assert role_manager.ratingToString(6) == "" assert role_manager.chad() == daddy assert role_manager.getAllVaults() == [] assert role_manager.getVault(asset, vault.apiVersion(), 1) == ZERO_ADDRESS @@ -52,7 +46,7 @@ def test_role_manager_setup( assert role_manager.getAllocatorFactory() == debt_allocator_factory assert role_manager.isVaultsRoleManager(vault) == False assert role_manager.getDebtAllocator(vault) == ZERO_ADDRESS - assert role_manager.getRating(vault) == 0 + assert role_manager.getCategory(vault) == 0 # Check registry too. assert registry.releaseRegistry() == release_registry @@ -129,7 +123,6 @@ def test__positions( "Daddy": (daddy, daddy_roles), "Brain": (brain, brain_roles), "Security": (security, security_roles), - "Keeper": (keeper, keeper_roles), "Strategy Manager": (strategy_manager, strategy_manager_roles), "Registry": (registry, 0), "Accountant": (healthcheck_accountant, 0), @@ -163,7 +156,7 @@ def test__positions( assert role_manager.getPositionRoles(id) == new_role assert role_manager.getPosition(id) == (user, new_role) - # Cannot update the debt allocator roles. + # Cannot update the debt allocator or keeper roles. id = to_bytes32("Debt Allocator") with ape.reverts("cannot update"): role_manager.setPositionRoles(id, 1, sender=daddy) @@ -176,6 +169,18 @@ def test__positions( assert event.position == id assert event.newAddress == user + id = to_bytes32("Keeper") + with ape.reverts("cannot update"): + role_manager.setPositionRoles(id, 1, sender=daddy) + + # But can update the holder since it is not used + tx = role_manager.setPositionHolder(id, user, sender=daddy) + + event = list(tx.decode_logs(role_manager.UpdatePositionHolder))[0] + + assert event.position == id + assert event.newAddress == user + # All positions should be changed now. assert role_manager.getDaddy() == user assert role_manager.getBrain() == user @@ -197,13 +202,13 @@ def test__positions( assert role_manager.getDaddyRoles() == new_role assert role_manager.getBrainRoles() == new_role assert role_manager.getSecurityRoles() == new_role - assert role_manager.getKeeperRoles() == new_role + assert role_manager.getKeeperRoles() == keeper_roles assert role_manager.getDebtAllocatorRoles() == debt_allocator_roles assert role_manager.getStrategyManagerRoles() == new_role assert role_manager.getPositionRoles(role_manager.DADDY()) == new_role assert role_manager.getPositionRoles(role_manager.BRAIN()) == new_role assert role_manager.getPositionRoles(role_manager.SECURITY()) == new_role - assert role_manager.getPositionRoles(role_manager.KEEPER()) == new_role + assert role_manager.getPositionRoles(role_manager.KEEPER()) == keeper_roles assert ( role_manager.getPositionRoles(role_manager.DEBT_ALLOCATOR()) == debt_allocator_roles @@ -279,30 +284,29 @@ def test_deploy_new_vault( vault_factory, debt_allocator_factory, ): - rating = int(1) + category = int(1) deposit_limit = int(100e18) profit_unlock = int(695) release_registry.newRelease(vault_factory.address, sender=daddy) assert role_manager.getAllVaults() == [] assert ( - role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + role_manager.getVault(asset, vault_factory.apiVersion(), category) + == ZERO_ADDRESS ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 - with ape.reverts("rating out of range"): - role_manager.newVault(asset, 0, deposit_limit, profit_unlock, sender=daddy) - - with ape.reverts("rating out of range"): - role_manager.newVault(asset, int(6), deposit_limit, profit_unlock, sender=daddy) - with ape.reverts("!allowed"): - role_manager.newVault(asset, rating, deposit_limit, profit_unlock, sender=user) + role_manager.newVault( + asset, category, deposit_limit, profit_unlock, sender=user + ) # Now the registry will revert with ape.reverts("!endorser"): - role_manager.newVault(asset, rating, deposit_limit, profit_unlock, sender=daddy) + role_manager.newVault( + asset, category, deposit_limit, profit_unlock, sender=daddy + ) # ADd the role manager as an endorser registry.setEndorser(role_manager, True, sender=daddy) @@ -310,12 +314,14 @@ def test_deploy_new_vault( # Haven't set the role manager as the vault manager. with ape.reverts("!vault manager"): - role_manager.newVault(asset, rating, deposit_limit, profit_unlock, sender=daddy) + role_manager.newVault( + asset, category, deposit_limit, profit_unlock, sender=daddy + ) healthcheck_accountant.setVaultManager(role_manager, sender=daddy) tx = role_manager.newVault( - asset, rating, deposit_limit, profit_unlock, sender=daddy + asset, category, deposit_limit, profit_unlock, sender=daddy ) event = list(tx.decode_logs(registry.NewEndorsedVault))[0] @@ -325,7 +331,7 @@ def test_deploy_new_vault( event = list(tx.decode_logs(role_manager.AddedNewVault))[0] assert event.vault == vault - assert event.rating == rating + assert event.category == category allocator = event.debtAllocator event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] @@ -334,20 +340,23 @@ def test_deploy_new_vault( debt_allocator = project.DebtAllocator.at(event.allocator) assert allocator == debt_allocator - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == asset - assert vault_rating == rating + assert vault_category == category assert vault_debt_allocator == debt_allocator assert index == 0 assert role_manager.getAllVaults() == [vault] - assert role_manager.getVault(asset, vault_factory.apiVersion(), rating) == vault + assert role_manager.getVault(asset, vault_factory.apiVersion(), category) == vault assert role_manager.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator - assert role_manager.getRating(vault) == rating + assert role_manager.getCategory(vault) == category assert registry.numAssets() == 1 assert registry.numEndorsedVaults(asset) == 1 assert registry.getAllEndorsedVaults() == [[vault]] @@ -370,8 +379,8 @@ def test_deploy_new_vault( assert vault.maxDeposit(user) == deposit_limit symbol = asset.symbol() - assert vault.symbol() == f"yv{symbol}-A" - assert vault.name() == f"{symbol}-A yVault" + assert vault.symbol() == f"yv{symbol}-1" + assert vault.name() == f"{symbol}-1 yVault" # Check debt allocator assert debt_allocator.vault() == vault @@ -387,14 +396,15 @@ def test_deploy_new_vault__duplicate_reverts( vault_factory, debt_allocator_factory, ): - rating = int(1) + category = int(1) deposit_limit = int(100e18) profit_unlock = int(695) release_registry.newRelease(vault_factory.address, sender=daddy) assert role_manager.getAllVaults() == [] assert ( - role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + role_manager.getVault(asset, vault_factory.apiVersion(), category) + == ZERO_ADDRESS ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 @@ -406,7 +416,7 @@ def test_deploy_new_vault__duplicate_reverts( healthcheck_accountant.setVaultManager(role_manager, sender=daddy) tx = role_manager.newVault( - asset, rating, deposit_limit, profit_unlock, sender=daddy + asset, category, deposit_limit, profit_unlock, sender=daddy ) event = list(tx.decode_logs(registry.NewEndorsedVault))[0] @@ -418,31 +428,36 @@ def test_deploy_new_vault__duplicate_reverts( assert event.vault == vault debt_allocator = project.DebtAllocator.at(event.allocator) - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == asset - assert vault_rating == rating + assert vault_category == category assert vault_debt_allocator == debt_allocator assert index == 0 assert role_manager.getAllVaults() == [vault] - assert role_manager.getVault(asset, vault_factory.apiVersion(), rating) == vault + assert role_manager.getVault(asset, vault_factory.apiVersion(), category) == vault assert role_manager.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator - assert role_manager.getRating(vault) == rating + assert role_manager.getCategory(vault) == category assert registry.numAssets() == 1 assert registry.numEndorsedVaults(asset) == 1 assert registry.getAllEndorsedVaults() == [[vault]] # Try and deploy a new one of the same settings. with ape.reverts(to_bytes32(f"Already Deployed {vault.address}")): - role_manager.newVault(asset, rating, deposit_limit, profit_unlock, sender=daddy) + role_manager.newVault( + asset, category, deposit_limit, profit_unlock, sender=daddy + ) - # can with a different rating. + # can with a different category. role_manager.newVault( - asset, rating + 1, deposit_limit, profit_unlock, max_fee="1", sender=daddy + asset, category + 1, deposit_limit, profit_unlock, max_fee="1", sender=daddy ) @@ -461,28 +476,23 @@ def test_deploy_new_vault__default_values( vault_factory, debt_allocator_factory, ): - rating = int(2) + category = int(2) release_registry.newRelease(vault_factory.address, sender=daddy) assert role_manager.getAllVaults() == [] assert ( - role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + role_manager.getVault(asset, vault_factory.apiVersion(), category) + == ZERO_ADDRESS ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 - with ape.reverts("rating out of range"): - role_manager.newVault(asset, 0, sender=daddy) - - with ape.reverts("rating out of range"): - role_manager.newVault(asset, int(6), sender=daddy) - with ape.reverts("!allowed"): - role_manager.newVault(asset, rating, sender=user) + role_manager.newVault(asset, category, sender=user) # Now the registry will revert with ape.reverts("!endorser"): - role_manager.newVault(asset, rating, sender=daddy) + role_manager.newVault(asset, category, sender=daddy) # ADd the role manager as an endorser registry.setEndorser(role_manager, True, sender=daddy) @@ -490,12 +500,12 @@ def test_deploy_new_vault__default_values( # Haven't set the role manager as the vault manager. with ape.reverts("!vault manager"): - role_manager.newVault(asset, rating, sender=daddy) + role_manager.newVault(asset, category, sender=daddy) healthcheck_accountant.setVaultManager(role_manager, sender=daddy) # User can now deploy - tx = role_manager.newVault(asset, rating, sender=daddy) + tx = role_manager.newVault(asset, category, sender=daddy) event = list(tx.decode_logs(registry.NewEndorsedVault))[0] @@ -504,27 +514,30 @@ def test_deploy_new_vault__default_values( event = list(tx.decode_logs(role_manager.AddedNewVault))[0] assert event.vault == vault - assert event.rating == rating + assert event.category == category event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] assert event.vault == vault debt_allocator = project.DebtAllocator.at(event.allocator) - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == asset - assert vault_rating == rating + assert vault_category == category assert vault_debt_allocator == debt_allocator assert index == 0 assert role_manager.getAllVaults() == [vault] - assert role_manager.getVault(asset, vault_factory.apiVersion(), rating) == vault + assert role_manager.getVault(asset, vault_factory.apiVersion(), category) == vault assert role_manager.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator - assert role_manager.getRating(vault) == rating + assert role_manager.getCategory(vault) == category assert registry.numAssets() == 1 assert registry.numEndorsedVaults(asset) == 1 assert registry.getAllEndorsedVaults() == [[vault]] @@ -547,8 +560,8 @@ def test_deploy_new_vault__default_values( assert vault.maxDeposit(user) == 0 symbol = asset.symbol() - assert vault.symbol() == f"yv{symbol}-B" - assert vault.name() == f"{symbol}-B yVault" + assert vault.symbol() == f"yv{symbol}-2" + assert vault.name() == f"{symbol}-2 yVault" # Check debt allocator assert debt_allocator.vault() == vault @@ -590,11 +603,12 @@ def test_add_new_vault__endorsed( name = " ksjdfl" symbol = "sdfa" - rating = int(1) + category = int(1) assert role_manager.getAllVaults() == [] assert ( - role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + role_manager.getVault(asset, vault_factory.apiVersion(), category) + == ZERO_ADDRESS ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 @@ -609,46 +623,43 @@ def test_add_new_vault__endorsed( assert registry.getAllEndorsedVaults() == [[vault]] with ape.reverts("!allowed"): - role_manager.addNewVault(vault, rating, sender=user) - - with ape.reverts("rating out of range"): - role_manager.addNewVault(vault, 0, sender=daddy) - - with ape.reverts("rating out of range"): - role_manager.addNewVault(vault, 6, sender=daddy) + role_manager.addNewVault(vault, category, sender=user) # Is not pending role manager with ape.reverts(): - role_manager.addNewVault(vault, rating, sender=user) + role_manager.addNewVault(vault, category, sender=user) vault.transfer_role_manager(role_manager, sender=daddy) - tx = role_manager.addNewVault(vault, rating, sender=daddy) + tx = role_manager.addNewVault(vault, category, sender=daddy) event = list(tx.decode_logs(role_manager.AddedNewVault))[0] assert event.vault == vault - assert event.rating == rating + assert event.category == category event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] assert event.vault == vault debt_allocator = project.DebtAllocator.at(event.allocator) - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == asset - assert vault_rating == rating + assert vault_category == category assert vault_debt_allocator == debt_allocator assert index == 0 assert role_manager.getAllVaults() == [vault] - assert role_manager.getVault(asset, vault_factory.apiVersion(), rating) == vault + assert role_manager.getVault(asset, vault_factory.apiVersion(), category) == vault assert role_manager.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator - assert role_manager.getRating(vault) == rating + assert role_manager.getCategory(vault) == category # Check roles assert vault.roles(role_manager) == 0 @@ -700,7 +711,7 @@ def test_add_new_vault__not_endorsed( name = " ksjdfl" symbol = "sdfa" - rating = int(1) + category = int(1) tx = vault_factory.deploy_new_vault(asset, name, symbol, daddy, 100, sender=daddy) @@ -711,52 +722,50 @@ def test_add_new_vault__not_endorsed( assert role_manager.getAllVaults() == [] assert ( - role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + role_manager.getVault(asset, vault_factory.apiVersion(), category) + == ZERO_ADDRESS ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 with ape.reverts("!allowed"): - role_manager.addNewVault(vault, rating, sender=user) - - with ape.reverts("rating out of range"): - role_manager.addNewVault(vault, 0, sender=daddy) - - with ape.reverts("rating out of range"): - role_manager.addNewVault(vault, 6, sender=daddy) + role_manager.addNewVault(vault, category, sender=user) # Is not pending role manager with ape.reverts(): - role_manager.addNewVault(vault, rating, sender=user) + role_manager.addNewVault(vault, category, sender=user) vault.transfer_role_manager(role_manager, sender=daddy) - tx = role_manager.addNewVault(vault, rating, sender=daddy) + tx = role_manager.addNewVault(vault, category, sender=daddy) event = list(tx.decode_logs(role_manager.AddedNewVault))[0] assert event.vault == vault - assert event.rating == rating + assert event.category == category event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] assert event.vault == vault debt_allocator = project.DebtAllocator.at(event.allocator) - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == asset - assert vault_rating == rating + assert vault_category == category assert vault_debt_allocator == debt_allocator assert index == 0 assert role_manager.getAllVaults() == [vault] - assert role_manager.getVault(asset, vault_factory.apiVersion(), rating) == vault + assert role_manager.getVault(asset, vault_factory.apiVersion(), category) == vault assert role_manager.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator - assert role_manager.getRating(vault) == rating + assert role_manager.getCategory(vault) == category assert registry.numAssets() == 1 assert registry.numEndorsedVaults(asset) == 1 assert registry.getAllEndorsedVaults() == [[vault]] @@ -811,7 +820,7 @@ def test_add_new_vault__with_debt_allocator( name = " ksjdfl" symbol = "sdfa" - rating = int(1) + category = int(1) tx = vault_factory.deploy_new_vault(asset, name, symbol, daddy, 100, sender=daddy) @@ -822,7 +831,8 @@ def test_add_new_vault__with_debt_allocator( assert role_manager.getAllVaults() == [] assert ( - role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + role_manager.getVault(asset, vault_factory.apiVersion(), category) + == ZERO_ADDRESS ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 @@ -833,42 +843,39 @@ def test_add_new_vault__with_debt_allocator( debt_allocator = project.DebtAllocator.at(event.allocator) with ape.reverts("!allowed"): - role_manager.addNewVault(vault, rating, debt_allocator, sender=user) - - with ape.reverts("rating out of range"): - role_manager.addNewVault(vault, 0, debt_allocator, sender=daddy) - - with ape.reverts("rating out of range"): - role_manager.addNewVault(vault, 6, debt_allocator, sender=daddy) + role_manager.addNewVault(vault, category, debt_allocator, sender=user) # Is not pending role manager with ape.reverts(): - role_manager.addNewVault(vault, rating, debt_allocator, sender=user) + role_manager.addNewVault(vault, category, debt_allocator, sender=user) vault.transfer_role_manager(role_manager, sender=daddy) - tx = role_manager.addNewVault(vault, rating, debt_allocator, sender=daddy) + tx = role_manager.addNewVault(vault, category, debt_allocator, sender=daddy) event = list(tx.decode_logs(role_manager.AddedNewVault))[0] assert event.vault == vault - assert event.rating == rating + assert event.category == category assert event.debtAllocator == debt_allocator - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == asset - assert vault_rating == rating + assert vault_category == category assert vault_debt_allocator == debt_allocator assert index == 0 assert role_manager.getAllVaults() == [vault] - assert role_manager.getVault(asset, vault_factory.apiVersion(), rating) == vault + assert role_manager.getVault(asset, vault_factory.apiVersion(), category) == vault assert role_manager.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator - assert role_manager.getRating(vault) == rating + assert role_manager.getCategory(vault) == category assert registry.numAssets() == 1 assert registry.numEndorsedVaults(asset) == 1 assert registry.getAllEndorsedVaults() == [[vault]] @@ -923,7 +930,7 @@ def test_add_new_vault__with_accountant( name = " ksjdfl" symbol = "sdfa" - rating = int(1) + category = int(1) tx = vault_factory.deploy_new_vault(asset, name, symbol, daddy, 100, sender=daddy) event = list(tx.decode_logs(vault_factory.NewVault))[0] @@ -933,7 +940,8 @@ def test_add_new_vault__with_accountant( assert role_manager.getAllVaults() == [] assert ( - role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + role_manager.getVault(asset, vault_factory.apiVersion(), category) + == ZERO_ADDRESS ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 @@ -948,42 +956,39 @@ def test_add_new_vault__with_accountant( vault.remove_role(daddy, ROLES.ACCOUNTANT_MANAGER, sender=daddy) with ape.reverts("!allowed"): - role_manager.addNewVault(vault, rating, debt_allocator, sender=user) - - with ape.reverts("rating out of range"): - role_manager.addNewVault(vault, 0, debt_allocator, sender=daddy) - - with ape.reverts("rating out of range"): - role_manager.addNewVault(vault, 6, debt_allocator, sender=daddy) + role_manager.addNewVault(vault, category, debt_allocator, sender=user) # Is not pending role manager with ape.reverts(): - role_manager.addNewVault(vault, rating, debt_allocator, sender=user) + role_manager.addNewVault(vault, category, debt_allocator, sender=user) vault.transfer_role_manager(role_manager, sender=daddy) - tx = role_manager.addNewVault(vault, rating, debt_allocator, sender=daddy) + tx = role_manager.addNewVault(vault, category, debt_allocator, sender=daddy) event = list(tx.decode_logs(role_manager.AddedNewVault))[0] assert event.vault == vault - assert event.rating == rating + assert event.category == category assert event.debtAllocator == debt_allocator - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == asset - assert vault_rating == rating + assert vault_category == category assert vault_debt_allocator == debt_allocator assert index == 0 assert role_manager.getAllVaults() == [vault] - assert role_manager.getVault(asset, vault_factory.apiVersion(), rating) == vault + assert role_manager.getVault(asset, vault_factory.apiVersion(), category) == vault assert role_manager.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator - assert role_manager.getRating(vault) == rating + assert role_manager.getCategory(vault) == category assert registry.numAssets() == 1 assert registry.numEndorsedVaults(asset) == 1 assert registry.getAllEndorsedVaults() == [[vault]] @@ -1031,19 +1036,20 @@ def test_add_new_vault__duplicate_reverts( daddy=daddy, ) - rating = int(1) + category = int(1) deposit_limit = int(100e18) profit_unlock = int(695) assert role_manager.getAllVaults() == [] assert ( - role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + role_manager.getVault(asset, vault_factory.apiVersion(), category) + == ZERO_ADDRESS ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 tx = role_manager.newVault( - asset, rating, deposit_limit, profit_unlock, sender=daddy + asset, category, deposit_limit, profit_unlock, sender=daddy ) event = list(tx.decode_logs(registry.NewEndorsedVault))[0] @@ -1055,20 +1061,23 @@ def test_add_new_vault__duplicate_reverts( assert event.vault == vault debt_allocator = project.DebtAllocator.at(event.allocator) - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == asset - assert vault_rating == rating + assert vault_category == category assert vault_debt_allocator == debt_allocator assert index == 0 assert role_manager.getAllVaults() == [vault] - assert role_manager.getVault(asset, vault_factory.apiVersion(), rating) == vault + assert role_manager.getVault(asset, vault_factory.apiVersion(), category) == vault assert role_manager.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator - assert role_manager.getRating(vault) == rating + assert role_manager.getCategory(vault) == category name = " ksjdfl" symbol = "sdfa" @@ -1082,10 +1091,10 @@ def test_add_new_vault__duplicate_reverts( ) with ape.reverts(to_bytes32(f"Already Deployed {vault.address}")): - role_manager.addNewVault(new_vault, rating, debt_allocator, sender=daddy) + role_manager.addNewVault(new_vault, category, debt_allocator, sender=daddy) - # Can add it with a different rating. - role_manager.addNewVault(vault, rating + 1, debt_allocator, sender=daddy) + # Can add it with a different category. + role_manager.addNewVault(vault, category + 1, debt_allocator, sender=daddy) def test_new_debt_allocator__deploys_one( @@ -1112,14 +1121,14 @@ def test_new_debt_allocator__deploys_one( daddy=daddy, ) - rating = int(2) + category = int(2) assert role_manager.getAllVaults() == [] assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 # Deploy a vault - tx = role_manager.newVault(asset, rating, sender=daddy) + tx = role_manager.newVault(asset, category, sender=daddy) event = list(tx.decode_logs(registry.NewEndorsedVault))[0] vault = project.dependencies["yearn-vaults"]["v3.0.1"].VaultV3.at(event.vault) @@ -1127,20 +1136,23 @@ def test_new_debt_allocator__deploys_one( event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] debt_allocator = project.DebtAllocator.at(event.allocator) - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == asset - assert vault_rating == rating + assert vault_category == category assert vault_debt_allocator == debt_allocator assert index == 0 assert role_manager.getAllVaults() == [vault] - assert role_manager.getVault(asset, vault_factory.apiVersion(), rating) == vault + assert role_manager.getVault(asset, vault_factory.apiVersion(), category) == vault assert role_manager.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator - assert role_manager.getRating(vault) == rating + assert role_manager.getCategory(vault) == category assert registry.numAssets() == 1 assert registry.numEndorsedVaults(asset) == 1 assert registry.getAllEndorsedVaults() == [[vault]] @@ -1178,19 +1190,22 @@ def test_new_debt_allocator__deploys_one( assert new_debt_allocator != debt_allocator assert new_debt_allocator.vault() == vault - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == asset - assert vault_rating == rating + assert vault_category == category assert vault_debt_allocator == new_debt_allocator assert index == 0 assert role_manager.getAllVaults() == [vault] assert role_manager.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == new_debt_allocator - assert role_manager.getRating(vault) == rating + assert role_manager.getCategory(vault) == category assert registry.numAssets() == 1 assert registry.numEndorsedVaults(asset) == 1 assert registry.getAllEndorsedVaults() == [[vault]] @@ -1231,14 +1246,14 @@ def test_new_debt_allocator__already_deployed( daddy=daddy, ) - rating = int(2) + category = int(2) assert role_manager.getAllVaults() == [] assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 # Deploy a vault - tx = role_manager.newVault(asset, rating, sender=daddy) + tx = role_manager.newVault(asset, category, sender=daddy) event = list(tx.decode_logs(registry.NewEndorsedVault))[0] vault = project.dependencies["yearn-vaults"]["v3.0.1"].VaultV3.at(event.vault) @@ -1246,20 +1261,23 @@ def test_new_debt_allocator__already_deployed( event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] debt_allocator = project.DebtAllocator.at(event.allocator) - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == asset - assert vault_rating == rating + assert vault_category == category assert vault_debt_allocator == debt_allocator assert index == 0 assert role_manager.getAllVaults() == [vault] - assert role_manager.getVault(asset, vault_factory.apiVersion(), rating) == vault + assert role_manager.getVault(asset, vault_factory.apiVersion(), category) == vault assert role_manager.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator - assert role_manager.getRating(vault) == rating + assert role_manager.getCategory(vault) == category assert registry.numAssets() == 1 assert registry.numEndorsedVaults(asset) == 1 assert registry.getAllEndorsedVaults() == [[vault]] @@ -1297,19 +1315,22 @@ def test_new_debt_allocator__already_deployed( assert new_debt_allocator != debt_allocator assert new_debt_allocator.vault() == vault - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == asset - assert vault_rating == rating + assert vault_category == category assert vault_debt_allocator == new_debt_allocator assert index == 0 assert role_manager.getAllVaults() == [vault] assert role_manager.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == new_debt_allocator - assert role_manager.getRating(vault) == rating + assert role_manager.getCategory(vault) == category assert registry.numAssets() == 1 assert registry.numEndorsedVaults(asset) == 1 assert registry.getAllEndorsedVaults() == [[vault]] @@ -1326,6 +1347,119 @@ def test_new_debt_allocator__already_deployed( assert vault.profitMaxUnlockTime() == role_manager.defaultProfitMaxUnlock() +def test_new_keeper( + role_manager, + daddy, + brain, + security, + keeper, + strategy_manager, + asset, + user, + healthcheck_accountant, + registry, + release_registry, + vault_factory, + debt_allocator_factory, +): + setup_role_manager( + role_manager=role_manager, + release_registry=release_registry, + registry=registry, + vault_factory=vault_factory, + accountant=healthcheck_accountant, + daddy=daddy, + ) + + category = int(2) + + assert role_manager.getAllVaults() == [] + assert registry.numAssets() == 0 + assert registry.numEndorsedVaults(asset) == 0 + + # Deploy a vault + tx = role_manager.newVault(asset, category, sender=daddy) + + event = list(tx.decode_logs(registry.NewEndorsedVault))[0] + vault = project.dependencies["yearn-vaults"]["v3.0.1"].VaultV3.at(event.vault) + + event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] + debt_allocator = project.DebtAllocator.at(event.allocator) + + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) + + assert vault_asset == asset + assert vault_category == category + assert vault_debt_allocator == debt_allocator + assert index == 0 + assert role_manager.getAllVaults() == [vault] + assert role_manager.getVault(asset, vault_factory.apiVersion(), category) == vault + assert role_manager.vaults(index) == vault + assert role_manager.isVaultsRoleManager(vault) == True + assert role_manager.getDebtAllocator(vault) == debt_allocator + assert role_manager.getCategory(vault) == category + assert registry.numAssets() == 1 + assert registry.numEndorsedVaults(asset) == 1 + assert registry.getAllEndorsedVaults() == [[vault]] + + # Check roles + assert vault.roles(role_manager) == 0 + assert vault.roles(daddy) == daddy_roles + assert vault.roles(brain) == brain_roles + assert vault.roles(security) == security_roles + assert vault.roles(debt_allocator) == debt_allocator_roles + assert vault.roles(strategy_manager) == strategy_manager_roles + assert vault.profitMaxUnlockTime() == role_manager.defaultProfitMaxUnlock() + + new_keeper = user + + assert vault.roles(keeper) == keeper_roles + assert vault.roles(new_keeper) == 0 + + # Update to a new debt allocator + with ape.reverts("!allowed"): + role_manager.updateKeeper(vault, new_keeper, sender=user) + + with ape.reverts("vault not added"): + role_manager.updateKeeper(user, new_keeper, sender=brain) + + tx = role_manager.updateKeeper(vault, new_keeper, sender=brain) + + assert vault.roles(new_keeper) == keeper_roles + assert vault.roles(keeper) == 0 + + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) + + assert vault_asset == asset + assert vault_category == category + assert index == 0 + assert role_manager.getAllVaults() == [vault] + assert role_manager.vaults(index) == vault + assert role_manager.isVaultsRoleManager(vault) == True + assert role_manager.getCategory(vault) == category + assert registry.numAssets() == 1 + assert registry.numEndorsedVaults(asset) == 1 + assert registry.getAllEndorsedVaults() == [[vault]] + + # Check roles + assert vault.roles(role_manager) == 0 + assert vault.roles(daddy) == daddy_roles + assert vault.roles(brain) == brain_roles + assert vault.roles(security) == security_roles + assert vault.roles(strategy_manager) == strategy_manager_roles + assert vault.profitMaxUnlockTime() == role_manager.defaultProfitMaxUnlock() + + def test_remove_vault( role_manager, daddy, @@ -1350,17 +1484,18 @@ def test_remove_vault( daddy=daddy, ) - rating = int(2) + category = int(2) assert role_manager.getAllVaults() == [] assert ( - role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + role_manager.getVault(asset, vault_factory.apiVersion(), category) + == ZERO_ADDRESS ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 # Deploy a vault - tx = role_manager.newVault(asset, rating, sender=daddy) + tx = role_manager.newVault(asset, category, sender=daddy) event = list(tx.decode_logs(registry.NewEndorsedVault))[0] vault = project.dependencies["yearn-vaults"]["v3.0.1"].VaultV3.at(event.vault) @@ -1368,20 +1503,23 @@ def test_remove_vault( event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] debt_allocator = project.DebtAllocator.at(event.allocator) - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == asset - assert vault_rating == rating + assert vault_category == category assert vault_debt_allocator == debt_allocator assert index == 0 assert role_manager.getAllVaults() == [vault] - assert role_manager.getVault(asset, vault_factory.apiVersion(), rating) == vault + assert role_manager.getVault(asset, vault_factory.apiVersion(), category) == vault assert role_manager.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator - assert role_manager.getRating(vault) == rating + assert role_manager.getCategory(vault) == category assert registry.numAssets() == 1 assert registry.numEndorsedVaults(asset) == 1 assert registry.getAllEndorsedVaults() == [[vault]] @@ -1411,21 +1549,25 @@ def test_remove_vault( event = list(tx.decode_logs(role_manager.RemovedVault))[0] assert event.vault == vault - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == ZERO_ADDRESS - assert vault_rating == 0 + assert vault_category == 0 assert vault_debt_allocator == ZERO_ADDRESS assert index == 0 assert role_manager.getAllVaults() == [] assert ( - role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + role_manager.getVault(asset, vault_factory.apiVersion(), category) + == ZERO_ADDRESS ) assert role_manager.isVaultsRoleManager(vault) == False assert role_manager.getDebtAllocator(vault) == ZERO_ADDRESS - assert role_manager.getRating(vault) == 0 + assert role_manager.getCategory(vault) == 0 # Still endorsed through the registry assert registry.numAssets() == 1 assert registry.numEndorsedVaults(asset) == 1 @@ -1473,17 +1615,18 @@ def test_remove_role( daddy=daddy, ) - rating = int(2) + category = int(2) assert role_manager.getAllVaults() == [] assert ( - role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + role_manager.getVault(asset, vault_factory.apiVersion(), category) + == ZERO_ADDRESS ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 # Deploy a vault - tx = role_manager.newVault(asset, rating, sender=daddy) + tx = role_manager.newVault(asset, category, sender=daddy) event = list(tx.decode_logs(registry.NewEndorsedVault))[0] vault = project.dependencies["yearn-vaults"]["v3.0.1"].VaultV3.at(event.vault) @@ -1491,16 +1634,19 @@ def test_remove_role( event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] debt_allocator = project.DebtAllocator.at(event.allocator) - (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( - vault - ) + ( + vault_asset, + vault_category, + vault_debt_allocator, + index, + ) = role_manager.vaultConfig(vault) assert vault_asset == asset - assert vault_rating == rating + assert vault_category == category assert vault_debt_allocator == debt_allocator assert index == 0 assert role_manager.getAllVaults() == [vault] - assert role_manager.getVault(asset, vault_factory.apiVersion(), rating) == vault + assert role_manager.getVault(asset, vault_factory.apiVersion(), category) == vault assert role_manager.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True diff --git a/tests/test_keeper.py b/tests/test_keeper.py new file mode 100644 index 0000000..4d1b057 --- /dev/null +++ b/tests/test_keeper.py @@ -0,0 +1,51 @@ +import ape +from ape import chain +from utils.constants import ZERO_ADDRESS, ROLES, MAX_INT + + +def test_keeper(daddy, keeper, vault, mock_tokenized, amount, user, asset, management): + strategy = mock_tokenized + # Revert on vault does not cause revert + with ape.reverts("not allowed"): + vault.process_report(strategy, sender=keeper) + + tx = keeper.process_report(vault, strategy, sender=user) + + profit, loss = tx.return_value + assert profit == 0 + assert loss == 0 + + vault.add_strategy(strategy, sender=daddy) + vault.set_role(keeper, ROLES.REPORTING_MANAGER, sender=daddy) + + amount = amount // 2 + + asset.approve(strategy, amount, sender=user) + strategy.deposit(amount, vault, sender=user) + + tx = keeper.process_report(vault, strategy, sender=user) + + profit, loss = tx.return_value + assert profit == amount + assert loss == 0 + + asset.transfer(strategy, amount, sender=user) + + strategy.setKeeper(user, sender=management) + + with ape.reverts("!keeper"): + strategy.report(sender=keeper) + + tx = keeper.report(strategy, sender=user) + + profit, loss = tx.return_value + assert profit == 0 + assert loss == 0 + + strategy.setKeeper(keeper, sender=management) + + tx = keeper.report(strategy, sender=user) + + profit, loss = tx.return_value + assert profit == amount + assert loss == 0