diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2cbf43a..fc429b1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -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 diff --git a/ape-config.yaml b/ape-config.yaml index 858dc57..59a7dcc 100644 --- a/ape-config.yaml +++ b/ape-config.yaml @@ -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 diff --git a/contracts/Managers/RoleManager.sol b/contracts/Managers/RoleManager.sol index 843e5cf..4ec4529 100644 --- a/contracts/Managers/RoleManager.sol +++ b/contracts/Managers/RoleManager.sol @@ -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 { @@ -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); @@ -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, @@ -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); @@ -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); @@ -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); @@ -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. @@ -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); } @@ -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); @@ -512,6 +544,9 @@ contract RoleManager is Governance2Step { // Update the vaults config. vaultConfig[_vault].debtAllocator = _debtAllocator; + + // Emit event. + emit UpdateDebtAllocator(_vault, _debtAllocator); } /** @@ -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]; @@ -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 diff --git a/contracts/Mocks/MockOracle.sol b/contracts/Mocks/MockOracle.sol new file mode 100644 index 0000000..2886262 --- /dev/null +++ b/contracts/Mocks/MockOracle.sol @@ -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 {} diff --git a/contracts/Mocks/MockTokenizedStrategy.sol b/contracts/Mocks/MockTokenizedStrategy.sol new file mode 100644 index 0000000..8709463 --- /dev/null +++ b/contracts/Mocks/MockTokenizedStrategy.sol @@ -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; + } +} diff --git a/contracts/debtAllocators/GenericDebtAllocator.sol b/contracts/debtAllocators/DebtAllocator.sol similarity index 68% rename from contracts/debtAllocators/GenericDebtAllocator.sol rename to contracts/debtAllocators/DebtAllocator.sol index 535f543..aad03d5 100644 --- a/contracts/debtAllocators/GenericDebtAllocator.sol +++ b/contracts/debtAllocators/DebtAllocator.sol @@ -3,14 +3,14 @@ pragma solidity 0.8.18; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {Governance} from "@periphery/utils/Governance.sol"; import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; +import {DebtAllocatorFactory} from "./DebtAllocatorFactory.sol"; /** - * @title YearnV3 Generic Debt Allocator + * @title YearnV3 Debt Allocator * @author yearn.finance * @notice - * This Generic Debt Allocator is meant to be used alongside + * This Debt Allocator is meant to be used alongside * a Yearn V3 vault to provide the needed triggers for a keeper * to perform automated debt updates for the vaults strategies. * @@ -28,9 +28,9 @@ import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; * And will pull funds from the strategy when it has `minimumChange` * more than its `maxRatio`. */ -contract GenericDebtAllocator is Governance { +contract DebtAllocator { /// @notice An event emitted when a strategies debt ratios are Updated. - event UpdateStrategyDebtRatios( + event UpdateStrategyDebtRatio( address indexed strategy, uint256 newTargetRatio, uint256 newMaxRatio, @@ -44,33 +44,65 @@ contract GenericDebtAllocator is Governance { event UpdateMinimumChange(uint256 newMinimumChange); /// @notice An event emitted when a keeper is added or removed. - event UpdateKeeper(address indexed keeper, bool allowed); + event UpdateManager(address indexed manager, bool allowed); /// @notice An event emitted when the max debt update loss is updated. event UpdateMaxDebtUpdateLoss(uint256 newMaxDebtUpdateLoss); - /// @notice An event emitted when the max base fee is updated. - event UpdateMaxAcceptableBaseFee(uint256 newMaxAcceptableBaseFee); - /// @notice Struct for each strategies info. struct Config { // The ideal percent in Basis Points the strategy should have. - uint256 targetRatio; + uint16 targetRatio; // The max percent of assets the strategy should hold. - uint256 maxRatio; + uint16 maxRatio; // Timestamp of the last time debt was updated. // The debt updates must be done through this allocator // for this to be used. - uint256 lastUpdate; + uint128 lastUpdate; + // We have an extra 96 bits in the slot. + // So we declare the variable in the struct so it can be + // used if this contract is inherited. + uint96 open; + } + + /// @notice Make sure the caller is governance. + modifier onlyGovernance() { + _isGovernance(); + _; + } + + /// @notice Make sure the caller is governance or a manager. + modifier onlyManagers() { + _isManager(); + _; } + /// @notice Make sure the caller is a keeper modifier onlyKeepers() { _isKeeper(); _; } + /// @notice Check the Factories governance address. + function _isGovernance() internal view virtual { + require( + msg.sender == DebtAllocatorFactory(factory).governance(), + "!governance" + ); + } + + /// @notice Check is either factories governance or local manager. + function _isManager() internal view virtual { + require( + managers[msg.sender] || + msg.sender == DebtAllocatorFactory(factory).governance(), + "!manager" + ); + } + + /// @notice Check is one of the allowed keepers. function _isKeeper() internal view virtual { - require(keepers[msg.sender], "!keeper"); + require(DebtAllocatorFactory(factory).keepers(msg.sender), "!keeper"); } uint256 internal constant MAX_BPS = 10_000; @@ -78,59 +110,47 @@ contract GenericDebtAllocator is Governance { /// @notice Address of the vault this serves as allocator for. address public vault; - /// @notice Total debt ratio currently allocated in basis points. - // Can't be more than 10_000. - uint256 public debtRatio; + /// @notice Address to get permissioned roles from. + address public factory; + + /// @notice Time to wait between debt updates in seconds. + uint256 public minimumWait; /// @notice The minimum amount denominated in asset that will // need to be moved to trigger a debt update. uint256 public minimumChange; - /// @notice Time to wait between debt updates. - uint256 public minimumWait; + /// @notice Total debt ratio currently allocated in basis points. + // Can't be more than 10_000. + uint256 public totalDebtRatio; /// @notice Max loss to accept on debt updates in basis points. uint256 public maxDebtUpdateLoss; - /// @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; - /// @notice Mapping of addresses that are allowed to update debt. - mapping(address => bool) public keepers; + mapping(address => bool) public managers; /// @notice Mapping of strategy => its config. - mapping(address => Config) public configs; + mapping(address => Config) internal _configs; - constructor( - address _vault, - address _governance, - uint256 _minimumChange - ) Governance(_governance) { - initialize(_vault, _governance, _minimumChange); + constructor(address _vault, uint256 _minimumChange) { + initialize(_vault, _minimumChange); } /** * @notice Initializes the debt allocator. * @dev Should be called atomically after cloning. * @param _vault Address of the vault this allocates debt for. - * @param _governance Address to govern this contract. * @param _minimumChange The minimum in asset that must be moved. */ - function initialize( - address _vault, - address _governance, - uint256 _minimumChange - ) public virtual { + function initialize(address _vault, uint256 _minimumChange) public virtual { require(address(vault) == address(0), "!initialized"); + // Set the factory to retrieve roles from. + factory = msg.sender; + // Set initial variables. vault = _vault; - governance = _governance; - // Default to allow governance to be a keeper. - keepers[_governance] = true; - minimumChange = _minimumChange; - // Default max base fee to uint256 max - maxAcceptableBaseFee = type(uint256).max; + // Default max loss on debt updates to 1 BP. maxDebtUpdateLoss = 1; } @@ -153,9 +173,14 @@ contract GenericDebtAllocator is Governance { function update_debt( address _strategy, uint256 _targetDebt - ) external virtual onlyKeepers { + ) public virtual onlyKeepers { IVault _vault = IVault(vault); + // If going to 0 record full balance first. + if (_targetDebt == 0) { + _vault.process_report(_strategy); + } + // Cache initial values in case of loss. uint256 initialDebt = _vault.strategies(_strategy).current_debt; uint256 initialAssets = _vault.totalAssets(); @@ -173,7 +198,7 @@ contract GenericDebtAllocator is Governance { } // Update the last time the strategies debt was updated. - configs[_strategy].lastUpdate = block.timestamp; + _configs[_strategy].lastUpdate = uint128(block.timestamp); } /** @@ -181,17 +206,15 @@ contract GenericDebtAllocator is Governance { * @dev This should be called by a keeper to decide if a strategies * debt should be updated and if so by how much. * - * NOTE: This cannot be used to withdraw down to 0 debt. - * * @param _strategy Address of the strategy to check. * @return . Bool representing if the debt should be updated. * @return . Calldata if `true` or reason if `false`. */ function shouldUpdateDebt( address _strategy - ) external view virtual returns (bool, bytes memory) { + ) public view virtual returns (bool, bytes memory) { // Check the base fee isn't too high. - if (block.basefee > maxAcceptableBaseFee) { + if (!DebtAllocatorFactory(factory).isCurrentBaseFeeAcceptable()) { return (false, bytes("Base Fee")); } @@ -203,7 +226,7 @@ contract GenericDebtAllocator is Governance { require(params.activation != 0, "!active"); // Get the strategy specific debt config. - Config memory config = configs[_strategy]; + Config memory config = getConfig(_strategy); if (block.timestamp - config.lastUpdate <= minimumWait) { return (false, bytes("min wait")); @@ -295,19 +318,46 @@ contract GenericDebtAllocator is Governance { return (false, bytes("Below Min")); } + /** + * @notice Increase a strategies target debt ratio. + * @dev `setStrategyDebtRatio` functions will do all needed checks. + * @param _strategy The address of the strategy to increase the debt ratio for. + * @param _increase The amount in Basis Points to increase it. + */ + function increaseStrategyDebtRatio( + address _strategy, + uint256 _increase + ) external virtual { + uint256 _currentRatio = getConfig(_strategy).targetRatio; + setStrategyDebtRatio(_strategy, _currentRatio + _increase); + } + + /** + * @notice Decrease a strategies target debt ratio. + * @param _strategy The address of the strategy to decrease the debt ratio for. + * @param _decrease The amount in Basis Points to decrease it. + */ + function decreaseStrategyDebtRatio( + address _strategy, + uint256 _decrease + ) external virtual { + uint256 _currentRatio = getConfig(_strategy).targetRatio; + setStrategyDebtRatio(_strategy, _currentRatio - _decrease); + } + /** * @notice Sets a new target debt ratio for a strategy. - * @dev This will default to a 10% increase for max debt. + * @dev This will default to a 20% increase for max debt. * * @param _strategy Address of the strategy to set. * @param _targetRatio Amount in Basis points to allocate. */ - function setStrategyDebtRatios( + function setStrategyDebtRatio( address _strategy, uint256 _targetRatio - ) external virtual { - uint256 maxRatio = Math.min((_targetRatio * 11_000) / MAX_BPS, MAX_BPS); - setStrategyDebtRatios(_strategy, _targetRatio, maxRatio); + ) public virtual { + uint256 maxRatio = Math.min((_targetRatio * 12_000) / MAX_BPS, MAX_BPS); + setStrategyDebtRatio(_strategy, _targetRatio, maxRatio); } /** @@ -319,11 +369,11 @@ contract GenericDebtAllocator is Governance { * @param _targetRatio Amount in Basis points to allocate. * @param _maxRatio Max ratio to give on debt increases. */ - function setStrategyDebtRatios( + function setStrategyDebtRatio( address _strategy, uint256 _targetRatio, uint256 _maxRatio - ) public virtual onlyKeepers { + ) public virtual onlyManagers { // Make sure a minimumChange has been set. require(minimumChange != 0, "!minimum"); // Cannot be more than 100%. @@ -331,42 +381,33 @@ contract GenericDebtAllocator is Governance { // Max cannot be lower than the target. require(_maxRatio >= _targetRatio, "max ratio"); + // Get the current config. + Config memory config = getConfig(_strategy); + // Get what will be the new total debt ratio. - uint256 newDebtRatio = debtRatio - - configs[_strategy].targetRatio + + uint256 newTotalDebtRatio = totalDebtRatio - + config.targetRatio + _targetRatio; // Make sure it is under 100% allocated - require(newDebtRatio <= MAX_BPS, "ratio too high"); + require(newTotalDebtRatio <= MAX_BPS, "ratio too high"); - // Write to storage. - configs[_strategy].targetRatio = _targetRatio; - configs[_strategy].maxRatio = _maxRatio; + // Update local config. + config.targetRatio = uint16(_targetRatio); + config.maxRatio = uint16(_maxRatio); - debtRatio = newDebtRatio; + // Write to storage. + _configs[_strategy] = config; + totalDebtRatio = newTotalDebtRatio; - emit UpdateStrategyDebtRatios( + emit UpdateStrategyDebtRatio( _strategy, _targetRatio, _maxRatio, - newDebtRatio + newTotalDebtRatio ); } - /** - * @notice Set if a keeper can update debt. - * @param _address The address to set mapping for. - * @param _allowed If the address can call {update_debt}. - */ - function setKeepers( - address _address, - bool _allowed - ) external virtual onlyGovernance { - keepers[_address] = _allowed; - - emit UpdateKeeper(_address, _allowed); - } - /** * @notice Set the minimum change variable for a strategy. * @dev This is the amount of debt that will needed to be @@ -413,19 +454,50 @@ contract GenericDebtAllocator is Governance { } /** - * @notice Set the max acceptable base fee. - * @dev This defaults to max uint256 and will need to - * be set for it to be used. - * - * Is denominated in gwei. So 50gwei would be set as 50e9. - * - * @param _maxAcceptableBaseFee The new max base fee. + * @notice Set if a manager can update ratios. + * @param _address The address to set mapping for. + * @param _allowed If the address can call {update_debt}. */ - function setMaxAcceptableBaseFee( - uint256 _maxAcceptableBaseFee + function setManager( + address _address, + bool _allowed ) external virtual onlyGovernance { - maxAcceptableBaseFee = _maxAcceptableBaseFee; + managers[_address] = _allowed; + + emit UpdateManager(_address, _allowed); + } - emit UpdateMaxAcceptableBaseFee(_maxAcceptableBaseFee); + /** + * @notice Get a strategies full config. + * @dev Used for customizations by inheriting the contract. + * @param _strategy Address of the strategy. + * @return The strategies current Config. + */ + function getConfig( + address _strategy + ) public view virtual returns (Config memory) { + return _configs[_strategy]; + } + + /** + * @notice Get a strategies target debt ratio. + * @param _strategy Address of the strategy. + * @return The strategies current targetRatio. + */ + function getStrategyTargetRatio( + address _strategy + ) external view virtual returns (uint256) { + return getConfig(_strategy).targetRatio; + } + + /** + * @notice Get a strategies max debt ratio. + * @param _strategy Address of the strategy. + * @return The strategies current maxRatio. + */ + function getStrategyMaxRatio( + address _strategy + ) external view virtual returns (uint256) { + return getConfig(_strategy).maxRatio; } } diff --git a/contracts/debtAllocators/DebtAllocatorFactory.sol b/contracts/debtAllocators/DebtAllocatorFactory.sol new file mode 100644 index 0000000..197ed66 --- /dev/null +++ b/contracts/debtAllocators/DebtAllocatorFactory.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GNU AGPLv3 +pragma solidity 0.8.18; + +import {DebtAllocator} from "./DebtAllocator.sol"; +import {Clonable} from "@periphery/utils/Clonable.sol"; +import {RoleManager} from "../Managers/RoleManager.sol"; +import {Governance} from "@periphery/utils/Governance.sol"; + +/** + * @title YearnV3 Debt Allocator Factory + * @author yearn.finance + * @notice + * Factory to deploy a debt allocator for a YearnV3 vault. + */ +contract DebtAllocatorFactory is Governance, Clonable { + /// @notice Revert message for when a debt allocator already exists. + error AlreadyDeployed(address _allocator); + + /// @notice An event emitted when a keeper is added or removed. + event UpdateKeeper(address indexed keeper, bool allowed); + + /// @notice An event emitted when the max base fee is updated. + event UpdateMaxAcceptableBaseFee(uint256 newMaxAcceptableBaseFee); + + /// @notice An event emitted when a new debt allocator is added or deployed. + event NewDebtAllocator(address indexed allocator, address indexed vault); + + /// @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; + + /// @notice Mapping of addresses that are allowed to update debt. + mapping(address => bool) public keepers; + + constructor(address _governance) Governance(_governance) { + // Deploy a dummy allocator as the original. + original = address(new DebtAllocator(address(1), 0)); + + // Default max base fee to uint max. + maxAcceptableBaseFee = type(uint256).max; + + // Default to allow governance to be a keeper. + keepers[_governance] = true; + emit UpdateKeeper(_governance, true); + } + + /** + * @notice Clones a new debt allocator. + * @dev defaults to msg.sender as the governance role and 0 + * for the `minimumChange`. + * + * @param _vault The vault for the allocator to be hooked to. + * @return Address of the new debt allocator + */ + function newDebtAllocator( + address _vault + ) external virtual returns (address) { + return newDebtAllocator(_vault, 0); + } + + /** + * @notice Clones a new debt allocator. + * @param _vault The vault for the allocator to be hooked to. + * @param _minimumChange The minimum amount needed to trigger debt update. + * @return newAllocator Address of the new debt allocator + */ + function newDebtAllocator( + address _vault, + uint256 _minimumChange + ) public virtual returns (address newAllocator) { + // Clone new allocator off the original. + newAllocator = _clone(); + + // Initialize the new allocator. + DebtAllocator(newAllocator).initialize(_vault, _minimumChange); + + // Emit event. + emit NewDebtAllocator(newAllocator, _vault); + } + + /** + * @notice Set the max acceptable base fee. + * @dev This defaults to max uint256 and will need to + * be set for it to be used. + * + * Is denominated in gwei. So 50gwei would be set as 50e9. + * + * @param _maxAcceptableBaseFee The new max base fee. + */ + function setMaxAcceptableBaseFee( + uint256 _maxAcceptableBaseFee + ) external virtual onlyGovernance { + maxAcceptableBaseFee = _maxAcceptableBaseFee; + + emit UpdateMaxAcceptableBaseFee(_maxAcceptableBaseFee); + } + + /** + * @notice Set if a keeper can update debt. + * @param _address The address to set mapping for. + * @param _allowed If the address can call {update_debt}. + */ + function setKeeper( + address _address, + bool _allowed + ) external virtual onlyGovernance { + keepers[_address] = _allowed; + + emit UpdateKeeper(_address, _allowed); + } + + /** + * @notice Returns wether or not the current base fee is acceptable + * based on the `maxAcceptableBaseFee`. + * @return . If the current base fee is acceptable. + */ + function isCurrentBaseFeeAcceptable() external view virtual returns (bool) { + return maxAcceptableBaseFee >= block.basefee; + } +} diff --git a/contracts/debtAllocators/GenericDebtAllocatorFactory.sol b/contracts/debtAllocators/GenericDebtAllocatorFactory.sol deleted file mode 100644 index be5759c..0000000 --- a/contracts/debtAllocators/GenericDebtAllocatorFactory.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: GNU AGPLv3 -pragma solidity 0.8.18; - -import {GenericDebtAllocator} from "./GenericDebtAllocator.sol"; -import {Clonable} from "@periphery/utils/Clonable.sol"; - -/** - * @title YearnV3 Generic Debt Allocator Factory - * @author yearn.finance - * @notice - * Factory for anyone to easily deploy their own generic - * debt allocator for a Yearn V3 Vault. - */ -contract GenericDebtAllocatorFactory is Clonable { - event NewDebtAllocator(address indexed allocator, address indexed vault); - - constructor() { - original = address(new GenericDebtAllocator(address(1), address(2), 0)); - } - - /** - * @notice Clones a new debt allocator. - * @dev defaults to msg.sender as the governance role and 0 - * for the `minimumChange`. - * - * @param _vault The vault for the allocator to be hooked to. - * @return Address of the new generic debt allocator - */ - function newGenericDebtAllocator( - address _vault - ) external virtual returns (address) { - return newGenericDebtAllocator(_vault, msg.sender, 0); - } - - /** - * @notice Clones a new debt allocator. - * @dev defaults to 0 for the `minimumChange`. - * - * @param _vault The vault for the allocator to be hooked to. - * @param _governance Address to serve as governance. - * @return Address of the new generic debt allocator - */ - function newGenericDebtAllocator( - address _vault, - address _governance - ) external virtual returns (address) { - return newGenericDebtAllocator(_vault, _governance, 0); - } - - /** - * @notice Clones a new debt allocator. - * @param _vault The vault for the allocator to be hooked to. - * @param _governance Address to serve as governance. - * @return newAllocator Address of the new generic debt allocator - */ - function newGenericDebtAllocator( - address _vault, - address _governance, - uint256 _minimumChange - ) public virtual returns (address newAllocator) { - newAllocator = _clone(); - - GenericDebtAllocator(newAllocator).initialize( - _vault, - _governance, - _minimumChange - ); - - emit NewDebtAllocator(newAllocator, _vault); - } -} diff --git a/contracts/debtAllocators/YieldManager/Keeper.sol b/contracts/debtAllocators/YieldManager/Keeper.sol new file mode 100644 index 0000000..8a9e459 --- /dev/null +++ b/contracts/debtAllocators/YieldManager/Keeper.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity 0.8.18; + +import {Governance} from "@periphery/utils/Governance.sol"; +import {IStrategy} from "@tokenized-strategy/interfaces/IStrategy.sol"; + +/// @notice Holds the `keeper` role of a V3 strategy so that a +/// multiple addresses can call report. +contract Keeper is Governance { + /// @notice Emitted when a strategy is removed. + event StrategyRemoved(address indexed strategy); + + /// @notice An event emitted when a keeper is added or removed. + event UpdateKeeper(address indexed keeper, bool allowed); + + /// @notice Emitted when a new strategy is added to the manager. + event StrategyAdded(address indexed strategy, address indexed owner); + + /// @notice Only the `_strategy` specific owner can call. + modifier onlyStrategyOwner(address _strategy) { + _isStrategyOwner(_strategy); + _; + } + + /// @notice Only the keepers can call. + modifier onlyKeepers() { + _isKeeper(); + _; + } + + /// @notice Checks if the msg sender is the owner of the strategy. + function _isStrategyOwner(address _strategy) internal view virtual { + require(strategyOwner[_strategy] == msg.sender, "!owner"); + } + + /// @notice Checks if the msg sender is a keeper. + function _isKeeper() internal view virtual { + require(keepers[msg.sender], "!keeper"); + } + + /// @notice Address check for keepers allowed to call. + mapping(address => bool) public keepers; + + /// @notice strategy address => struct with info. + mapping(address => address) public strategyOwner; + + constructor(address _governance) Governance(_governance) {} + + /** + * @notice Add a new strategy, using the current `management` as the owner. + * @param _strategy The address of the strategy. + */ + function addNewStrategy(address _strategy) external virtual onlyGovernance { + require(strategyOwner[_strategy] == address(0), "already active"); + require(IStrategy(_strategy).keeper() == address(this), "!keeper"); + + address currentManager = IStrategy(_strategy).management(); + + // Store the owner of the strategy. + strategyOwner[_strategy] = currentManager; + + emit StrategyAdded(_strategy, currentManager); + } + + /** + * @notice Updates the owner of a strategy. + * @param _strategy The address of the strategy. + * @param _newOwner The address of the new owner. + */ + function updateStrategyOwner( + address _strategy, + address _newOwner + ) external virtual onlyStrategyOwner(_strategy) { + require( + _newOwner != address(0) && + _newOwner != address(this) && + _newOwner != _strategy, + "bad address" + ); + strategyOwner[_strategy] = _newOwner; + } + + /** + * @notice Removes the strategy. + * @param _strategy The address of the strategy. + */ + function removeStrategy(address _strategy) external virtual { + // Only governance or the strategy owner can call. + if (msg.sender != governance) _isStrategyOwner(_strategy); + + delete strategyOwner[_strategy]; + + emit StrategyRemoved(_strategy); + } + + /** + * @notice Reports full profit for a strategy. + * @param _strategy The address of the strategy. + */ + function report(address _strategy) external virtual onlyKeepers { + // If the strategy has been added to the keeper. + if (strategyOwner[_strategy] != address(0)) { + // Report profits. + IStrategy(_strategy).report(); + } + } + + /** + * @notice Tends a strategy. + * @param _strategy The address of the strategy. + */ + function tend(address _strategy) external virtual onlyKeepers { + // If the strategy has been added to the keeper. + if (strategyOwner[_strategy] != address(0)) { + // Tend. + IStrategy(_strategy).tend(); + } + } + + /** + * @notice Set if a keeper can update debt. + * @param _address The address to set mapping for. + * @param _allowed If the address can call {update_debt}. + */ + function setKeeper( + address _address, + bool _allowed + ) external virtual onlyGovernance { + keepers[_address] = _allowed; + + emit UpdateKeeper(_address, _allowed); + } +} diff --git a/contracts/debtAllocators/YieldManager/YieldManager.sol b/contracts/debtAllocators/YieldManager/YieldManager.sol new file mode 100644 index 0000000..61ec991 --- /dev/null +++ b/contracts/debtAllocators/YieldManager/YieldManager.sol @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity 0.8.18; + +import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; +import {AprOracle} from "@periphery/AprOracle/AprOracle.sol"; + +import {Keeper, Governance} from "./Keeper.sol"; +import {DebtAllocator} from "../DebtAllocator.sol"; + +/** + * @title YearnV3 Yield Yield Based Debt Allocator + * @author yearn.finance + * @notice + * This Debt Allocator is meant to be used alongside + * a Yearn V3 vault to allocate funds to the optimal strategy. + */ +contract YieldManager is Governance { + /// @notice Emitted when the open flag is updated. + event UpdateOpen(bool status); + + /// @notice Emitted when a proposer status is updated. + event UpdateProposer(address indexed proposer, bool status); + + /// @notice Emitted when a vaults status is updated. + event UpdateVaultAllocator(address indexed vault, address allocator); + + // Struct that contains the address of the strategy and its best allocation. + struct Allocation { + // Address of the strategy. + address strategy; + // Debt for the strategy to end with. + // Can take 79 Billion 18 decimal tokens. + uint96 newDebt; + } + + /// @notice Only allow the sender to be an allocator if not opened. + modifier onlyProposersOrOpen() { + _isProposerOrOpen(); + _; + } + + /// @notice Check if it has been opened or is an allocator. + function _isProposerOrOpen() internal view { + require(proposer[msg.sender] || open, "!allocator or open"); + } + + uint256 internal constant MAX_BPS = 10_000; + + /// @notice Contract that holds the logic and oracles for each strategy. + AprOracle internal constant aprOracle = + AprOracle(0xF012fBb9283e03994A7829fCE994a105cC066c14); + + /// @notice Flag to set to allow anyone to propose allocations. + bool public open; + + /// @notice Address that should hold the strategies `management` role. + address public immutable keeper; + + /// @notice Addresses that are allowed to propose allocations. + mapping(address => bool) public proposer; + + /// @notice Mapping for vaults that can be allocated for => its debt allocator. + mapping(address => address) public vaultAllocator; + + constructor(address _governance, address _keeper) Governance(_governance) { + keeper = _keeper; + } + + /** + * @notice Update a `_vault`s target allocation of debt. + * @dev This takes the address of a vault and an array of + * its strategies and their specific target allocations. + * + * The `_newAllocations` array should: + * - Contain all strategies that hold any amount of debt from the vault + * even if the debt wont be adjusted in order to get the correct + * on chain rate. + * - Be ordered so that all debt decreases are at the beginning of the array + * and debt increases at the end. + * - Account for all limiting values such as the vaults max_debt and min_total_idle + * as well as the strategies maxDeposit/maxRedeem that are enforced on debt updates. + * - Account for the expected differences in amounts caused by unrealised losses or profits. + * + * @param _vault The address of the vault to propose an allocation for. + * @param _newAllocations Array of strategies and their new proposed allocation. + * @return _currentRate The current weighted rate that the collective strategies are earning. + * @return _expectedRate The expected weighted rate that the collective strategies would earn. + */ + function updateAllocation( + address _vault, + Allocation[] memory _newAllocations + ) + external + virtual + onlyProposersOrOpen + returns (uint256 _currentRate, uint256 _expectedRate) + { + address allocator = vaultAllocator[_vault]; + require(allocator != address(0), "vault not added"); + + // Get the total assets the vault has. + uint256 _totalAssets = IVault(_vault).totalAssets(); + + // If 0 nothing to do. + if (_totalAssets == 0) return (0, 0); + + // Always first account for the amount idle in the vault. + uint256 _accountedFor = IVault(_vault).totalIdle(); + // Create local variables used through loops. + address _strategy; + uint256 _currentDebt; + uint256 _newDebt; + uint256 _strategyRate; + uint256 _targetRatio; + for (uint256 i = 0; i < _newAllocations.length; ++i) { + _strategy = _newAllocations[i].strategy; + _newDebt = uint256(_newAllocations[i].newDebt); + // Get the debt the strategy current has. + _currentDebt = IVault(_vault).strategies(_strategy).current_debt; + // Add to what we have accounted for. + _accountedFor += _currentDebt; + + // Get the current weighted rate the strategy is earning + _strategyRate = (aprOracle.getStrategyApr(_strategy, 0) * + _currentDebt); + + // Add to the amount currently being earned. + _currentRate += _strategyRate; + + // If we are withdrawing. + if (_currentDebt > _newDebt) { + // If we are pulling all debt from a strategy. + if (_newDebt == 0) { + // Try to report profits to have them start to unlock. + Keeper(keeper).report(_strategy); + } + + if ( + // We cannot decrease debt if the strategy has any unrealised losses. + IVault(_vault).assess_share_of_unrealised_losses( + _strategy, + _currentDebt + ) != 0 + ) { + // Realize the loss. + (, uint256 _loss) = IVault(_vault).process_report( + _strategy + ); + // Update balances. + _currentDebt -= _loss; + _totalAssets -= _loss; + _accountedFor -= _loss; + } + + // Make sure we the vault can withdraw that amount. + require( + _maxWithdraw(_vault, _strategy) >= _currentDebt - _newDebt, + "max withdraw" + ); + } else if (_currentDebt < _newDebt) { + // Make sure the strategy is allowed that much. + require( + IVault(_vault).strategies(_strategy).max_debt >= _newDebt, + "max debt" + ); + // Make sure the vault can deposit the desired amount. + require( + IVault(_strategy).maxDeposit(_vault) >= + _newDebt - _currentDebt, + "max deposit" + ); + } + + // Get the target based on the new debt. + _targetRatio = _newDebt < _totalAssets + ? (_newDebt * MAX_BPS) / _totalAssets + : MAX_BPS; + + // If different than the current target. + if ( + DebtAllocator(allocator).getStrategyTargetRatio(_strategy) != + _targetRatio + ) { + // Update allocation. + DebtAllocator(allocator).setStrategyDebtRatio( + _strategy, + _targetRatio + ); + } + + // If the new and current debt are the same. + if (_newDebt == _currentDebt) { + // We assume the new rate will be the same as current. + _expectedRate += _strategyRate; + } else if (_newDebt != 0) { + _expectedRate += (aprOracle.getStrategyApr( + _strategy, + int256(_newDebt) - int256(_currentDebt) // Debt change. + ) * _newDebt); + } + } + + // Make sure the minimum_total_idle was respected. + _checkMinimumTotalIdle(_vault, allocator); + // Make sure the ending amounts are the same otherwise rates could be wrong. + require(_totalAssets == _accountedFor, "cheater"); + // Make sure we expect to earn more than we currently are. + require(_expectedRate > _currentRate, "fail"); + } + + /** + * @notice Validates that all assets of a vault are accounted for in + * the proposed allocation array. + * + * If not the APR calculation will not be correct. + * + * @param _vault The address of the vault to propose an allocation for. + * @param _newAllocations Array of strategies and their new proposed allocation. + */ + function validateAllocation( + address _vault, + Allocation[] memory _newAllocations + ) external view virtual returns (bool) { + // Get the total assets the vault has. + uint256 _totalAssets = IVault(_vault).totalAssets(); + + // If 0 nothing to do. + if (_totalAssets == 0) return false; + + // Always first account for the amount idle in the vault. + uint256 _accountedFor = IVault(_vault).totalIdle(); + for (uint256 i = 0; i < _newAllocations.length; ++i) { + // Add the debt for each strategy in the array. + _accountedFor += IVault(_vault) + .strategies(_newAllocations[i].strategy) + .current_debt; + } + + // Make sure the ending amounts are the same. + return _totalAssets == _accountedFor; + } + + /** + * @notice Get the current weighted yield rate the vault is earning + * and the expected rate based on the proposed changes. + * + * Must divide by the totalAssets to get the APR as 1e18. + * + * @param _vault The address of the vault to propose an allocation for. + * @param _newAllocations Array of strategies and their new proposed allocation. + * @return _currentRate The current weighted rate that the collective strategies are earning. + * @return _expectedRate The expected weighted rate that the collective strategies would earn. + */ + function getCurrentAndExpectedRate( + address _vault, + Allocation[] memory _newAllocations + ) + external + view + virtual + returns (uint256 _currentRate, uint256 _expectedRate) + { + // Get the total assets the vault has. + uint256 _totalAssets = IVault(_vault).totalAssets(); + + // If 0 nothing to do. + if (_totalAssets == 0) return (0, 0); + + uint256 _newDebt; + address _strategy; + uint256 _currentDebt; + uint256 _strategyRate; + for (uint256 i = 0; i < _newAllocations.length; ++i) { + _newDebt = uint256(_newAllocations[i].newDebt); + _strategy = _newAllocations[i].strategy; + _currentDebt = IVault(_vault).strategies(_strategy).current_debt; + + // Get the current weighted rate the strategy is earning + _strategyRate = (aprOracle.getStrategyApr(_strategy, 0) * + _currentDebt); + + // Add to the amount currently being earned. + _currentRate += _strategyRate; + + // If the strategies debt is not changing. + if (_currentDebt == _newDebt) { + // No need to call the APR oracle again. + _expectedRate += _strategyRate; + } else { + // We add the expected rate with the new debt. + _expectedRate += (aprOracle.getStrategyApr( + _strategy, + int256(_newDebt) - int256(_currentDebt) + ) * _newDebt); + } + } + } + + /** + * @notice Update a `_vault`s allocation of debt. + * @dev This takes the address of a vault and an array of + * its strategies and their specific allocation. + * + * The `_newAllocations` array should: + * - Contain all strategies that hold any amount of debt from the vault + * even if the debt wont be adjusted in order to get the correct + * on chain rate. + * - Be ordered so that all debt decreases are at the beginning of the array + * and debt increases at the end. + * - Account for all limiting values such as the vaults max_debt and min_total_idle + * as well as the strategies maxDeposit/maxRedeem that are enforced on debt updates. + * - Account for the expected differences in amounts caused by unrealised losses or profits. + * + * This will not do any APR checks and assumes the sender has completed + * any and all necessary checks before sending. + * + * @param _vault The address of the vault to propose an allocation for. + * @param _newAllocations Array of strategies and their new proposed allocation. + */ + function updateAllocationPermissioned( + address _vault, + Allocation[] memory _newAllocations + ) external virtual onlyGovernance { + address allocator = vaultAllocator[_vault]; + require(allocator != address(0), "vault not added"); + address _strategy; + uint256 _newDebt; + uint256 _currentDebt; + uint256 _targetRatio; + uint256 _totalAssets = IVault(_vault).totalAssets(); + for (uint256 i = 0; i < _newAllocations.length; ++i) { + _strategy = _newAllocations[i].strategy; + _newDebt = uint256(_newAllocations[i].newDebt); + // Get the debt the strategy current has. + _currentDebt = IVault(_vault).strategies(_strategy).current_debt; + + // If we are withdrawing. + if (_currentDebt > _newDebt) { + // If we are pulling all debt from a strategy. + if (_newDebt == 0) { + // We need to report profits and have them immediately unlock to not lose out on locked profit. + Keeper(keeper).report(_strategy); + } + + if ( + // We cannot decrease debt if the strategy has any unrealised losses. + IVault(_vault).assess_share_of_unrealised_losses( + _strategy, + _currentDebt + ) != 0 + ) { + // Realize the loss. + (, uint256 _loss) = IVault(_vault).process_report( + _strategy + ); + // Update balances. + _currentDebt -= _loss; + _totalAssets -= _loss; + } + + // Make sure we the vault can withdraw that amount. + require( + _maxWithdraw(_vault, _strategy) >= _currentDebt - _newDebt, + "max withdraw" + ); + } else if (_currentDebt < _newDebt) { + // Make sure the strategy is allowed that much. + require( + IVault(_vault).strategies(_strategy).max_debt >= _newDebt, + "max debt" + ); + // Make sure the vault can deposit the desired amount. + require( + IVault(_strategy).maxDeposit(_vault) >= + _newDebt - _currentDebt, + "max deposit" + ); + } + + // Get the target based on the new debt. + _targetRatio = _newDebt < _totalAssets + ? (_newDebt * MAX_BPS) / _totalAssets + : MAX_BPS; + + if ( + DebtAllocator(allocator).getStrategyTargetRatio(_strategy) != + _targetRatio + ) { + // Update allocation. + DebtAllocator(allocator).setStrategyDebtRatio( + _strategy, + _targetRatio + ); + } + } + } + + /** + * @dev Helper function to get the max a vault can withdraw from a strategy to + * avoid stack to deep. + * + * Uses maxRedeem and convertToAssets since that is what the vault uses. + */ + function _maxWithdraw( + address _vault, + address _strategy + ) internal view virtual returns (uint256) { + return + IVault(_strategy).convertToAssets( + IVault(_strategy).maxRedeem(_vault) + ); + } + + /** + * @dev Helper function to check that the minimum_total_idle of the vault + * is accounted for in the allocation given. + * + * The expected Rate could be wrong if it allocated funds not allowed to be deployed. + * + * Use a separate function to avoid stack to deep. + */ + function _checkMinimumTotalIdle( + address _vault, + address _allocator + ) internal view virtual { + uint256 totalRatio = DebtAllocator(_allocator).totalDebtRatio(); + uint256 minIdle = IVault(_vault).minimum_total_idle(); + + // No need if minIdle is 0. + if (minIdle != 0) { + // Make sure we wouldn't allocate more than allowed. + require( + // Use 1e18 precision for more exact checks. + 1e18 - (1e14 * totalRatio) >= + (minIdle * 1e18) / IVault(_vault).totalAssets(), + "min idle" + ); + } + } + + /** + * @notice Sets the permission for a proposer. + * @param _address The address of the proposer. + * @param _allowed The permission to set for the proposer. + */ + function setProposer( + address _address, + bool _allowed + ) external virtual onlyGovernance { + proposer[_address] = _allowed; + + emit UpdateProposer(_address, _allowed); + } + + /** + * @notice Sets the mapping of vaults allowed. + * @param _vault The address of the _vault. + * @param _allocator The vault specific debt allocator. + */ + function setVaultAllocator( + address _vault, + address _allocator + ) external virtual onlyGovernance { + vaultAllocator[_vault] = _allocator; + + emit UpdateVaultAllocator(_vault, _allocator); + } + + /** + * @notice Sets the open status of the contract. + * @param _open The new open status to set. + */ + function setOpen(bool _open) external virtual onlyGovernance { + open = _open; + + emit UpdateOpen(_open); + } +} diff --git a/requirements.txt b/requirements.txt index 8cc4d8b..009de13 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ black==22.3.0 -eth-ape>=0.6.7 +eth-ape==0.6.27 pysha3==1.0.2 \ No newline at end of file diff --git a/scripts/deploy_allocator_factory.py b/scripts/deploy_allocator_factory.py index 80d958e..eb56809 100644 --- a/scripts/deploy_allocator_factory.py +++ b/scripts/deploy_allocator_factory.py @@ -10,17 +10,17 @@ def deploy_allocator_factory(): - print("Deploying Generic Debt Allocator Factory on ChainID", chain.chain_id) + print("Deploying Debt Allocator Factory on ChainID", chain.chain_id) if input("Do you want to continue? ") == "n": return - allocator_factory = project.GenericDebtAllocatorFactory + allocator_factory = project.DebtAllocatorFactory deployer_contract = project.Deployer.at( "0x8D85e7c9A4e369E53Acc8d5426aE1568198b0112" ) - salt_string = "Generic Debt Allocator Factory" + salt_string = "Debt Allocator Factory" # Create a SHA-256 hash object hash_object = hashlib.sha256() @@ -34,9 +34,14 @@ def deploy_allocator_factory(): print(f"Salt we are using {salt}") print("Init balance:", deployer.balance / 1e18) + gov = input("Governance? ") + + allocator_constructor = allocator_factory.constructor.encode_input(gov) + # generate and deploy deploy_bytecode = HexBytes( HexBytes(allocator_factory.contract_type.deployment_bytecode.bytecode) + + allocator_constructor ) print(f"Deploying the Factory...") @@ -50,6 +55,9 @@ def deploy_allocator_factory(): print("------------------") print(f"Deployed the Factory to {address}") print("------------------") + print( + f"Encoded Constructor to use for verifaction {allocator_constructor.hex()[2:]}" + ) def main(): diff --git a/scripts/deploy_role_manager.py b/scripts/deploy_role_manager.py index e547d14..0501041 100644 --- a/scripts/deploy_role_manager.py +++ b/scripts/deploy_role_manager.py @@ -3,7 +3,7 @@ from hexbytes import HexBytes import hashlib -deployer = accounts.load("v3_deployer") +deployer = accounts.load("") def deploy_role_manager(): diff --git a/scripts/deploy_yield_manager.py b/scripts/deploy_yield_manager.py new file mode 100644 index 0000000..7ada604 --- /dev/null +++ b/scripts/deploy_yield_manager.py @@ -0,0 +1,63 @@ +from ape import project, accounts, Contract, chain, networks, managers, compilers +from ape.utils import ZERO_ADDRESS +from hexbytes import HexBytes +import hashlib + +deployer = accounts.load("") + + +def deploy_yield_manager(): + + print("Deploying Yield Manager on ChainID", chain.chain_id) + + if input("Do you want to continue? ") == "n": + return + + yield_manager = project.YieldManager + deployer_contract = project.Deployer.at( + "0x8D85e7c9A4e369E53Acc8d5426aE1568198b0112" + ) + + salt_string = "Yield Manager test" + + # Create a SHA-256 hash object + hash_object = hashlib.sha256() + # Update the hash object with the string data + hash_object.update(salt_string.encode("utf-8")) + # Get the hexadecimal representation of the hash + hex_hash = hash_object.hexdigest() + # Convert the hexadecimal hash to an integer + salt = int(hex_hash, 16) + + print(f"Salt we are using {salt}") + print("Init balance:", deployer.balance / 1e18) + + print(f"Deploying the Yield Manager...") + print("Enter the addresses to use on deployment.") + + gov = input("Governance? ") + keeper = input("Keeper? ") + + constructor = yield_manager.constructor.encode_input( + gov, + keeper, + ) + + deploy_bytecode = HexBytes( + HexBytes(yield_manager.contract_type.deployment_bytecode.bytecode) + constructor + ) + + tx = deployer_contract.deploy(deploy_bytecode, salt, sender=deployer) + + event = list(tx.decode_logs(deployer_contract.Deployed)) + + address = event[0].addr + + print("------------------") + print(f"Deployed the Yield Manager to {address}") + print("------------------") + print(f"Encoded Constructor to use for verifaction {constructor.hex()[2:]}") + + +def main(): + deploy_yield_manager() diff --git a/tests/conftest.py b/tests/conftest.py index 5faa409..2c225e2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ import pytest -from ape import accounts, project -from utils.constants import MAX_INT, WEEK, ROLES +from ape import accounts, project, networks +from utils.constants import MAX_INT, WEEK, ROLES, ZERO_ADDRESS from web3 import Web3, HTTPProvider from hexbytes import HexBytes import os @@ -52,7 +52,7 @@ def vault_manager(accounts): @pytest.fixture(scope="session") def strategy_manager(accounts): - return accounts[8] + yield accounts[8] @pytest.fixture(scope="session") @@ -206,6 +206,24 @@ def strategy(asset, create_strategy): yield strategy +@pytest.fixture(scope="session") +def deploy_mock_tokenized(project, daddy, asset, management, keeper): + def deploy_mock_tokenized(name="name", apr=0): + mock_tokenized = daddy.deploy( + project.MockTokenized, asset, name, management, keeper, apr + ) + return mock_tokenized + + yield deploy_mock_tokenized + + +@pytest.fixture(scope="session") +def mock_tokenized(deploy_mock_tokenized): + mock_tokenized = deploy_mock_tokenized() + + yield mock_tokenized + + @pytest.fixture(scope="function") def create_vault_and_strategy(strategy, vault, deposit_into_vault): def create_vault_and_strategy(account, amount_into_vault): @@ -377,33 +395,31 @@ def address_provider(deploy_address_provider): @pytest.fixture(scope="session") -def deploy_generic_debt_allocator_factory(project, daddy): - def deploy_generic_debt_allocator_factory(gov=daddy): - generic_debt_allocator_factory = gov.deploy(project.GenericDebtAllocatorFactory) +def deploy_debt_allocator_factory(project, daddy, brain): + def deploy_debt_allocator_factory(gov=daddy): + debt_allocator_factory = gov.deploy(project.DebtAllocatorFactory, brain) - return generic_debt_allocator_factory + return debt_allocator_factory - yield deploy_generic_debt_allocator_factory + yield deploy_debt_allocator_factory @pytest.fixture(scope="session") -def generic_debt_allocator_factory(deploy_generic_debt_allocator_factory): - generic_debt_allocator_factory = deploy_generic_debt_allocator_factory() +def debt_allocator_factory(deploy_debt_allocator_factory): + debt_allocator_factory = deploy_debt_allocator_factory() - yield generic_debt_allocator_factory + yield debt_allocator_factory @pytest.fixture(scope="session") -def generic_debt_allocator(generic_debt_allocator_factory, project, vault, daddy): - tx = generic_debt_allocator_factory.newGenericDebtAllocator( - vault, daddy, sender=daddy - ) +def debt_allocator(debt_allocator_factory, project, vault, daddy): + tx = debt_allocator_factory.newDebtAllocator(vault, sender=daddy) - event = list(tx.decode_logs(generic_debt_allocator_factory.NewDebtAllocator))[0] + event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] - generic_debt_allocator = project.GenericDebtAllocator.at(event.allocator) + debt_allocator = project.DebtAllocator.at(event.allocator) - yield generic_debt_allocator + yield debt_allocator @pytest.fixture(scope="session") @@ -428,8 +444,9 @@ def deploy_role_manager( def role_manager( deploy_role_manager, daddy, + brain, healthcheck_accountant, - generic_debt_allocator_factory, + debt_allocator_factory, registry, ): role_manager = deploy_role_manager() @@ -439,7 +456,49 @@ def role_manager( ) role_manager.setPositionHolder(role_manager.REGISTRY(), registry, sender=daddy) role_manager.setPositionHolder( - role_manager.ALLOCATOR_FACTORY(), generic_debt_allocator_factory, sender=daddy + role_manager.ALLOCATOR_FACTORY(), debt_allocator_factory, sender=daddy ) return role_manager + + +@pytest.fixture(scope="session") +def deploy_keeper(project, daddy): + def deploy_keeper(): + keeper = daddy.deploy(project.Keeper, daddy) + + return keeper + + yield deploy_keeper + + +@pytest.fixture(scope="session") +def keeper(deploy_keeper): + keeper = deploy_keeper() + + yield keeper + + +@pytest.fixture(scope="session") +def deploy_yield_manager(project, daddy, keeper): + def deploy_yield_manager(): + yield_manager = daddy.deploy(project.YieldManager, daddy, keeper) + + return yield_manager + + yield deploy_yield_manager + + +@pytest.fixture(scope="session") +def yield_manager(deploy_yield_manager): + yield_manager = deploy_yield_manager() + + yield yield_manager + + +@pytest.fixture(scope="session") +def apr_oracle(project): + oracle = project.MockOracle + address = "0xF012fBb9283e03994A7829fCE994a105cC066c14" + networks.provider.set_code(address, oracle.contract_type.runtime_bytecode.bytecode) + yield oracle.at(address) diff --git a/tests/debtAllocators/test_debt_allocator.py b/tests/debtAllocators/test_debt_allocator.py new file mode 100644 index 0000000..6c1ae16 --- /dev/null +++ b/tests/debtAllocators/test_debt_allocator.py @@ -0,0 +1,466 @@ +import ape +from ape import chain, project +from utils.constants import ZERO_ADDRESS, MAX_INT, ROLES + + +def test_setup(debt_allocator_factory, brain, user, strategy, vault): + assert debt_allocator_factory.governance() == brain + assert debt_allocator_factory.keepers(brain) == True + assert debt_allocator_factory.maxAcceptableBaseFee() == MAX_INT + + tx = debt_allocator_factory.newDebtAllocator(vault, 0, sender=user) + + events = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator)) + + assert len(events) == 1 + assert events[0].vault == vault.address + + debt_allocator = project.DebtAllocator.at(events[0].allocator) + + assert debt_allocator.managers(brain) == False + assert debt_allocator.factory() == debt_allocator_factory.address + assert debt_allocator.vault() == vault.address + assert debt_allocator.getConfig(strategy) == (0, 0, 0, 0) + assert debt_allocator.totalDebtRatio() == 0 + with ape.reverts("!active"): + debt_allocator.shouldUpdateDebt(strategy) + + +def test_set_keepers(debt_allocator_factory, debt_allocator, brain, user): + assert debt_allocator_factory.keepers(brain) == True + assert debt_allocator_factory.keepers(user) == False + + with ape.reverts("!governance"): + debt_allocator_factory.setKeeper(user, True, sender=user) + + tx = debt_allocator_factory.setKeeper(user, True, sender=brain) + + event = list(tx.decode_logs(debt_allocator_factory.UpdateKeeper))[0] + + assert event.keeper == user + assert event.allowed == True + assert debt_allocator_factory.keepers(user) == True + + tx = debt_allocator_factory.setKeeper(brain, False, sender=brain) + + event = list(tx.decode_logs(debt_allocator_factory.UpdateKeeper))[0] + + assert event.keeper == brain + assert event.allowed == False + assert debt_allocator_factory.keepers(brain) == False + + +def test_set_managers(debt_allocator, brain, user): + assert debt_allocator.managers(brain) == False + assert debt_allocator.managers(user) == False + + with ape.reverts("!governance"): + debt_allocator.setManager(user, True, sender=user) + + tx = debt_allocator.setManager(user, True, sender=brain) + + event = list(tx.decode_logs(debt_allocator.UpdateManager))[0] + + assert event.manager == user + assert event.allowed == True + assert debt_allocator.managers(user) == True + + tx = debt_allocator.setManager(user, False, sender=brain) + + event = list(tx.decode_logs(debt_allocator.UpdateManager))[0] + + assert event.manager == user + assert event.allowed == False + assert debt_allocator.managers(user) == False + + +def test_set_minimum_change(debt_allocator, brain, strategy, user): + assert debt_allocator.getConfig(strategy) == (0, 0, 0, 0) + assert debt_allocator.minimumChange() == 0 + + minimum = int(1e17) + + with ape.reverts("!governance"): + debt_allocator.setMinimumChange(minimum, sender=user) + + with ape.reverts("zero"): + debt_allocator.setMinimumChange(0, sender=brain) + + tx = debt_allocator.setMinimumChange(minimum, sender=brain) + + event = list(tx.decode_logs(debt_allocator.UpdateMinimumChange))[0] + + assert event.newMinimumChange == minimum + assert debt_allocator.minimumChange() == minimum + + +def test_set_minimum_wait(debt_allocator, brain, strategy, user): + assert debt_allocator.getConfig(strategy) == (0, 0, 0, 0) + assert debt_allocator.minimumWait() == 0 + + minimum = int(1e17) + + with ape.reverts("!governance"): + debt_allocator.setMinimumWait(minimum, sender=user) + + tx = debt_allocator.setMinimumWait(minimum, sender=brain) + + event = list(tx.decode_logs(debt_allocator.UpdateMinimumWait))[0] + + assert event.newMinimumWait == minimum + assert debt_allocator.minimumWait() == minimum + + +def test_set_max_debt_update_loss(debt_allocator, brain, strategy, user): + assert debt_allocator.getConfig(strategy) == (0, 0, 0, 0) + assert debt_allocator.maxDebtUpdateLoss() == 1 + + max = int(69) + + with ape.reverts("!governance"): + debt_allocator.setMaxDebtUpdateLoss(max, sender=user) + + with ape.reverts("higher than max"): + debt_allocator.setMaxDebtUpdateLoss(10_001, sender=brain) + + tx = debt_allocator.setMaxDebtUpdateLoss(max, sender=brain) + + event = list(tx.decode_logs(debt_allocator.UpdateMaxDebtUpdateLoss))[0] + + assert event.newMaxDebtUpdateLoss == max + assert debt_allocator.maxDebtUpdateLoss() == max + + +def test_set_ratios( + debt_allocator, brain, daddy, vault, strategy, create_strategy, user +): + assert debt_allocator.getConfig(strategy) == (0, 0, 0, 0) + + minimum = int(1e17) + max = int(6_000) + target = int(5_000) + + with ape.reverts("!manager"): + debt_allocator.setStrategyDebtRatio(strategy, target, max, sender=user) + + vault.add_strategy(strategy.address, sender=daddy) + + with ape.reverts("!minimum"): + debt_allocator.setStrategyDebtRatio(strategy, target, max, sender=brain) + + debt_allocator.setMinimumChange(minimum, sender=brain) + + with ape.reverts("max too high"): + debt_allocator.setStrategyDebtRatio(strategy, target, int(10_001), sender=brain) + + with ape.reverts("max ratio"): + debt_allocator.setStrategyDebtRatio(strategy, int(max + 1), max, sender=brain) + + tx = debt_allocator.setStrategyDebtRatio(strategy, target, max, sender=brain) + + event = list(tx.decode_logs(debt_allocator.UpdateStrategyDebtRatio))[0] + + assert event.newTargetRatio == target + assert event.newMaxRatio == max + assert event.newTotalDebtRatio == target + assert debt_allocator.totalDebtRatio() == target + assert debt_allocator.getConfig(strategy) == (target, max, 0, 0) + + new_strategy = create_strategy() + vault.add_strategy(new_strategy, sender=daddy) + with ape.reverts("ratio too high"): + debt_allocator.setStrategyDebtRatio( + new_strategy, int(10_000), int(10_000), sender=brain + ) + + target = int(8_000) + tx = debt_allocator.setStrategyDebtRatio(strategy, target, sender=brain) + + event = list(tx.decode_logs(debt_allocator.UpdateStrategyDebtRatio))[0] + + assert event.newTargetRatio == target + assert event.newMaxRatio == target * 1.2 + assert event.newTotalDebtRatio == target + assert debt_allocator.totalDebtRatio() == target + assert debt_allocator.getConfig(strategy) == (target, target * 1.2, 0, 0) + + +def test_increase_debt_ratio( + debt_allocator, brain, daddy, vault, strategy, create_strategy, user +): + assert debt_allocator.getConfig(strategy) == (0, 0, 0, 0) + + minimum = int(1e17) + target = int(5_000) + increase = int(5_000) + max = target * 1.2 + + with ape.reverts("!manager"): + debt_allocator.increaseStrategyDebtRatio(strategy, increase, sender=user) + + vault.add_strategy(strategy.address, sender=daddy) + + with ape.reverts("!minimum"): + debt_allocator.increaseStrategyDebtRatio(strategy, increase, sender=brain) + + debt_allocator.setMinimumChange(minimum, sender=brain) + + tx = debt_allocator.increaseStrategyDebtRatio(strategy, increase, sender=brain) + + event = list(tx.decode_logs(debt_allocator.UpdateStrategyDebtRatio))[0] + + assert event.newTargetRatio == target + assert event.newMaxRatio == max + assert event.newTotalDebtRatio == target + assert debt_allocator.totalDebtRatio() == target + assert debt_allocator.getConfig(strategy) == (target, max, 0, 0) + + new_strategy = create_strategy() + vault.add_strategy(new_strategy, sender=daddy) + + with ape.reverts("ratio too high"): + debt_allocator.increaseStrategyDebtRatio(new_strategy, int(5_001), sender=brain) + + target = int(8_000) + max = target * 1.2 + increase = int(3_000) + tx = debt_allocator.increaseStrategyDebtRatio(strategy, increase, sender=brain) + + event = list(tx.decode_logs(debt_allocator.UpdateStrategyDebtRatio))[0] + + assert event.newTargetRatio == target + assert event.newMaxRatio == max + assert event.newTotalDebtRatio == target + assert debt_allocator.totalDebtRatio() == target + assert debt_allocator.getConfig(strategy) == (target, max, 0, 0) + + target = int(10_000) + max = int(10_000) + increase = int(2_000) + tx = debt_allocator.increaseStrategyDebtRatio(strategy, increase, sender=brain) + + event = list(tx.decode_logs(debt_allocator.UpdateStrategyDebtRatio))[0] + + assert event.newTargetRatio == target + assert event.newMaxRatio == max + assert event.newTotalDebtRatio == target + assert debt_allocator.totalDebtRatio() == target + assert debt_allocator.getConfig(strategy) == (target, max, 0, 0) + + +def test_decrease_debt_ratio( + debt_allocator, brain, vault, strategy, daddy, create_strategy, user +): + assert debt_allocator.getConfig(strategy) == (0, 0, 0, 0) + + minimum = int(1e17) + target = int(5_000) + max = target * 1.2 + + vault.add_strategy(strategy.address, sender=daddy) + debt_allocator.setMinimumChange(minimum, sender=brain) + + # Underflow + with ape.reverts(): + debt_allocator.decreaseStrategyDebtRatio(strategy, target, sender=brain) + + # Add the target + tx = debt_allocator.increaseStrategyDebtRatio(strategy, target, sender=brain) + + event = list(tx.decode_logs(debt_allocator.UpdateStrategyDebtRatio))[0] + + assert event.newTargetRatio == target + assert event.newMaxRatio == max + assert event.newTotalDebtRatio == target + assert debt_allocator.totalDebtRatio() == target + assert debt_allocator.getConfig(strategy) == (target, max, 0, 0) + + target = int(2_000) + max = target * 1.2 + decrease = int(3_000) + + with ape.reverts("!manager"): + debt_allocator.decreaseStrategyDebtRatio(strategy, decrease, sender=user) + + tx = debt_allocator.decreaseStrategyDebtRatio(strategy, decrease, sender=brain) + + event = list(tx.decode_logs(debt_allocator.UpdateStrategyDebtRatio))[0] + + assert event.newTargetRatio == target + assert event.newMaxRatio == max + assert event.newTotalDebtRatio == target + assert debt_allocator.totalDebtRatio() == target + assert debt_allocator.getConfig(strategy) == (target, max, 0, 0) + + target = int(0) + max = int(0) + decrease = int(2_000) + tx = debt_allocator.decreaseStrategyDebtRatio(strategy, decrease, sender=brain) + + event = list(tx.decode_logs(debt_allocator.UpdateStrategyDebtRatio))[0] + + assert event.newTargetRatio == target + assert event.newMaxRatio == max + assert event.newTotalDebtRatio == target + assert debt_allocator.totalDebtRatio() == target + assert debt_allocator.getConfig(strategy) == (0, 0, 0, 0) + + +def test_should_update_debt( + debt_allocator, vault, strategy, brain, daddy, deposit_into_vault, amount +): + assert debt_allocator.getConfig(strategy.address) == (0, 0, 0, 0) + vault.add_role(debt_allocator, ROLES.DEBT_MANAGER, sender=daddy) + + with ape.reverts("!active"): + debt_allocator.shouldUpdateDebt(strategy.address) + + vault.add_strategy(strategy.address, sender=daddy) + + minimum = int(1) + target = int(5_000) + max = int(5_000) + + debt_allocator.setMinimumChange(minimum, sender=brain) + debt_allocator.setStrategyDebtRatio(strategy, target, max, sender=brain) + + # Vault has no assets so should be false. + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + assert bool == False + assert bytes == ("Below Min").encode("utf-8") + + deposit_into_vault(vault, amount) + + # No max debt has been set so should be false. + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + assert bool == False + assert bytes == ("Below Min").encode("utf-8") + + vault.update_max_debt_for_strategy(strategy, MAX_INT, sender=daddy) + + # Should now want to allocate 50% + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + assert bool == True + print("got", bytes) + print("Made ", vault.update_debt.encode_input(strategy.address, amount // 2)) + assert bytes == vault.update_debt.encode_input(strategy.address, amount // 2) + + debt_allocator.update_debt(strategy, amount // 2, sender=brain) + chain.mine(1) + + # Should now be false again once allocated + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + assert bool == False + assert bytes == ("Below Min").encode("utf-8") + + # Update the ratio to make true + debt_allocator.setStrategyDebtRatio( + strategy, int(target + 1), int(target + 1), sender=brain + ) + + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + assert bool == True + assert bytes == vault.update_debt.encode_input( + strategy.address, int(amount * 5_001 // 10_000) + ) + + # Set a minimumWait time + debt_allocator.setMinimumWait(MAX_INT, sender=brain) + # Should now be false + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + assert bool == False + assert bytes == ("min wait").encode("utf-8") + + debt_allocator.setMinimumWait(0, sender=brain) + + # Lower the max debt so its == to current debt + vault.update_max_debt_for_strategy(strategy, int(amount // 2), sender=daddy) + + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + assert bool == False + assert bytes == ("Below Min").encode("utf-8") + + # Reset it. + vault.update_max_debt_for_strategy(strategy, MAX_INT, sender=daddy) + + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + assert bool == True + assert bytes == vault.update_debt.encode_input( + strategy.address, int(amount * 5_001 // 10_000) + ) + + # Increase the minimum_total_idle + vault.set_minimum_total_idle(vault.totalIdle(), sender=daddy) + + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + assert bool == False + assert bytes == ("No Idle").encode("utf-8") + + vault.set_minimum_total_idle(0, sender=daddy) + + # increase the minimum so its false again + debt_allocator.setMinimumChange(int(1e30), sender=brain) + + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + assert bool == False + assert bytes == ("Below Min").encode("utf-8") + + # Lower the target and minimum + debt_allocator.setMinimumChange(int(1), sender=brain) + debt_allocator.setStrategyDebtRatio( + strategy, int(target // 2), int(target // 2), sender=brain + ) + + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + assert bool == True + assert bytes == vault.update_debt.encode_input(strategy.address, amount // 4) + + +def test_update_debt( + debt_allocator_factory, + debt_allocator, + vault, + strategy, + brain, + daddy, + user, + deposit_into_vault, + amount, +): + assert debt_allocator.getConfig(strategy) == (0, 0, 0, 0) + deposit_into_vault(vault, amount) + + assert vault.totalIdle() == amount + assert vault.totalDebt() == 0 + + vault.add_strategy(strategy, sender=daddy) + vault.update_max_debt_for_strategy(strategy, MAX_INT, sender=daddy) + + # This reverts by the allocator + with ape.reverts("!keeper"): + debt_allocator.update_debt(strategy, amount, sender=user) + + # This reverts by the vault + with ape.reverts("not allowed"): + debt_allocator.update_debt(strategy, amount, sender=brain) + + vault.add_role( + debt_allocator, + ROLES.DEBT_MANAGER | ROLES.REPORTING_MANAGER, + sender=daddy, + ) + + debt_allocator.update_debt(strategy, amount, sender=brain) + + timestamp = debt_allocator.getConfig(strategy)[2] + assert timestamp != 0 + assert vault.totalIdle() == 0 + assert vault.totalDebt() == amount + + debt_allocator_factory.setKeeper(user, True, sender=brain) + + debt_allocator.update_debt(strategy, 0, sender=user) + + assert debt_allocator.getConfig(strategy)[2] != timestamp + assert vault.totalIdle() == amount + assert vault.totalDebt() == 0 diff --git a/tests/debtAllocators/test_generic_debt_allocator.py b/tests/debtAllocators/test_generic_debt_allocator.py deleted file mode 100644 index 130ecc4..0000000 --- a/tests/debtAllocators/test_generic_debt_allocator.py +++ /dev/null @@ -1,315 +0,0 @@ -import ape -from ape import chain, project -from utils.constants import ZERO_ADDRESS, MAX_INT, ROLES - - -def test_setup(generic_debt_allocator_factory, user, strategy, vault): - tx = generic_debt_allocator_factory.newGenericDebtAllocator( - vault, user, 0, sender=user - ) - - events = list(tx.decode_logs(generic_debt_allocator_factory.NewDebtAllocator)) - - assert len(events) == 1 - assert events[0].vault == vault.address - - generic_debt_allocator = project.GenericDebtAllocator.at(events[0].allocator) - - assert generic_debt_allocator.governance() == user - assert generic_debt_allocator.keepers(user) == True - assert generic_debt_allocator.vault() == vault.address - assert generic_debt_allocator.configs(strategy) == (0, 0, 0) - assert generic_debt_allocator.debtRatio() == 0 - with ape.reverts("!active"): - generic_debt_allocator.shouldUpdateDebt(strategy) - - -def test_set_keepers(generic_debt_allocator, daddy, vault, strategy, user): - assert generic_debt_allocator.keepers(daddy) == True - assert generic_debt_allocator.keepers(user) == False - - with ape.reverts("!governance"): - generic_debt_allocator.setKeepers(user, True, sender=user) - - tx = generic_debt_allocator.setKeepers(user, True, sender=daddy) - - event = list(tx.decode_logs(generic_debt_allocator.UpdateKeeper))[0] - - assert event.keeper == user - assert event.allowed == True - assert generic_debt_allocator.keepers(user) == True - - tx = generic_debt_allocator.setKeepers(daddy, False, sender=daddy) - - event = list(tx.decode_logs(generic_debt_allocator.UpdateKeeper))[0] - - assert event.keeper == daddy - assert event.allowed == False - assert generic_debt_allocator.keepers(daddy) == False - - -def test_set_minimum_change(generic_debt_allocator, daddy, vault, strategy, user): - assert generic_debt_allocator.configs(strategy) == (0, 0, 0) - assert generic_debt_allocator.minimumChange() == 0 - - minimum = int(1e17) - - with ape.reverts("!governance"): - generic_debt_allocator.setMinimumChange(minimum, sender=user) - - with ape.reverts("zero"): - generic_debt_allocator.setMinimumChange(0, sender=daddy) - - tx = generic_debt_allocator.setMinimumChange(minimum, sender=daddy) - - event = list(tx.decode_logs(generic_debt_allocator.UpdateMinimumChange))[0] - - assert event.newMinimumChange == minimum - assert generic_debt_allocator.minimumChange() == minimum - - -def test_set_minimum_wait(generic_debt_allocator, daddy, vault, strategy, user): - assert generic_debt_allocator.configs(strategy) == (0, 0, 0) - assert generic_debt_allocator.minimumWait() == 0 - - minimum = int(1e17) - - with ape.reverts("!governance"): - generic_debt_allocator.setMinimumWait(minimum, sender=user) - - tx = generic_debt_allocator.setMinimumWait(minimum, sender=daddy) - - event = list(tx.decode_logs(generic_debt_allocator.UpdateMinimumWait))[0] - - assert event.newMinimumWait == minimum - assert generic_debt_allocator.minimumWait() == minimum - - -def test_set_max_debt_update_loss(generic_debt_allocator, daddy, vault, strategy, user): - assert generic_debt_allocator.configs(strategy) == (0, 0, 0) - assert generic_debt_allocator.maxDebtUpdateLoss() == 1 - - max = int(69) - - with ape.reverts("!governance"): - generic_debt_allocator.setMaxDebtUpdateLoss(max, sender=user) - - with ape.reverts("higher than max"): - generic_debt_allocator.setMaxDebtUpdateLoss(10_001, sender=daddy) - - tx = generic_debt_allocator.setMaxDebtUpdateLoss(max, sender=daddy) - - event = list(tx.decode_logs(generic_debt_allocator.UpdateMaxDebtUpdateLoss))[0] - - assert event.newMaxDebtUpdateLoss == max - assert generic_debt_allocator.maxDebtUpdateLoss() == max - - -def test_set_ratios( - generic_debt_allocator, daddy, vault, strategy, create_strategy, user -): - assert generic_debt_allocator.configs(strategy) == (0, 0, 0) - - minimum = int(1e17) - max = int(6_000) - target = int(5_000) - - with ape.reverts("!keeper"): - generic_debt_allocator.setStrategyDebtRatios(strategy, target, max, sender=user) - - vault.add_strategy(strategy.address, sender=daddy) - - with ape.reverts("!minimum"): - generic_debt_allocator.setStrategyDebtRatios( - strategy, target, max, sender=daddy - ) - - generic_debt_allocator.setMinimumChange(minimum, sender=daddy) - - with ape.reverts("max too high"): - generic_debt_allocator.setStrategyDebtRatios( - strategy, target, int(10_001), sender=daddy - ) - - with ape.reverts("max ratio"): - generic_debt_allocator.setStrategyDebtRatios( - strategy, int(max + 1), max, sender=daddy - ) - - tx = generic_debt_allocator.setStrategyDebtRatios( - strategy, target, max, sender=daddy - ) - - event = list(tx.decode_logs(generic_debt_allocator.UpdateStrategyDebtRatios))[0] - - assert event.newTargetRatio == target - assert event.newMaxRatio == max - assert event.newTotalDebtRatio == target - assert generic_debt_allocator.debtRatio() == target - assert generic_debt_allocator.configs(strategy) == (target, max, 0) - - new_strategy = create_strategy() - vault.add_strategy(new_strategy, sender=daddy) - with ape.reverts("ratio too high"): - generic_debt_allocator.setStrategyDebtRatios( - new_strategy, int(10_000), int(10_000), sender=daddy - ) - - target = int(8_000) - tx = generic_debt_allocator.setStrategyDebtRatios(strategy, target, sender=daddy) - - event = list(tx.decode_logs(generic_debt_allocator.UpdateStrategyDebtRatios))[0] - - assert event.newTargetRatio == target - assert event.newMaxRatio == target * 1.1 - assert event.newTotalDebtRatio == target - assert generic_debt_allocator.debtRatio() == target - assert generic_debt_allocator.configs(strategy) == (target, target * 1.1, 0) - - -def test_should_update_debt( - generic_debt_allocator, vault, strategy, daddy, deposit_into_vault, amount -): - assert generic_debt_allocator.configs(strategy.address) == (0, 0, 0) - vault.add_role(generic_debt_allocator, ROLES.DEBT_MANAGER, sender=daddy) - - with ape.reverts("!active"): - generic_debt_allocator.shouldUpdateDebt(strategy.address) - - vault.add_strategy(strategy.address, sender=daddy) - - minimum = int(1) - target = int(5_000) - max = int(5_000) - - generic_debt_allocator.setMinimumChange(minimum, sender=daddy) - generic_debt_allocator.setStrategyDebtRatios(strategy, target, max, sender=daddy) - - # Vault has no assets so should be false. - (bool, bytes) = generic_debt_allocator.shouldUpdateDebt(strategy.address) - assert bool == False - assert bytes == ("Below Min").encode("utf-8") - - deposit_into_vault(vault, amount) - - # No max debt has been set so should be false. - (bool, bytes) = generic_debt_allocator.shouldUpdateDebt(strategy.address) - assert bool == False - assert bytes == ("Below Min").encode("utf-8") - - vault.update_max_debt_for_strategy(strategy, MAX_INT, sender=daddy) - - # Should now want to allocate 50% - (bool, bytes) = generic_debt_allocator.shouldUpdateDebt(strategy.address) - assert bool == True - print("got", bytes) - print("Made ", vault.update_debt.encode_input(strategy.address, amount // 2)) - assert bytes == vault.update_debt.encode_input(strategy.address, amount // 2) - - generic_debt_allocator.update_debt(strategy, amount // 2, sender=daddy) - chain.mine(1) - - # Should now be false again once allocated - (bool, bytes) = generic_debt_allocator.shouldUpdateDebt(strategy.address) - assert bool == False - assert bytes == ("Below Min").encode("utf-8") - - # Update the ratio to make true - generic_debt_allocator.setStrategyDebtRatios( - strategy, int(target + 1), int(target + 1), sender=daddy - ) - - (bool, bytes) = generic_debt_allocator.shouldUpdateDebt(strategy.address) - assert bool == True - assert bytes == vault.update_debt.encode_input( - strategy.address, int(amount * 5_001 // 10_000) - ) - - # Set a minimumWait time - generic_debt_allocator.setMinimumWait(MAX_INT, sender=daddy) - # Should now be false - (bool, bytes) = generic_debt_allocator.shouldUpdateDebt(strategy.address) - assert bool == False - assert bytes == ("min wait").encode("utf-8") - - generic_debt_allocator.setMinimumWait(0, sender=daddy) - - # Lower the max debt so its == to current debt - vault.update_max_debt_for_strategy(strategy, int(amount // 2), sender=daddy) - - (bool, bytes) = generic_debt_allocator.shouldUpdateDebt(strategy.address) - assert bool == False - assert bytes == ("Below Min").encode("utf-8") - - # Reset it. - vault.update_max_debt_for_strategy(strategy, MAX_INT, sender=daddy) - - (bool, bytes) = generic_debt_allocator.shouldUpdateDebt(strategy.address) - assert bool == True - assert bytes == vault.update_debt.encode_input( - strategy.address, int(amount * 5_001 // 10_000) - ) - - # Increase the minimum_total_idle - vault.set_minimum_total_idle(vault.totalIdle(), sender=daddy) - - (bool, bytes) = generic_debt_allocator.shouldUpdateDebt(strategy.address) - assert bool == False - assert bytes == ("No Idle").encode("utf-8") - - vault.set_minimum_total_idle(0, sender=daddy) - - # increase the minimum so its false again - generic_debt_allocator.setMinimumChange(int(1e30), sender=daddy) - - (bool, bytes) = generic_debt_allocator.shouldUpdateDebt(strategy.address) - assert bool == False - assert bytes == ("Below Min").encode("utf-8") - - # Lower the target and minimum - generic_debt_allocator.setMinimumChange(int(1), sender=daddy) - generic_debt_allocator.setStrategyDebtRatios( - strategy, int(target // 2), int(target // 2), sender=daddy - ) - - (bool, bytes) = generic_debt_allocator.shouldUpdateDebt(strategy.address) - assert bool == True - assert bytes == vault.update_debt.encode_input(strategy.address, amount // 4) - - -def test_update_debt( - generic_debt_allocator, vault, strategy, daddy, user, deposit_into_vault, amount -): - assert generic_debt_allocator.configs(strategy) == (0, 0, 0) - deposit_into_vault(vault, amount) - - assert vault.totalIdle() == amount - assert vault.totalDebt() == 0 - - vault.add_strategy(strategy, sender=daddy) - vault.update_max_debt_for_strategy(strategy, MAX_INT, sender=daddy) - - # This reverts by the allocator - with ape.reverts("!keeper"): - generic_debt_allocator.update_debt(strategy, amount, sender=user) - - # This reverts by the vault - with ape.reverts("not allowed"): - generic_debt_allocator.update_debt(strategy, amount, sender=daddy) - - vault.add_role(generic_debt_allocator, ROLES.DEBT_MANAGER, sender=daddy) - - generic_debt_allocator.update_debt(strategy, amount, sender=daddy) - - timestamp = generic_debt_allocator.configs(strategy)[2] - assert timestamp != 0 - assert vault.totalIdle() == 0 - assert vault.totalDebt() == amount - - generic_debt_allocator.setKeepers(user, True, sender=daddy) - - generic_debt_allocator.update_debt(strategy, 0, sender=user) - - assert generic_debt_allocator.configs(strategy)[2] != timestamp - assert vault.totalIdle() == amount - assert vault.totalDebt() == 0 diff --git a/tests/debtAllocators/yield/test_keeper_contract.py b/tests/debtAllocators/yield/test_keeper_contract.py new file mode 100644 index 0000000..3ae3561 --- /dev/null +++ b/tests/debtAllocators/yield/test_keeper_contract.py @@ -0,0 +1,164 @@ +import ape +from ape import chain, project +from utils.constants import ZERO_ADDRESS, MAX_INT, ROLES + + +def test_keeper_setup(keeper, mock_tokenized, daddy, yield_manager): + assert keeper.strategyOwner(mock_tokenized) == ZERO_ADDRESS + assert keeper.keepers(yield_manager) == False + assert keeper.governance() == daddy + + +def test_add_new_strategy(keeper, mock_tokenized, daddy, yield_manager, management): + mock_tokenized.setKeeper(daddy, sender=management) + assert keeper.strategyOwner(mock_tokenized) == ZERO_ADDRESS + assert keeper.keepers(yield_manager) == False + + with ape.reverts("!governance"): + keeper.addNewStrategy(mock_tokenized, sender=management) + + with ape.reverts("!keeper"): + keeper.addNewStrategy(mock_tokenized, sender=daddy) + + mock_tokenized.setKeeper(keeper, sender=management) + + tx = keeper.addNewStrategy(mock_tokenized, sender=daddy) + + assert mock_tokenized.keeper() == keeper + + event = list(tx.decode_logs(keeper.StrategyAdded))[0] + + assert event.strategy == mock_tokenized + assert event.owner == management + + assert keeper.strategyOwner(mock_tokenized) == management + + # cannot add it again + with ape.reverts("already active"): + keeper.addNewStrategy(mock_tokenized, sender=daddy) + + +def test_remove_strategy(keeper, mock_tokenized, management, user, daddy): + # Will revert on modifier if not yet added. + with ape.reverts("!owner"): + keeper.removeStrategy(mock_tokenized, sender=management) + + mock_tokenized.setKeeper(keeper, sender=management) + + keeper.addNewStrategy(mock_tokenized, sender=daddy) + + assert mock_tokenized.keeper() == keeper + assert keeper.strategyOwner(mock_tokenized) == management + + with ape.reverts("!owner"): + keeper.removeStrategy(mock_tokenized, sender=user) + + tx = keeper.removeStrategy(mock_tokenized, sender=management) + + event = list(tx.decode_logs(keeper.StrategyRemoved))[0] + + assert event.strategy == mock_tokenized + + assert keeper.strategyOwner(mock_tokenized) == ZERO_ADDRESS + assert mock_tokenized.management() == management + + +def test_update_owner(keeper, mock_tokenized, yield_manager, management, user, daddy): + with ape.reverts("!owner"): + keeper.updateStrategyOwner(mock_tokenized, user, sender=management) + + mock_tokenized.setKeeper(keeper, sender=management) + + keeper.addNewStrategy(mock_tokenized, sender=daddy) + + assert mock_tokenized.keeper() == keeper + assert keeper.strategyOwner(mock_tokenized) == management + + with ape.reverts("!owner"): + keeper.updateStrategyOwner(mock_tokenized, user, sender=user) + + with ape.reverts("bad address"): + keeper.updateStrategyOwner(mock_tokenized, ZERO_ADDRESS, sender=management) + + with ape.reverts("bad address"): + keeper.updateStrategyOwner(mock_tokenized, mock_tokenized, sender=management) + + with ape.reverts("bad address"): + keeper.updateStrategyOwner(mock_tokenized, keeper, sender=management) + + keeper.updateStrategyOwner(mock_tokenized, user, sender=management) + + assert keeper.strategyOwner(mock_tokenized) == user + assert mock_tokenized.keeper() == keeper + + +def test_report(keeper, mock_tokenized, management, yield_manager, asset, user, daddy): + mock_tokenized.setKeeper(keeper, sender=management) + + keeper.addNewStrategy(mock_tokenized, sender=daddy) + + assert mock_tokenized.keeper() == keeper + assert keeper.strategyOwner(mock_tokenized) == management + + # deposit into the strategy + to_deposit = asset.balanceOf(user) // 2 + profit = asset.balanceOf(user) - to_deposit + + asset.approve(mock_tokenized, to_deposit, sender=user) + mock_tokenized.deposit(to_deposit, user, sender=user) + + assert mock_tokenized.totalAssets() == to_deposit + assert mock_tokenized.totalSupply() == to_deposit + + # simulate profit + asset.transfer(mock_tokenized, profit, sender=user) + + assert mock_tokenized.totalAssets() == to_deposit + assert mock_tokenized.totalSupply() == to_deposit + current_unlock_time = mock_tokenized.profitMaxUnlockTime() + assert current_unlock_time == 0 + assert mock_tokenized.pricePerShare() == 10 ** asset.decimals() + + with ape.reverts("!keeper"): + keeper.report(mock_tokenized, sender=yield_manager) + + keeper.setKeeper(yield_manager, True, sender=daddy) + + keeper.report(mock_tokenized, sender=yield_manager) + + # Profit should be fully unlocked + assert mock_tokenized.totalAssets() == to_deposit + profit + assert mock_tokenized.totalSupply() == to_deposit + assert mock_tokenized.pricePerShare() > 10 ** asset.decimals() + + +def test_tend(keeper, mock_tokenized, management, yield_manager, asset, user, daddy): + mock_tokenized.setKeeper(keeper, sender=management) + + keeper.addNewStrategy(mock_tokenized, sender=daddy) + + assert mock_tokenized.keeper() == keeper + assert keeper.strategyOwner(mock_tokenized) == management + + # deposit into the strategy + to_deposit = asset.balanceOf(user) // 2 + profit = asset.balanceOf(user) - to_deposit + + asset.approve(mock_tokenized, to_deposit, sender=user) + mock_tokenized.deposit(to_deposit, user, sender=user) + + assert mock_tokenized.totalAssets() == to_deposit + assert mock_tokenized.totalSupply() == to_deposit + + # simulate profit + asset.transfer(mock_tokenized, profit, sender=user) + + assert mock_tokenized.totalAssets() == to_deposit + assert mock_tokenized.totalSupply() == to_deposit + + with ape.reverts("!keeper"): + keeper.tend(mock_tokenized, sender=yield_manager) + + keeper.setKeeper(yield_manager, True, sender=daddy) + + keeper.tend(mock_tokenized, sender=yield_manager) diff --git a/tests/debtAllocators/yield/test_yield_manager.py b/tests/debtAllocators/yield/test_yield_manager.py new file mode 100644 index 0000000..dbac718 --- /dev/null +++ b/tests/debtAllocators/yield/test_yield_manager.py @@ -0,0 +1,935 @@ +import ape +from ape import chain, project, networks +from utils.constants import ZERO_ADDRESS, MAX_INT, ROLES, MAX_BPS + + +def setup_vault(vault, strategies, oracle, chad): + for strategy in strategies: + vault.add_strategy(strategy, sender=chad) + vault.update_max_debt_for_strategy(strategy, 2**256 - 1, sender=chad) + management = strategy.management() + strategy.setProfitMaxUnlockTime(1, sender=management) + oracle.setOracle(strategy, strategy, sender=management) + + +def full_setup( + deploy_mock_tokenized, + vault, + apr_oracle, + daddy, + brain, + yield_manager, + debt_allocator, + keeper, + management, + user, +): + # Strategy two will have the higher apr + strategy_one = deploy_mock_tokenized("One", int(1e16)) + strategy_two = deploy_mock_tokenized("two", int(1e17)) + setup_vault(vault, [strategy_one, strategy_two], apr_oracle, daddy) + yield_manager.setVaultAllocator(vault, debt_allocator, sender=daddy) + debt_allocator.setManager(yield_manager, True, sender=brain) + debt_allocator.setMinimumChange(1, sender=brain) + vault.add_role(yield_manager, ROLES.REPORTING_MANAGER, sender=daddy) + vault.set_role( + debt_allocator, + ROLES.DEBT_MANAGER | ROLES.REPORTING_MANAGER, + sender=daddy, + ) + strategy_one.setKeeper(keeper, sender=management) + keeper.addNewStrategy(strategy_one, sender=daddy) + keeper.setKeeper(yield_manager, True, sender=daddy) + yield_manager.setProposer(user, True, sender=daddy) + + return (strategy_one, strategy_two) + + +def test_yield_manager_setup(yield_manager, daddy, vault, management, keeper): + assert yield_manager.keeper() == keeper + assert yield_manager.governance() == daddy + assert yield_manager.open() == False + assert yield_manager.proposer(management) == False + assert yield_manager.vaultAllocator(vault) == ZERO_ADDRESS + + +def test_setters(yield_manager, daddy, vault, debt_allocator, management): + assert yield_manager.vaultAllocator(vault) == ZERO_ADDRESS + assert yield_manager.proposer(management) == False + assert yield_manager.open() == False + + with ape.reverts("!governance"): + yield_manager.setVaultAllocator(vault, debt_allocator, sender=management) + + tx = yield_manager.setVaultAllocator(vault, debt_allocator, sender=daddy) + + event = list(tx.decode_logs(yield_manager.UpdateVaultAllocator))[0] + + assert event.vault == vault + assert event.allocator == debt_allocator + assert yield_manager.vaultAllocator(vault) == debt_allocator + + with ape.reverts("!governance"): + yield_manager.setProposer(management, True, sender=management) + + tx = yield_manager.setProposer(management, True, sender=daddy) + + event = list(tx.decode_logs(yield_manager.UpdateProposer))[0] + + assert event.proposer == management + assert event.status == True + assert yield_manager.proposer(management) == True + + tx = yield_manager.setProposer(management, False, sender=daddy) + + event = list(tx.decode_logs(yield_manager.UpdateProposer))[0] + + assert event.proposer == management + assert event.status == False + assert yield_manager.proposer(management) == False + + with ape.reverts("!governance"): + yield_manager.setOpen(True, sender=management) + + tx = yield_manager.setOpen(True, sender=daddy) + + event = list(tx.decode_logs(yield_manager.UpdateOpen))[0] + + assert event.status == True + assert yield_manager.open() + + +def test_update_allocation( + apr_oracle, + yield_manager, + vault, + management, + keeper, + daddy, + brain, + user, + amount, + asset, + deploy_mock_tokenized, + debt_allocator, +): + # Strategy two will have the higher apr + strategy_one = deploy_mock_tokenized("One", int(1e16)) + strategy_two = deploy_mock_tokenized("two", int(1e17)) + setup_vault(vault, [strategy_one, strategy_two], apr_oracle, daddy) + keeper.setKeeper(yield_manager, True, sender=daddy) + + asset.approve(vault, amount, sender=user) + vault.deposit(amount, user, sender=user) + + # Can just pass in one at to allocate + allocation = [(strategy_two, amount)] + + with ape.reverts("!allocator or open"): + yield_manager.updateAllocation(vault, allocation, sender=user) + + yield_manager.setProposer(user, True, sender=daddy) + + with ape.reverts("vault not added"): + yield_manager.updateAllocation(vault, allocation, sender=user) + + yield_manager.setVaultAllocator(vault, debt_allocator, sender=daddy) + + # Must give allocator the manager role + with ape.reverts("!manager"): + yield_manager.updateAllocation(vault, allocation, sender=user) + + debt_allocator.setManager(yield_manager, True, sender=brain) + debt_allocator.setMinimumChange(1, sender=brain) + vault.set_role( + debt_allocator, + ROLES.DEBT_MANAGER | ROLES.REPORTING_MANAGER, + sender=daddy, + ) + + # Set max debt for strategy to 0. + vault.update_max_debt_for_strategy(strategy_two, 0, sender=daddy) + + with ape.reverts("max debt"): + yield_manager.updateAllocation(vault, allocation, sender=user) + + vault.update_max_debt_for_strategy(strategy_two, 2**256 - 1, sender=daddy) + + tx = yield_manager.updateAllocation(vault, allocation, sender=user) + + (before, now) = tx.return_value + + assert debt_allocator.getConfig(strategy_two).targetRatio == MAX_BPS + assert debt_allocator.getConfig(strategy_one).targetRatio == 0 + assert debt_allocator.shouldUpdateDebt(strategy_one)[0] == False + assert debt_allocator.shouldUpdateDebt(strategy_two)[0] == True + assert debt_allocator.shouldUpdateDebt(strategy_two)[ + 1 + ] == vault.update_debt.encode_input(strategy_two.address, amount) + assert before == 0 + + debt_allocator.update_debt(strategy_two, amount, sender=brain) + assert debt_allocator.shouldUpdateDebt(strategy_two)[0] == False + + # assert now == int(1e17 * amount) + assert vault.totalIdle() == 0 + assert vault.totalDebt() == amount + assert vault.strategies(strategy_two).current_debt == amount + + allocation = [] + with ape.reverts("cheater"): + yield_manager.updateAllocation(vault, allocation, sender=user) + + allocation = [(strategy_one, amount)] + with ape.reverts("ratio too high"): + yield_manager.updateAllocation(vault, allocation, sender=user) + + allocation = [(strategy_two, amount // 2)] + with ape.reverts("fail"): + yield_manager.updateAllocation(vault, allocation, sender=user) + + # strategy one is now earning more + strategy_one.setApr(int(1.5e17), sender=management) + + # only move part + to_move = amount // 2 + # will revert if in the wrong order + allocation = [(strategy_one, to_move), (strategy_two, amount - to_move)] + with ape.reverts("ratio too high"): + yield_manager.updateAllocation(vault, allocation, sender=user) + + allocation = [(strategy_two, amount - to_move), (strategy_one, to_move)] + tx = yield_manager.updateAllocation(vault, allocation, sender=user) + + assert debt_allocator.getConfig(strategy_two).targetRatio != MAX_BPS + assert debt_allocator.getConfig(strategy_one).targetRatio != 0 + (bool_one, bytes_one) = debt_allocator.shouldUpdateDebt(strategy_one) + (bool_two, bytes_two) = debt_allocator.shouldUpdateDebt(strategy_two) + assert bool_one == False + assert bool_two == True + assert bytes_two == vault.update_debt.encode_input( + strategy_two.address, amount - to_move + ) + + debt_allocator.update_debt(strategy_two, amount - to_move, sender=brain) + + (bool_one, bytes_one) = debt_allocator.shouldUpdateDebt(strategy_one) + assert bool_one == True + assert bytes_one == vault.update_debt.encode_input(strategy_one, to_move) + + debt_allocator.update_debt(strategy_one, to_move, sender=brain) + + (bool_one, bytes_one) = debt_allocator.shouldUpdateDebt(strategy_one) + (bool_two, bytes_two) = debt_allocator.shouldUpdateDebt(strategy_two) + assert bool_one == False + assert bool_two == False + + assert vault.totalIdle() == 0 + assert vault.totalDebt() == amount + assert vault.totalAssets() == amount + assert vault.strategies(strategy_one).current_debt == to_move + assert vault.strategies(strategy_two).current_debt == amount - to_move + + # Try and move all + allocation = [(strategy_two, 0), (strategy_one, amount)] + + strategy_two.setKeeper(keeper, sender=management) + keeper.addNewStrategy(strategy_two, sender=daddy) + + tx = yield_manager.updateAllocation(vault, allocation, sender=user) + + assert debt_allocator.getConfig(strategy_two).targetRatio == 0 + assert debt_allocator.getConfig(strategy_one).targetRatio == MAX_BPS + + (bool_one, bytes_one) = debt_allocator.shouldUpdateDebt(strategy_one) + (bool_two, bytes_two) = debt_allocator.shouldUpdateDebt(strategy_two) + assert bool_one == False + assert bool_two == True + assert bytes_two == vault.update_debt.encode_input(strategy_two.address, 0) + + debt_allocator.update_debt(strategy_two, 0, sender=brain) + + (bool_one, bytes_one) = debt_allocator.shouldUpdateDebt(strategy_one) + assert bool_one == True + assert bytes_one == vault.update_debt.encode_input(strategy_one, amount) + + debt_allocator.update_debt(strategy_one, amount, sender=brain) + + (bool_one, bytes_one) = debt_allocator.shouldUpdateDebt(strategy_one) + (bool_two, bytes_two) = debt_allocator.shouldUpdateDebt(strategy_two) + assert bool_one == False + assert bool_two == False + + # assert len(list(tx.decode_logs(strategy_two.Reported))) == 1 + assert vault.totalIdle() == 0 + assert vault.totalDebt() == amount + assert vault.totalAssets() == amount + assert vault.strategies(strategy_one).current_debt == amount + assert vault.strategies(strategy_two).current_debt == 0 + + # Try and move all them all back + allocation = [(strategy_one, 1), (strategy_two, amount)] + with ape.reverts("fail"): + yield_manager.updateAllocation(vault, allocation, sender=user) + + +def test_update_allocation_pending_profit( + apr_oracle, + yield_manager, + vault, + management, + keeper, + daddy, + brain, + user, + amount, + asset, + deploy_mock_tokenized, + debt_allocator, +): + strategy_one, strategy_two = full_setup( + deploy_mock_tokenized=deploy_mock_tokenized, + vault=vault, + apr_oracle=apr_oracle, + daddy=daddy, + brain=brain, + yield_manager=yield_manager, + debt_allocator=debt_allocator, + keeper=keeper, + management=management, + user=user, + ) + + profit = amount // 10 + amount = amount - profit + + asset.approve(vault, amount, sender=user) + vault.deposit(amount, user, sender=user) + + debt_allocator.update_debt(strategy_one, amount, sender=brain) + + assert vault.totalAssets() == amount + assert vault.totalDebt() == amount + assert vault.strategies(strategy_one).current_debt == amount + + asset.transfer(strategy_one, profit, sender=user) + + allocation = [(strategy_one, 0), (strategy_two, amount + profit)] + + tx = yield_manager.updateAllocation(vault, allocation, sender=user) + + assert len(list(tx.decode_logs(strategy_one.Reported))) == 1 + + assert debt_allocator.shouldUpdateDebt(strategy_two)[0] == False + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy_one) + assert bool == True + assert bytes == vault.update_debt.encode_input(strategy_one, 0) + + tx = debt_allocator.update_debt(strategy_one, 0, sender=brain) + + event = list(tx.decode_logs(vault.StrategyReported)) + assert len(event) == 1 + assert event[0].gain == profit + + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy_two) + assert bool == True + assert bytes == vault.update_debt.encode_input(strategy_two, amount + profit) + + +def test_update_allocation_pending_loss( + apr_oracle, + yield_manager, + vault, + management, + keeper, + daddy, + brain, + user, + amount, + asset, + deploy_mock_tokenized, + debt_allocator, +): + strategy_one, strategy_two = full_setup( + deploy_mock_tokenized=deploy_mock_tokenized, + vault=vault, + apr_oracle=apr_oracle, + daddy=daddy, + brain=brain, + yield_manager=yield_manager, + debt_allocator=debt_allocator, + keeper=keeper, + management=management, + user=user, + ) + + loss = amount // 10 + + asset.approve(vault, amount, sender=user) + vault.deposit(amount, user, sender=user) + + debt_allocator.update_debt(strategy_one, amount, sender=brain) + + assert vault.totalAssets() == amount + assert vault.totalDebt() == amount + assert vault.strategies(strategy_one).current_debt == amount + + asset.transfer(user, loss, sender=strategy_one) + + allocation = [(strategy_one, 0), (strategy_two, amount)] + + tx = yield_manager.updateAllocation(vault, allocation, sender=user) + + assert len(list(tx.decode_logs(vault.StrategyReported))) == 1 + + assert debt_allocator.shouldUpdateDebt(strategy_two)[0] == False + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy_one) + assert bool == True + assert bytes == vault.update_debt.encode_input(strategy_one, 0) + + debt_allocator.update_debt(strategy_one, 0, sender=brain) + + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy_two) + assert bool == True + assert bytes == vault.update_debt.encode_input(strategy_two, amount - loss) + + +def test_update_allocation_pending_loss_move_half( + apr_oracle, + yield_manager, + vault, + management, + keeper, + daddy, + brain, + user, + amount, + asset, + deploy_mock_tokenized, + debt_allocator, +): + strategy_one, strategy_two = full_setup( + deploy_mock_tokenized=deploy_mock_tokenized, + vault=vault, + apr_oracle=apr_oracle, + daddy=daddy, + brain=brain, + yield_manager=yield_manager, + debt_allocator=debt_allocator, + keeper=keeper, + management=management, + user=user, + ) + + loss = amount // 10 + + asset.approve(vault, amount, sender=user) + vault.deposit(amount, user, sender=user) + + debt_allocator.update_debt(strategy_one, amount, sender=brain) + + assert vault.totalAssets() == amount + assert vault.totalDebt() == amount + assert vault.strategies(strategy_one).current_debt == amount + + # Record strategy loss + asset.transfer(user, loss, sender=strategy_one) + strategy_one.report(sender=keeper) + + to_move = amount // 2 + allocation = [(strategy_one, amount - loss - to_move), (strategy_two, to_move)] + + tx = yield_manager.updateAllocation(vault, allocation, sender=user) + + assert len(list(tx.decode_logs(vault.StrategyReported))) == 1 + assert vault.totalAssets() < amount + + assert debt_allocator.shouldUpdateDebt(strategy_two)[0] == False + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy_one) + assert bool == True + + debt_allocator.update_debt(strategy_one, amount - to_move, sender=brain) + + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy_two) + assert bool == True + assert bytes == vault.update_debt.encode_input(strategy_two, to_move - loss) + + +def test_update_allocation_pending_loss_move_all( + apr_oracle, + yield_manager, + vault, + management, + keeper, + daddy, + brain, + user, + amount, + asset, + deploy_mock_tokenized, + debt_allocator, +): + strategy_one, strategy_two = full_setup( + deploy_mock_tokenized=deploy_mock_tokenized, + vault=vault, + apr_oracle=apr_oracle, + daddy=daddy, + brain=brain, + yield_manager=yield_manager, + debt_allocator=debt_allocator, + keeper=keeper, + management=management, + user=user, + ) + + loss = amount // 10 + + asset.approve(vault, amount, sender=user) + vault.deposit(amount, user, sender=user) + + debt_allocator.update_debt(strategy_one, amount, sender=brain) + + assert vault.totalAssets() == amount + assert vault.totalDebt() == amount + assert vault.strategies(strategy_one).current_debt == amount + + # Record strategy loss + asset.transfer(user, loss, sender=strategy_one) + strategy_one.report(sender=keeper) + + to_move = amount + allocation = [(strategy_one, amount - to_move), (strategy_two, to_move)] + + tx = yield_manager.updateAllocation(vault, allocation, sender=user) + + assert len(list(tx.decode_logs(vault.StrategyReported))) == 1 + assert vault.totalAssets() < amount + + assert debt_allocator.shouldUpdateDebt(strategy_two)[0] == False + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy_one) + assert bool == True + + debt_allocator.update_debt(strategy_one, amount - to_move, sender=brain) + + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy_two) + assert bool == True + assert bytes == vault.update_debt.encode_input(strategy_two, to_move - loss) + + +def test_validate_allocation( + apr_oracle, + yield_manager, + vault, + daddy, + brain, + user, + amount, + asset, + deploy_mock_tokenized, + debt_allocator, +): + strategy_one = deploy_mock_tokenized("One") + strategy_two = deploy_mock_tokenized("two") + setup_vault(vault, [strategy_one, strategy_two], apr_oracle, daddy) + yield_manager.setVaultAllocator(vault, debt_allocator, sender=daddy) + vault.set_role( + debt_allocator, + ROLES.DEBT_MANAGER | ROLES.REPORTING_MANAGER, + sender=daddy, + ) + + asset.approve(vault, amount, sender=user) + vault.deposit(amount, user, sender=user) + + # Can validate the allocation with no strategies when all is idle + assert vault.totalIdle() == amount + + assert yield_manager.validateAllocation(vault, []) + assert yield_manager.validateAllocation(vault, [(strategy_one, amount)]) + assert yield_manager.validateAllocation( + vault, [(strategy_one, amount), (strategy_two, 0)] + ) + + debt_allocator.update_debt(strategy_one, amount // 2, sender=brain) + + assert yield_manager.validateAllocation(vault, []) == False + assert yield_manager.validateAllocation(vault, [(strategy_one, amount)]) + assert yield_manager.validateAllocation( + vault, [(strategy_one, amount), (strategy_two, 0)] + ) + + # Now will be false + debt_allocator.update_debt(strategy_two, vault.totalIdle() // 2, sender=brain) + + assert yield_manager.validateAllocation(vault, []) == False + assert yield_manager.validateAllocation(vault, [(strategy_one, amount)]) == False + assert yield_manager.validateAllocation( + vault, [(strategy_one, amount), (strategy_two, 0)] + ) + + +def test_get_current_and_expected( + apr_oracle, + yield_manager, + vault, + management, + keeper, + daddy, + user, + amount, + asset, + deploy_mock_tokenized, +): + # Strategy two will have the higher apr + strategy_one = deploy_mock_tokenized("One", int(1e16)) + strategy_two = deploy_mock_tokenized("two", int(1e17)) + setup_vault(vault, [strategy_one, strategy_two], apr_oracle, daddy) + + asset.approve(vault, amount, sender=user) + vault.deposit(amount, user, sender=user) + + allocation = [] + (current, expected) = yield_manager.getCurrentAndExpectedRate(vault, allocation) + + assert current == 0 + assert expected == 0 + + allocation = [(strategy_one, 0), (strategy_two, 0)] + (current, expected) = yield_manager.getCurrentAndExpectedRate(vault, allocation) + assert current == 0 + assert expected == 0 + + allocation = [(strategy_one, amount), (strategy_two, 0)] + (current, expected) = yield_manager.getCurrentAndExpectedRate(vault, allocation) + assert current == 0 + assert expected != 0 + + vault.update_debt(strategy_one, amount, sender=daddy) + + allocation = [(strategy_one, 0), (strategy_two, amount)] + (current, new_expected) = yield_manager.getCurrentAndExpectedRate(vault, allocation) + assert current == expected + assert expected != 0 + assert expected > 0 + + assert vault.totalAssets() == amount + assert vault.totalDebt() == amount + assert vault.strategies(strategy_one).current_debt == amount + + +def test_update_allocation_permissioned( + apr_oracle, + yield_manager, + vault, + management, + keeper, + daddy, + brain, + user, + amount, + asset, + deploy_mock_tokenized, + debt_allocator, +): + strategy_one, strategy_two = full_setup( + deploy_mock_tokenized=deploy_mock_tokenized, + vault=vault, + apr_oracle=apr_oracle, + daddy=daddy, + brain=brain, + yield_manager=yield_manager, + debt_allocator=debt_allocator, + keeper=keeper, + management=management, + user=user, + ) + asset.approve(vault, amount, sender=user) + vault.deposit(amount, user, sender=user) + + # Can just pass in one at to allocate + allocation = [(strategy_two, amount)] + + with ape.reverts("!governance"): + yield_manager.updateAllocationPermissioned(vault, allocation, sender=user) + + yield_manager.setProposer(user, True, sender=daddy) + + # Still cant allocate even with allocator role + with ape.reverts("!governance"): + yield_manager.updateAllocationPermissioned(vault, allocation, sender=user) + + tx = yield_manager.updateAllocationPermissioned(vault, allocation, sender=daddy) + + assert debt_allocator.shouldUpdateDebt(strategy_one)[0] == False + (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy_two) + assert bool == True + assert bytes == vault.update_debt.encode_input(strategy_two, amount) + + debt_allocator.update_debt(strategy_two, amount, sender=brain) + + allocation = [(strategy_one, amount)] + with ape.reverts("ratio too high"): + yield_manager.updateAllocationPermissioned(vault, allocation, sender=daddy) + + # strategy one is now earning more + strategy_one.setApr(int(1.5e17), sender=management) + + # only move part + to_move = amount // 2 + # will revert if in the wrong order + allocation = [(strategy_one, to_move), (strategy_two, amount - to_move)] + with ape.reverts("ratio too high"): + yield_manager.updateAllocationPermissioned(vault, allocation, sender=daddy) + + allocation = [(strategy_two, amount - to_move), (strategy_one, to_move)] + tx = yield_manager.updateAllocationPermissioned(vault, allocation, sender=daddy) + + assert debt_allocator.getConfig(strategy_two).targetRatio != MAX_BPS + assert debt_allocator.getConfig(strategy_one).targetRatio != 0 + (bool_one, bytes_one) = debt_allocator.shouldUpdateDebt(strategy_one) + (bool_two, bytes_two) = debt_allocator.shouldUpdateDebt(strategy_two) + assert bool_one == False + assert bool_two == True + assert bytes_two == vault.update_debt.encode_input( + strategy_two.address, amount - to_move + ) + + debt_allocator.update_debt(strategy_two, amount - to_move, sender=brain) + + (bool_one, bytes_one) = debt_allocator.shouldUpdateDebt(strategy_one) + assert bool_one == True + assert bytes_one == vault.update_debt.encode_input(strategy_one, to_move) + + debt_allocator.update_debt(strategy_one, to_move, sender=brain) + + (bool_one, bytes_one) = debt_allocator.shouldUpdateDebt(strategy_one) + (bool_two, bytes_two) = debt_allocator.shouldUpdateDebt(strategy_two) + assert bool_one == False + assert bool_two == False + + # Try and move all + allocation = [(strategy_two, 0), (strategy_one, amount)] + + strategy_two.setKeeper(keeper, sender=management) + keeper.addNewStrategy(strategy_two, sender=daddy) + keeper.setKeeper(yield_manager, True, sender=daddy) + + tx = yield_manager.updateAllocationPermissioned(vault, allocation, sender=daddy) + + assert len(list(tx.decode_logs(strategy_two.Reported))) == 1 + + assert debt_allocator.getConfig(strategy_two).targetRatio == 0 + assert debt_allocator.getConfig(strategy_one).targetRatio == MAX_BPS + (bool_one, bytes_one) = debt_allocator.shouldUpdateDebt(strategy_one) + (bool_two, bytes_two) = debt_allocator.shouldUpdateDebt(strategy_two) + assert bool_one == False + assert bool_two == True + assert bytes_two == vault.update_debt.encode_input(strategy_two.address, 0) + + debt_allocator.update_debt(strategy_two, 0, sender=brain) + + (bool_one, bytes_one) = debt_allocator.shouldUpdateDebt(strategy_one) + assert bool_one == True + assert bytes_one == vault.update_debt.encode_input(strategy_one, amount) + + debt_allocator.update_debt(strategy_one, amount, sender=brain) + + (bool_one, bytes_one) = debt_allocator.shouldUpdateDebt(strategy_one) + (bool_two, bytes_two) = debt_allocator.shouldUpdateDebt(strategy_two) + assert bool_one == False + assert bool_two == False + + assert vault.totalIdle() == 0 + assert vault.totalDebt() == amount + assert vault.totalAssets() == amount + assert vault.strategies(strategy_one).current_debt == amount + assert vault.strategies(strategy_two).current_debt == 0 + + +def test_update_allocation__max_withdraw( + apr_oracle, + yield_manager, + vault, + management, + keeper, + daddy, + brain, + user, + amount, + asset, + deploy_mock_tokenized, + debt_allocator, +): + strategy_one, strategy_two = full_setup( + deploy_mock_tokenized=deploy_mock_tokenized, + vault=vault, + apr_oracle=apr_oracle, + daddy=daddy, + brain=brain, + yield_manager=yield_manager, + debt_allocator=debt_allocator, + keeper=keeper, + management=management, + user=user, + ) + + asset.approve(vault, amount, sender=user) + vault.deposit(amount, user, sender=user) + + debt_allocator.update_debt(strategy_one, amount, sender=brain) + + assert vault.totalAssets() == amount + assert vault.totalDebt() == amount + assert vault.strategies(strategy_one).current_debt == amount + + limit = amount // 2 + + # Simulate a max withdraw limit + strategy_one.setLimit(limit, sender=daddy) + + # Try and move all + allocation = [(strategy_one, 0), (strategy_two, amount)] + + with ape.reverts("max withdraw"): + yield_manager.updateAllocation(vault, allocation, sender=user) + + # Can withdraw up to the limit + allocation = [(strategy_one, amount - limit), (strategy_two, limit)] + yield_manager.updateAllocation(vault, allocation, sender=user) + + # lower the limit to 0 + strategy_one.setLimit(0, sender=daddy) + + # Now can move everything. + allocation = [(strategy_one, 0), (strategy_two, amount)] + yield_manager.updateAllocation(vault, allocation, sender=user) + + +def test_update_allocation__max_deposit( + apr_oracle, + yield_manager, + vault, + management, + keeper, + daddy, + brain, + user, + amount, + asset, + deploy_mock_tokenized, + debt_allocator, +): + strategy_one, strategy_two = full_setup( + deploy_mock_tokenized=deploy_mock_tokenized, + vault=vault, + apr_oracle=apr_oracle, + daddy=daddy, + brain=brain, + yield_manager=yield_manager, + debt_allocator=debt_allocator, + keeper=keeper, + management=management, + user=user, + ) + + asset.approve(vault, amount, sender=user) + vault.deposit(amount, user, sender=user) + + debt_allocator.update_debt(strategy_one, amount, sender=brain) + + assert vault.totalAssets() == amount + assert vault.totalDebt() == amount + assert vault.strategies(strategy_one).current_debt == amount + + limit = amount // 2 + + # Simulate a max deposit limit + strategy_two.setMaxDebt(limit, sender=daddy) + + # Try and move all + allocation = [(strategy_one, 0), (strategy_two, amount)] + + with ape.reverts("max deposit"): + yield_manager.updateAllocation(vault, allocation, sender=user) + + # Can deposit up to the limit + allocation = [(strategy_one, amount - limit), (strategy_two, limit)] + yield_manager.updateAllocation(vault, allocation, sender=user) + + # Increase the limit + strategy_two.setMaxDebt(2**256 - 1, sender=daddy) + + # Now can move everything. + allocation = [(strategy_one, 0), (strategy_two, amount)] + yield_manager.updateAllocation(vault, allocation, sender=user) + + +def test_update_allocation__min_idle( + apr_oracle, + yield_manager, + vault, + management, + keeper, + daddy, + brain, + user, + amount, + asset, + deploy_mock_tokenized, + debt_allocator, +): + strategy_one, strategy_two = full_setup( + deploy_mock_tokenized=deploy_mock_tokenized, + vault=vault, + apr_oracle=apr_oracle, + daddy=daddy, + brain=brain, + yield_manager=yield_manager, + debt_allocator=debt_allocator, + keeper=keeper, + management=management, + user=user, + ) + + asset.approve(vault, amount, sender=user) + vault.deposit(amount, user, sender=user) + + assert vault.totalAssets() == amount + assert vault.totalDebt() == 0 + + min_idle = amount // 2 + + # add a minimum total idle requirement + vault.set_minimum_total_idle(min_idle, sender=daddy) + + # Try and move all + allocation = [(strategy_one, amount)] + + with ape.reverts("min idle"): + yield_manager.updateAllocation(vault, allocation, sender=user) + + # Even just over the limit reverts. + allocation = [(strategy_one, int(amount - min_idle + 1e18))] + + with ape.reverts("min idle"): + yield_manager.updateAllocation(vault, allocation, sender=user) + + # Can deposit up to the limit + allocation = [(strategy_one, amount - min_idle)] + tx = yield_manager.updateAllocation(vault, allocation, sender=user) + + event = list(tx.decode_logs(debt_allocator.UpdateStrategyDebtRatio))[0] + assert event.newTargetRatio == 5_000 + + # lower the min idle to 0 + vault.set_minimum_total_idle(0, sender=daddy) + + # Now can move everything. + allocation = [(strategy_one, 0), (strategy_two, amount)] + tx = yield_manager.updateAllocation(vault, allocation, sender=user) + + event = list(tx.decode_logs(debt_allocator.UpdateStrategyDebtRatio)) + + assert len(event) == 2 + assert event[0].newTargetRatio == 0 + assert event[1].newTargetRatio == 10_000 diff --git a/tests/manager/test_role_manager.py b/tests/manager/test_role_manager.py index caac85b..1ac246c 100644 --- a/tests/manager/test_role_manager.py +++ b/tests/manager/test_role_manager.py @@ -21,7 +21,7 @@ def test_role_manager_setup( strategy_manager, healthcheck_accountant, registry, - generic_debt_allocator_factory, + debt_allocator_factory, release_registry, vault, asset, @@ -36,6 +36,7 @@ def test_role_manager_setup( assert role_manager.ratingToString(6) == "" assert role_manager.chad() == daddy assert role_manager.getAllVaults() == [] + assert role_manager.getVault(asset, vault.apiVersion(), 1) == ZERO_ADDRESS assert role_manager.getDaddy() == daddy assert role_manager.getBrain() == brain assert role_manager.getSecurity() == security @@ -43,7 +44,7 @@ def test_role_manager_setup( assert role_manager.getStrategyManager() == strategy_manager assert role_manager.getRegistry() == registry assert role_manager.getAccountant() == healthcheck_accountant - assert role_manager.getAllocatorFactory() == generic_debt_allocator_factory + 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 @@ -63,7 +64,7 @@ def test__positions( strategy_manager, healthcheck_accountant, registry, - generic_debt_allocator_factory, + debt_allocator_factory, user, ): assert role_manager.getDaddy() == daddy @@ -73,7 +74,7 @@ def test__positions( assert role_manager.getStrategyManager() == strategy_manager assert role_manager.getRegistry() == registry assert role_manager.getAccountant() == healthcheck_accountant - assert role_manager.getAllocatorFactory() == generic_debt_allocator_factory + assert role_manager.getAllocatorFactory() == debt_allocator_factory assert role_manager.getPositionHolder(role_manager.DADDY()) == daddy assert role_manager.getPositionHolder(role_manager.BRAIN()) == brain assert role_manager.getPositionHolder(role_manager.SECURITY()) == security @@ -89,7 +90,7 @@ def test__positions( ) assert ( role_manager.getPositionHolder(role_manager.ALLOCATOR_FACTORY()) - == generic_debt_allocator_factory + == debt_allocator_factory ) # Check roles assert role_manager.getDaddyRoles() == daddy_roles @@ -127,7 +128,7 @@ def test__positions( "Strategy Manager": (strategy_manager, strategy_manager_roles), "Registry": (registry, 0), "Accountant": (healthcheck_accountant, 0), - "Allocator Factory": (generic_debt_allocator_factory, 0), + "Allocator Factory": (debt_allocator_factory, 0), } new_role = int(420_69) @@ -271,17 +272,20 @@ def test_deploy_new_vault( registry, release_registry, vault_factory, - generic_debt_allocator_factory, + debt_allocator_factory, ): + rating = 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 + ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 - rating = int(1) - deposit_limit = int(100e18) - profit_unlock = int(695) - with ape.reverts("rating out of range"): role_manager.newVault(asset, 0, deposit_limit, profit_unlock, sender=daddy) @@ -319,10 +323,10 @@ def test_deploy_new_vault( assert event.rating == rating allocator = event.debtAllocator - event = list(tx.decode_logs(generic_debt_allocator_factory.NewDebtAllocator))[0] + event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] assert event.vault == vault - debt_allocator = project.GenericDebtAllocator.at(event.allocator) + debt_allocator = project.DebtAllocator.at(event.allocator) assert allocator == debt_allocator (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( @@ -334,6 +338,7 @@ def test_deploy_new_vault( 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.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator @@ -365,7 +370,75 @@ def test_deploy_new_vault( # Check debt allocator assert debt_allocator.vault() == vault - assert debt_allocator.governance() == brain + + +def test_deploy_new_vault__duplicate_reverts( + role_manager, + daddy, + asset, + healthcheck_accountant, + registry, + release_registry, + vault_factory, + debt_allocator_factory, +): + rating = 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 + ) + assert registry.numAssets() == 0 + assert registry.numEndorsedVaults(asset) == 0 + + # ADd the role manager as an endorser + registry.setEndorser(role_manager, True, sender=daddy) + assert registry.endorsers(role_manager) + + healthcheck_accountant.setVaultManager(role_manager, sender=daddy) + + tx = role_manager.newVault( + asset, rating, deposit_limit, profit_unlock, 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] + + assert event.vault == vault + debt_allocator = project.DebtAllocator.at(event.allocator) + + (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( + vault + ) + + assert vault_asset == asset + assert vault_rating == rating + 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.vaults(index) == vault + assert role_manager.isVaultsRoleManager(vault) == True + assert role_manager.getDebtAllocator(vault) == debt_allocator + assert role_manager.getRating(vault) == rating + 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) + + # can with a different rating. + role_manager.newVault( + asset, rating + 1, deposit_limit, profit_unlock, max_fee="1", sender=daddy + ) def test_deploy_new_vault__default_values( @@ -381,15 +454,18 @@ def test_deploy_new_vault__default_values( registry, release_registry, vault_factory, - generic_debt_allocator_factory, + debt_allocator_factory, ): + rating = 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 + ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 - rating = int(2) - with ape.reverts("rating out of range"): role_manager.newVault(asset, 0, sender=daddy) @@ -425,10 +501,10 @@ def test_deploy_new_vault__default_values( assert event.vault == vault assert event.rating == rating - event = list(tx.decode_logs(generic_debt_allocator_factory.NewDebtAllocator))[0] + event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] assert event.vault == vault - debt_allocator = project.GenericDebtAllocator.at(event.allocator) + debt_allocator = project.DebtAllocator.at(event.allocator) (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( vault @@ -439,6 +515,7 @@ def test_deploy_new_vault__default_values( 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.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator @@ -470,7 +547,6 @@ def test_deploy_new_vault__default_values( # Check debt allocator assert debt_allocator.vault() == vault - assert debt_allocator.governance() == brain def setup_role_manager( @@ -496,7 +572,7 @@ def test_add_new_vault__endorsed( registry, release_registry, vault_factory, - generic_debt_allocator_factory, + debt_allocator_factory, ): setup_role_manager( role_manager=role_manager, @@ -507,12 +583,17 @@ def test_add_new_vault__endorsed( daddy=daddy, ) + name = " ksjdfl" + symbol = "sdfa" + rating = int(1) + assert role_manager.getAllVaults() == [] + assert ( + role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 - name = " ksjdfl" - symbol = "sdfa" tx = registry.newEndorsedVault(asset, name, symbol, daddy, 100, sender=daddy) event = list(tx.decode_logs(registry.NewEndorsedVault))[0] @@ -522,8 +603,6 @@ def test_add_new_vault__endorsed( assert registry.numEndorsedVaults(asset) == 1 assert registry.getAllEndorsedVaults() == [[vault]] - rating = int(1) - with ape.reverts("!allowed"): role_manager.addNewVault(vault, rating, sender=user) @@ -546,10 +625,10 @@ def test_add_new_vault__endorsed( assert event.vault == vault assert event.rating == rating - event = list(tx.decode_logs(generic_debt_allocator_factory.NewDebtAllocator))[0] + event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] assert event.vault == vault - debt_allocator = project.GenericDebtAllocator.at(event.allocator) + debt_allocator = project.DebtAllocator.at(event.allocator) (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( vault @@ -560,6 +639,7 @@ def test_add_new_vault__endorsed( 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.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator @@ -587,7 +667,6 @@ def test_add_new_vault__endorsed( # Check debt allocator assert debt_allocator.vault() == vault - assert debt_allocator.governance() == brain def test_add_new_vault__not_endorsed( @@ -603,7 +682,7 @@ def test_add_new_vault__not_endorsed( registry, release_registry, vault_factory, - generic_debt_allocator_factory, + debt_allocator_factory, ): setup_role_manager( role_manager=role_manager, @@ -616,6 +695,8 @@ def test_add_new_vault__not_endorsed( name = " ksjdfl" symbol = "sdfa" + rating = int(1) + tx = vault_factory.deploy_new_vault(asset, name, symbol, daddy, 100, sender=daddy) event = list(tx.decode_logs(vault_factory.NewVault))[0] @@ -624,11 +705,12 @@ def test_add_new_vault__not_endorsed( ) assert role_manager.getAllVaults() == [] + assert ( + role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 - rating = int(1) - with ape.reverts("!allowed"): role_manager.addNewVault(vault, rating, sender=user) @@ -651,10 +733,10 @@ def test_add_new_vault__not_endorsed( assert event.vault == vault assert event.rating == rating - event = list(tx.decode_logs(generic_debt_allocator_factory.NewDebtAllocator))[0] + event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] assert event.vault == vault - debt_allocator = project.GenericDebtAllocator.at(event.allocator) + debt_allocator = project.DebtAllocator.at(event.allocator) (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( vault @@ -665,6 +747,7 @@ def test_add_new_vault__not_endorsed( 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.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator @@ -695,7 +778,6 @@ def test_add_new_vault__not_endorsed( # Check debt allocator assert debt_allocator.vault() == vault - assert debt_allocator.governance() == brain def test_add_new_vault__with_debt_allocator( @@ -711,7 +793,7 @@ def test_add_new_vault__with_debt_allocator( registry, release_registry, vault_factory, - generic_debt_allocator_factory, + debt_allocator_factory, ): setup_role_manager( role_manager=role_manager, @@ -724,6 +806,8 @@ def test_add_new_vault__with_debt_allocator( name = " ksjdfl" symbol = "sdfa" + rating = int(1) + tx = vault_factory.deploy_new_vault(asset, name, symbol, daddy, 100, sender=daddy) event = list(tx.decode_logs(vault_factory.NewVault))[0] @@ -732,14 +816,16 @@ def test_add_new_vault__with_debt_allocator( ) assert role_manager.getAllVaults() == [] + assert ( + role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 - rating = int(1) - tx = generic_debt_allocator_factory.newGenericDebtAllocator(vault, sender=daddy) - event = list(tx.decode_logs(generic_debt_allocator_factory.NewDebtAllocator))[0] + tx = debt_allocator_factory.newDebtAllocator(vault, sender=brain) + event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] assert event.vault == vault - debt_allocator = project.GenericDebtAllocator.at(event.allocator) + debt_allocator = project.DebtAllocator.at(event.allocator) with ape.reverts("!allowed"): role_manager.addNewVault(vault, rating, debt_allocator, sender=user) @@ -773,6 +859,7 @@ def test_add_new_vault__with_debt_allocator( 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.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator @@ -803,7 +890,6 @@ def test_add_new_vault__with_debt_allocator( # Check debt allocator assert debt_allocator.vault() == vault - assert debt_allocator.governance() == daddy def test_add_new_vault__with_accountant( @@ -819,7 +905,7 @@ def test_add_new_vault__with_accountant( registry, release_registry, vault_factory, - generic_debt_allocator_factory, + debt_allocator_factory, ): setup_role_manager( role_manager=role_manager, @@ -832,6 +918,7 @@ def test_add_new_vault__with_accountant( name = " ksjdfl" symbol = "sdfa" + rating = int(1) tx = vault_factory.deploy_new_vault(asset, name, symbol, daddy, 100, sender=daddy) event = list(tx.decode_logs(vault_factory.NewVault))[0] @@ -840,14 +927,16 @@ def test_add_new_vault__with_accountant( ) assert role_manager.getAllVaults() == [] + assert ( + role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 - rating = int(1) - tx = generic_debt_allocator_factory.newGenericDebtAllocator(vault, sender=daddy) - event = list(tx.decode_logs(generic_debt_allocator_factory.NewDebtAllocator))[0] + tx = debt_allocator_factory.newDebtAllocator(vault, sender=brain) + event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] assert event.vault == vault - debt_allocator = project.GenericDebtAllocator.at(event.allocator) + debt_allocator = project.DebtAllocator.at(event.allocator) vault.add_role(daddy, ROLES.ACCOUNTANT_MANAGER, sender=daddy) vault.set_accountant(user, sender=daddy) @@ -885,6 +974,7 @@ def test_add_new_vault__with_accountant( 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.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator @@ -915,7 +1005,82 @@ def test_add_new_vault__with_accountant( # Check debt allocator assert debt_allocator.vault() == vault - assert debt_allocator.governance() == daddy + + +def test_add_new_vault__duplicate_reverts( + role_manager, + daddy, + asset, + 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, + ) + + rating = 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 + ) + assert registry.numAssets() == 0 + assert registry.numEndorsedVaults(asset) == 0 + + tx = role_manager.newVault( + asset, rating, deposit_limit, profit_unlock, 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] + + assert event.vault == vault + debt_allocator = project.DebtAllocator.at(event.allocator) + + (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( + vault + ) + + assert vault_asset == asset + assert vault_rating == rating + 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.vaults(index) == vault + assert role_manager.isVaultsRoleManager(vault) == True + assert role_manager.getDebtAllocator(vault) == debt_allocator + assert role_manager.getRating(vault) == rating + + name = " ksjdfl" + symbol = "sdfa" + + # Deploy a new vault with the same asset + tx = vault_factory.deploy_new_vault(asset, name, symbol, daddy, 100, sender=daddy) + + event = list(tx.decode_logs(vault_factory.NewVault))[0] + new_vault = project.dependencies["yearn-vaults"]["v3.0.1"].VaultV3.at( + event.vault_address + ) + + with ape.reverts(to_bytes32(f"Already Deployed {vault.address}")): + role_manager.addNewVault(new_vault, rating, debt_allocator, sender=daddy) + + # Can add it with a different rating. + role_manager.addNewVault(vault, rating + 1, debt_allocator, sender=daddy) def test_new_debt_allocator__deploys_one( @@ -931,7 +1096,7 @@ def test_new_debt_allocator__deploys_one( registry, release_registry, vault_factory, - generic_debt_allocator_factory, + debt_allocator_factory, ): setup_role_manager( role_manager=role_manager, @@ -942,20 +1107,20 @@ def test_new_debt_allocator__deploys_one( daddy=daddy, ) + rating = int(2) + assert role_manager.getAllVaults() == [] assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 - rating = int(2) - # Deploy a vault tx = role_manager.newVault(asset, rating, 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(generic_debt_allocator_factory.NewDebtAllocator))[0] - debt_allocator = project.GenericDebtAllocator.at(event.allocator) + 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 @@ -966,6 +1131,7 @@ def test_new_debt_allocator__deploys_one( 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.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator @@ -986,7 +1152,6 @@ def test_new_debt_allocator__deploys_one( # Check debt allocator assert debt_allocator.vault() == vault - assert debt_allocator.governance() == brain # Update to a new debt allocator with ape.reverts("!allowed"): @@ -997,13 +1162,16 @@ def test_new_debt_allocator__deploys_one( tx = role_manager.updateDebtAllocator(vault, sender=brain) - event = list(tx.decode_logs(generic_debt_allocator_factory.NewDebtAllocator))[0] - new_debt_allocator = project.GenericDebtAllocator.at(event.allocator) + event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] + new_debt_allocator = project.DebtAllocator.at(event.allocator) + + event = list(tx.decode_logs(role_manager.UpdateDebtAllocator))[0] + + assert event.vault == vault + assert event.debtAllocator == new_debt_allocator assert new_debt_allocator != debt_allocator assert new_debt_allocator.vault() == vault - assert new_debt_allocator.governance() == brain - assert new_debt_allocator.maxAcceptableBaseFee() == MAX_INT (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( vault @@ -1047,7 +1215,7 @@ def test_new_debt_allocator__already_deployed( registry, release_registry, vault_factory, - generic_debt_allocator_factory, + debt_allocator_factory, ): setup_role_manager( role_manager=role_manager, @@ -1058,20 +1226,20 @@ def test_new_debt_allocator__already_deployed( daddy=daddy, ) + rating = int(2) + assert role_manager.getAllVaults() == [] assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 - rating = int(2) - # Deploy a vault tx = role_manager.newVault(asset, rating, 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(generic_debt_allocator_factory.NewDebtAllocator))[0] - debt_allocator = project.GenericDebtAllocator.at(event.allocator) + 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 @@ -1082,6 +1250,7 @@ def test_new_debt_allocator__already_deployed( 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.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator @@ -1102,11 +1271,9 @@ def test_new_debt_allocator__already_deployed( # Check debt allocator assert debt_allocator.vault() == vault - assert debt_allocator.governance() == brain - - tx = generic_debt_allocator_factory.newGenericDebtAllocator(vault, sender=daddy) - event = list(tx.decode_logs(generic_debt_allocator_factory.NewDebtAllocator))[0] - new_debt_allocator = project.GenericDebtAllocator.at(event.allocator) + tx = debt_allocator_factory.newDebtAllocator(vault, sender=brain) + event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] + new_debt_allocator = project.DebtAllocator.at(event.allocator) # Update to a new debt allocator with ape.reverts("!allowed"): @@ -1117,10 +1284,13 @@ def test_new_debt_allocator__already_deployed( tx = role_manager.updateDebtAllocator(vault, new_debt_allocator, sender=brain) + event = list(tx.decode_logs(role_manager.UpdateDebtAllocator))[0] + + assert event.vault == vault + assert event.debtAllocator == new_debt_allocator + assert new_debt_allocator != debt_allocator assert new_debt_allocator.vault() == vault - assert new_debt_allocator.governance() == daddy - assert new_debt_allocator.maxAcceptableBaseFee() == MAX_INT (vault_asset, vault_rating, vault_debt_allocator, index) = role_manager.vaultConfig( vault @@ -1164,7 +1334,7 @@ def test_remove_vault( registry, release_registry, vault_factory, - generic_debt_allocator_factory, + debt_allocator_factory, ): setup_role_manager( role_manager=role_manager, @@ -1175,20 +1345,23 @@ def test_remove_vault( daddy=daddy, ) + rating = int(2) + assert role_manager.getAllVaults() == [] + assert ( + role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + ) assert registry.numAssets() == 0 assert registry.numEndorsedVaults(asset) == 0 - rating = int(2) - # Deploy a vault tx = role_manager.newVault(asset, rating, 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(generic_debt_allocator_factory.NewDebtAllocator))[0] - debt_allocator = project.GenericDebtAllocator.at(event.allocator) + 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 @@ -1199,6 +1372,7 @@ def test_remove_vault( 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.vaults(index) == vault assert role_manager.isVaultsRoleManager(vault) == True assert role_manager.getDebtAllocator(vault) == debt_allocator @@ -1219,7 +1393,6 @@ def test_remove_vault( # Check debt allocator assert debt_allocator.vault() == vault - assert debt_allocator.governance() == brain # Remove the vault with ape.reverts("!allowed"): @@ -1242,6 +1415,9 @@ def test_remove_vault( assert vault_debt_allocator == ZERO_ADDRESS assert index == 0 assert role_manager.getAllVaults() == [] + assert ( + role_manager.getVault(asset, vault_factory.apiVersion(), rating) == ZERO_ADDRESS + ) assert role_manager.isVaultsRoleManager(vault) == False assert role_manager.getDebtAllocator(vault) == ZERO_ADDRESS assert role_manager.getRating(vault) == 0