From aa47dd94ba6a73e2c52e74963f7651a922c74322 Mon Sep 17 00:00:00 2001 From: Schlag <89420541+Schlagonia@users.noreply.github.com> Date: Mon, 19 Aug 2024 21:07:34 -0600 Subject: [PATCH] build: allocator (#51) * build: allocator * fix: import * build: compile allocator * chore: comments * fix: encode call --- contracts/Managers/V3Deployer.sol | 4 +- contracts/debtAllocators/DebtAllocator.sol | 541 ++++++++++++------ .../debtAllocators/DebtAllocatorFactory.sol | 128 +---- .../DebtOptimizerApplicator.sol | 22 +- tests/conftest.py | 12 +- tests/debtAllocators/test_debt_allocator.py | 336 +++++++---- 6 files changed, 626 insertions(+), 417 deletions(-) diff --git a/contracts/Managers/V3Deployer.sol b/contracts/Managers/V3Deployer.sol index 8e873c4..bf88bb7 100644 --- a/contracts/Managers/V3Deployer.sol +++ b/contracts/Managers/V3Deployer.sol @@ -348,7 +348,9 @@ contract V3Deployer is Positions { } if (_debtAllocator == address(0)) { - _debtAllocator = address(new DebtAllocatorFactory(_roleManager)); + _debtAllocator = DebtAllocatorFactory( + _fromAddressProvider(ALLOCATOR_FACTORY) + ).newDebtAllocator(_roleManager); } projects[_id] = Project({ diff --git a/contracts/debtAllocators/DebtAllocator.sol b/contracts/debtAllocators/DebtAllocator.sol index 40c149e..17671ff 100644 --- a/contracts/debtAllocators/DebtAllocator.sol +++ b/contracts/debtAllocators/DebtAllocator.sol @@ -1,22 +1,27 @@ // SPDX-License-Identifier: GNU AGPLv3 pragma solidity >=0.8.18; +import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; +import {Governance} from "@periphery/utils/Governance.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; -import {DebtAllocatorFactory} from "./DebtAllocatorFactory.sol"; +interface IBaseFee { + function basefee_global() external view returns (uint256); +} /** - * @title YearnV3 Debt Allocator + * @title YearnV3 Debt Allocator * @author yearn.finance * @notice * 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. * - * Each allocator contract will serve one Vault and each strategy - * that should be managed by this allocator will need to be added - * manually by setting a `targetRatio` and `maxRatio`. + * Each vault that should be managed by this allocator will + * need to be added by first setting a `minimumChange` for the + * vault, which will act as the amount of funds to move that will + * trigger a debt update. Then adding each strategy by setting a + * `targetRatio` and `maxRatio`. * * The allocator aims to allocate debt between the strategies * based on their set target ratios. Which are denominated in basis @@ -25,12 +30,22 @@ import {DebtAllocatorFactory} from "./DebtAllocatorFactory.sol"; * * The trigger will attempt to allocate up to the `maxRatio` when * the strategy has `minimumChange` amount less than the `targetRatio`. - * And will pull funds from the strategy when it has `minimumChange` + * And will pull funds to the `targetRatio` when it has `minimumChange` * more than its `maxRatio`. */ -contract DebtAllocator { +contract DebtAllocator is Governance { + /// @notice An event emitted when the base fee provider is set. + event UpdatedBaseFeeProvider(address baseFeeProvider); + + /// @notice An event emitted when a keeper is added or removed. + event UpdateKeeper(address indexed keeper, bool allowed); + + /// @notice An event emitted when the max base fee is updated. + event UpdateMaxAcceptableBaseFee(uint256 newMaxAcceptableBaseFee); + /// @notice An event emitted when a strategies debt ratios are Updated. event UpdateStrategyDebtRatio( + address indexed vault, address indexed strategy, uint256 newTargetRatio, uint256 newMaxRatio, @@ -38,14 +53,21 @@ contract DebtAllocator { ); /// @notice An event emitted when a strategy is added or removed. - event StrategyChanged(address indexed strategy, Status status); + event StrategyChanged( + address indexed vault, + address indexed strategy, + Status status + ); + + /// @notice An event emitted when the minimum change is updated. + event UpdateMinimumChange(address indexed vault, uint256 newMinimumChange); + + /// @notice An even emitted when the paused status is updated. + event UpdatePaused(address indexed vault, bool indexed status); /// @notice An event emitted when the minimum time to wait is updated. event UpdateMinimumWait(uint256 newMinimumWait); - /// @notice An event emitted when the minimum change is updated. - event UpdateMinimumChange(uint256 newMinimumChange); - /// @notice An event emitted when a keeper is added or removed. event UpdateManager(address indexed manager, bool allowed); @@ -60,7 +82,7 @@ contract DebtAllocator { } /// @notice Struct for each strategies info. - struct Config { + struct StrategyConfig { // Flag to set when a strategy is added. bool added; // The ideal percent in Basis Points the strategy should have. @@ -77,10 +99,29 @@ contract DebtAllocator { uint120 open; } - /// @notice Make sure the caller is governance. - modifier onlyGovernance() { - _isGovernance(); - _; + /// @notice Struct to hold the vault's info. + struct VaultConfig { + // Optional flag to stop the triggers. + bool paused; + // The minimum amount denominated in asset that will + // need to be moved to trigger a debt update. + uint128 minimumChange; + // Total debt ratio currently allocated in basis points. + // Can't be more than 10_000. + uint16 totalDebtRatio; + } + + /// @notice Used during the `shouldUpdateDebt` to hold the data. + struct StrategyDebtInfo { + VaultConfig vaultConfig; + StrategyConfig strategyConfig; + uint256 vaultAssets; + uint256 targetDebt; + uint256 maxDebt; + uint256 currentIdle; + uint256 minIdle; + uint256 max; + uint256 toChange; } /// @notice Make sure the caller is governance or a manager. @@ -95,111 +136,97 @@ contract DebtAllocator { _; } - /// @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" - ); + require(managers[msg.sender] || msg.sender == governance, "!manager"); } /// @notice Check is one of the allowed keepers. function _isKeeper() internal view virtual { - require(DebtAllocatorFactory(factory).keepers(msg.sender), "!keeper"); + require(keepers[msg.sender], "!keeper"); } uint256 internal constant MAX_BPS = 10_000; - /// @notice Address to get permissioned roles from. - address public immutable factory; - - /// @notice Address of the vault this serves as allocator for. - address public vault; - /// @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 Total debt ratio currently allocated in basis points. - // Can't be more than 10_000. - uint256 public totalDebtRatio; + /// @notice Provider to read current block's base fee. + address public baseFeeProvider; /// @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; + /// @notice Mapping of addresses that are allowed to update debt ratios. mapping(address => bool) public managers; - /// @notice Mapping of strategy => its config. - mapping(address => Config) internal _configs; + mapping(address => VaultConfig) internal _vaultConfigs; - constructor() { - // Set the factory to retrieve roles from. Will be the same for all clones so can use immutable. - factory = msg.sender; + /// @notice Mapping of vault => strategy => its config. + mapping(address => mapping(address => StrategyConfig)) + internal _strategyConfigs; - // Don't allow for original version to be initialized. - vault = address(1); - } + constructor() Governance(msg.sender) {} /** - * @notice Initializes the debt allocator. - * @dev Should be called atomically after cloning. - * @param _vault Address of the vault this allocates debt for. - * @param _minimumChange The minimum in asset that must be moved. + * @notice Initialize the contract after being cloned. + * @dev Sets default values for the global variables. */ - function initialize(address _vault, uint256 _minimumChange) public virtual { - require(address(vault) == address(0), "!initialized"); + function initialize(address _governance) external { + require(governance == address(0), "initialized"); + require(_governance != address(0), "ZERO ADDRESS"); + + governance = _governance; + emit GovernanceTransferred(address(0), _governance); - // Set initial variables. - vault = _vault; - minimumChange = _minimumChange; + // Default max base fee to uint max. + maxAcceptableBaseFee = type(uint256).max; - // Default max loss on debt updates to 1 BP. + // Default to allow 1 BP loss. maxDebtUpdateLoss = 1; + + // Default minimum wait to 6 hours + minimumWait = 60 * 60 * 6; + + // Default to allow governance to be a keeper. + keepers[_governance] = true; + emit UpdateKeeper(_governance, true); } /** * @notice Debt update wrapper for the vault. - * @dev This can be used if a minimum time between debt updates - * is desired to be used for the trigger and to enforce a max loss. - * - * This contract must have the DEBT_MANAGER role assigned to them. - * - * The function signature matches the vault so no update to the - * call data is required. + * @dev This contract must have the DEBT_MANAGER role assigned to them. * - * This will also run checks on losses realized during debt + * This will also uses the `maxUpdateDebtLoss` during debt * updates to assure decreases did not realize profits outside * of the allowed range. */ function update_debt( + address _vault, address _strategy, uint256 _targetDebt ) public virtual onlyKeepers { - IVault _vault = IVault(vault); + IVault vault = IVault(_vault); // If going to 0 record full balance first. if (_targetDebt == 0) { - _vault.process_report(_strategy); + vault.process_report(_strategy); } // Update debt with the default max loss. - _vault.update_debt(_strategy, _targetDebt, maxDebtUpdateLoss); + vault.update_debt(_strategy, _targetDebt, maxDebtUpdateLoss); // Update the last time the strategies debt was updated. - _configs[_strategy].lastUpdate = uint96(block.timestamp); + _strategyConfigs[_vault][_strategy].lastUpdate = uint96( + block.timestamp + ); } /** @@ -207,109 +234,141 @@ contract DebtAllocator { * @dev This should be called by a keeper to decide if a strategies * debt should be updated and if so by how much. * + * @param _vault Address of the vault to update. * @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 _vault, address _strategy ) public view virtual returns (bool, bytes memory) { + // Store all local variables in a struct to avoid stack to deep + StrategyDebtInfo memory strategyDebtInfo; + + strategyDebtInfo.vaultConfig = getVaultConfig(_vault); + + // Don't do anything if paused. + if (strategyDebtInfo.vaultConfig.paused) + return (false, bytes("Paused")); + + // Check the base fee isn't too high. + if (!isCurrentBaseFeeAcceptable()) return (false, bytes("Base Fee")); + // Get the strategy specific debt config. - Config memory config = getConfig(_strategy); + strategyDebtInfo.strategyConfig = getStrategyConfig(_vault, _strategy); // Make sure the strategy has been added to the allocator. - if (!config.added) return (false, bytes("!added")); + if (!strategyDebtInfo.strategyConfig.added) + return (false, bytes("!added")); - // Check the base fee isn't too high. - if (!DebtAllocatorFactory(factory).isCurrentBaseFeeAcceptable()) { - return (false, bytes("Base Fee")); + if ( + block.timestamp - strategyDebtInfo.strategyConfig.lastUpdate <= + minimumWait + ) { + return (false, bytes("min wait")); } - // Cache the vault variable. - IVault _vault = IVault(vault); // Retrieve the strategy specific parameters. - IVault.StrategyParams memory params = _vault.strategies(_strategy); + IVault.StrategyParams memory params = IVault(_vault).strategies( + _strategy + ); // Make sure its an active strategy. require(params.activation != 0, "!active"); - if (block.timestamp - config.lastUpdate <= minimumWait) { - return (false, bytes("min wait")); - } - - uint256 vaultAssets = _vault.totalAssets(); + strategyDebtInfo.vaultAssets = IVault(_vault).totalAssets(); // Get the target debt for the strategy based on vault assets. - uint256 targetDebt = Math.min( - (vaultAssets * config.targetRatio) / MAX_BPS, + strategyDebtInfo.targetDebt = Math.min( + (strategyDebtInfo.vaultAssets * + strategyDebtInfo.strategyConfig.targetRatio) / MAX_BPS, // Make sure it is not more than the max allowed. params.max_debt ); // Get the max debt we would want the strategy to have. - uint256 maxDebt = Math.min( - (vaultAssets * config.maxRatio) / MAX_BPS, + strategyDebtInfo.maxDebt = Math.min( + (strategyDebtInfo.vaultAssets * + strategyDebtInfo.strategyConfig.maxRatio) / MAX_BPS, // Make sure it is not more than the max allowed. params.max_debt ); // If we need to add more. - if (targetDebt > params.current_debt) { - uint256 currentIdle = _vault.totalIdle(); - uint256 minIdle = _vault.minimum_total_idle(); + if (strategyDebtInfo.targetDebt > params.current_debt) { + strategyDebtInfo.currentIdle = IVault(_vault).totalIdle(); + strategyDebtInfo.minIdle = IVault(_vault).minimum_total_idle(); + strategyDebtInfo.max = IVault(_strategy).maxDeposit(_vault); // We can't add more than the available idle. - if (minIdle >= currentIdle) { + if (strategyDebtInfo.minIdle >= strategyDebtInfo.currentIdle) { return (false, bytes("No Idle")); } // Add up to the max if possible - uint256 toAdd = Math.min( - maxDebt - params.current_debt, + strategyDebtInfo.toChange = Math.min( + strategyDebtInfo.maxDebt - params.current_debt, // Can't take more than is available. Math.min( - currentIdle - minIdle, - IVault(_strategy).maxDeposit(vault) + strategyDebtInfo.currentIdle - strategyDebtInfo.minIdle, + strategyDebtInfo.max ) ); // If the amount to add is over our threshold. - if (toAdd > minimumChange) { + if ( + strategyDebtInfo.toChange > + strategyDebtInfo.vaultConfig.minimumChange + ) { // Return true and the calldata. return ( true, - abi.encodeWithSignature( - "update_debt(address,uint256)", - _strategy, - params.current_debt + toAdd + abi.encodeCall( + this.update_debt, + ( + _vault, + _strategy, + params.current_debt + strategyDebtInfo.toChange + ) ) ); } // If current debt is greater than our max. - } else if (maxDebt < params.current_debt) { - uint256 toPull = params.current_debt - targetDebt; + } else if (strategyDebtInfo.maxDebt < params.current_debt) { + strategyDebtInfo.toChange = + params.current_debt - + strategyDebtInfo.targetDebt; + + strategyDebtInfo.currentIdle = IVault(_vault).totalIdle(); + strategyDebtInfo.minIdle = IVault(_vault).minimum_total_idle(); + strategyDebtInfo.max = IVault(_strategy).convertToAssets( + IVault(_strategy).maxRedeem(_vault) + ); - uint256 currentIdle = _vault.totalIdle(); - uint256 minIdle = _vault.minimum_total_idle(); - if (minIdle > currentIdle) { + if (strategyDebtInfo.minIdle > strategyDebtInfo.currentIdle) { // Pull at least the amount needed for minIdle. - toPull = Math.max(toPull, minIdle - currentIdle); + strategyDebtInfo.toChange = Math.max( + strategyDebtInfo.toChange, + strategyDebtInfo.minIdle - strategyDebtInfo.currentIdle + ); } // Find out by how much. Aim for the target. - toPull = Math.min( - toPull, + strategyDebtInfo.toChange = Math.min( + strategyDebtInfo.toChange, // Account for the current liquidity constraints. // Use max redeem to match vault logic. - IVault(_strategy).convertToAssets( - IVault(_strategy).maxRedeem(address(_vault)) - ) + strategyDebtInfo.max ); // Check if it's over the threshold. - if (toPull > minimumChange) { + if ( + strategyDebtInfo.toChange > + strategyDebtInfo.vaultConfig.minimumChange + ) { // Can't lower debt if there are unrealised losses. if ( - _vault.assess_share_of_unrealised_losses( + IVault(_vault).assess_share_of_unrealised_losses( _strategy, params.current_debt ) != 0 @@ -320,10 +379,13 @@ contract DebtAllocator { // If so return true and the calldata. return ( true, - abi.encodeWithSignature( - "update_debt(address,uint256)", - _strategy, - params.current_debt - toPull + abi.encodeCall( + this.update_debt, + ( + _vault, + _strategy, + params.current_debt - strategyDebtInfo.toChange + ) ) ); } @@ -333,6 +395,10 @@ contract DebtAllocator { return (false, bytes("Below Min")); } + /*////////////////////////////////////////////////////////////// + STRATEGY MANAGEMENT + //////////////////////////////////////////////////////////////*/ + /** * @notice Increase a strategies target debt ratio. * @dev `setStrategyDebtRatio` functions will do all needed checks. @@ -340,11 +406,13 @@ contract DebtAllocator { * @param _increase The amount in Basis Points to increase it. */ function increaseStrategyDebtRatio( + address _vault, address _strategy, uint256 _increase ) external virtual { - uint256 _currentRatio = getConfig(_strategy).targetRatio; - setStrategyDebtRatio(_strategy, _currentRatio + _increase); + uint256 _currentRatio = getStrategyConfig(_vault, _strategy) + .targetRatio; + setStrategyDebtRatio(_vault, _strategy, _currentRatio + _increase); } /** @@ -353,11 +421,13 @@ contract DebtAllocator { * @param _decrease The amount in Basis Points to decrease it. */ function decreaseStrategyDebtRatio( + address _vault, address _strategy, uint256 _decrease ) external virtual { - uint256 _currentRatio = getConfig(_strategy).targetRatio; - setStrategyDebtRatio(_strategy, _currentRatio - _decrease); + uint256 _currentRatio = getStrategyConfig(_vault, _strategy) + .targetRatio; + setStrategyDebtRatio(_vault, _strategy, _currentRatio - _decrease); } /** @@ -368,11 +438,12 @@ contract DebtAllocator { * @param _targetRatio Amount in Basis points to allocate. */ function setStrategyDebtRatio( + address _vault, address _strategy, uint256 _targetRatio ) public virtual { uint256 maxRatio = Math.min((_targetRatio * 12_000) / MAX_BPS, MAX_BPS); - setStrategyDebtRatio(_strategy, _targetRatio, maxRatio); + setStrategyDebtRatio(_vault, _strategy, _targetRatio, maxRatio); } /** @@ -380,48 +451,55 @@ contract DebtAllocator { * @dev A `minimumChange` for that strategy must be set first. * This is to prevent debt from being updated too frequently. * + * @param _vault Address of the vault * @param _strategy Address of the strategy to set. * @param _targetRatio Amount in Basis points to allocate. * @param _maxRatio Max ratio to give on debt increases. */ function setStrategyDebtRatio( + address _vault, address _strategy, uint256 _targetRatio, uint256 _maxRatio ) public virtual onlyManagers { + VaultConfig storage vaultConfig = _vaultConfigs[_vault]; // Make sure a minimumChange has been set. - require(minimumChange != 0, "!minimum"); + require(vaultConfig.minimumChange != 0, "!minimum"); // Cannot be more than 100%. require(_maxRatio <= MAX_BPS, "max too high"); // Max cannot be lower than the target. require(_maxRatio >= _targetRatio, "max ratio"); // Get the current config. - Config memory config = getConfig(_strategy); + StrategyConfig memory strategyConfig = getStrategyConfig( + _vault, + _strategy + ); // Set added flag if not set yet. - if (!config.added) { - config.added = true; - emit StrategyChanged(_strategy, Status.ADDED); + if (!strategyConfig.added) { + strategyConfig.added = true; + emit StrategyChanged(_vault, _strategy, Status.ADDED); } // Get what will be the new total debt ratio. - uint256 newTotalDebtRatio = totalDebtRatio - - config.targetRatio + + uint256 newTotalDebtRatio = vaultConfig.totalDebtRatio - + strategyConfig.targetRatio + _targetRatio; // Make sure it is under 100% allocated require(newTotalDebtRatio <= MAX_BPS, "ratio too high"); // Update local config. - config.targetRatio = uint16(_targetRatio); - config.maxRatio = uint16(_maxRatio); + strategyConfig.targetRatio = uint16(_targetRatio); + strategyConfig.maxRatio = uint16(_maxRatio); // Write to storage. - _configs[_strategy] = config; - totalDebtRatio = newTotalDebtRatio; + _strategyConfigs[_vault][_strategy] = strategyConfig; + vaultConfig.totalDebtRatio = uint16(newTotalDebtRatio); emit UpdateStrategyDebtRatio( + _vault, _strategy, _targetRatio, _maxRatio, @@ -432,59 +510,80 @@ contract DebtAllocator { /** * @notice Remove a strategy from this debt allocator. * @dev Will delete the full config for the strategy + * @param _vault Address of the vault * @param _strategy Address of the address ro remove. */ - function removeStrategy(address _strategy) external virtual onlyManagers { - Config memory config = getConfig(_strategy); - require(config.added, "!added"); + function removeStrategy( + address _vault, + address _strategy + ) external virtual onlyManagers { + StrategyConfig memory strategyConfig = getStrategyConfig( + _vault, + _strategy + ); + require(strategyConfig.added, "!added"); - uint256 target = config.targetRatio; + uint256 target = strategyConfig.targetRatio; // Remove any debt ratio the strategy holds. if (target != 0) { - totalDebtRatio -= target; - emit UpdateStrategyDebtRatio(_strategy, 0, 0, totalDebtRatio); + uint256 newRatio = _vaultConfigs[_vault].totalDebtRatio - target; + _vaultConfigs[_vault].totalDebtRatio = uint16(newRatio); + emit UpdateStrategyDebtRatio(_vault, _strategy, 0, 0, newRatio); } // Remove the full config including the `added` flag. - delete _configs[_strategy]; + delete _strategyConfigs[_vault][_strategy]; // Emit Event. - emit StrategyChanged(_strategy, Status.REMOVED); + emit StrategyChanged(_vault, _strategy, Status.REMOVED); } + /*////////////////////////////////////////////////////////////// + VAULT MANAGEMENT + //////////////////////////////////////////////////////////////*/ + /** * @notice Set the minimum change variable for a strategy. * @dev This is the minimum amount of debt to be * added or pulled for it to trigger an update. * + * @param _vault Address of the vault * @param _minimumChange The new minimum to set for the strategy. */ function setMinimumChange( + address _vault, uint256 _minimumChange ) external virtual onlyGovernance { require(_minimumChange > 0, "zero"); + // Make sure it fits in the slot size. + require(_minimumChange < type(uint128).max, "too high"); + // Set the new minimum. - minimumChange = _minimumChange; + _vaultConfigs[_vault].minimumChange = uint128(_minimumChange); - emit UpdateMinimumChange(_minimumChange); + emit UpdateMinimumChange(_vault, _minimumChange); } /** - * @notice Set the max loss in Basis points to allow on debt updates. - * @dev Withdrawing during debt updates use {redeem} which allows for 100% loss. - * This can be used to assure a loss is not realized on redeem outside the tolerance. - * @param _maxDebtUpdateLoss The max loss to accept on debt updates. + * @notice Allows governance to pause the triggers. + * @param _vault Address of the vault + * @param _status Status to set the `paused` bool to. */ - function setMaxDebtUpdateLoss( - uint256 _maxDebtUpdateLoss + function setPaused( + address _vault, + bool _status ) external virtual onlyGovernance { - require(_maxDebtUpdateLoss <= MAX_BPS, "higher than max"); - maxDebtUpdateLoss = _maxDebtUpdateLoss; + require(_status != _vaultConfigs[_vault].paused, "already set"); + _vaultConfigs[_vault].paused = _status; - emit UpdateMaxDebtUpdateLoss(_maxDebtUpdateLoss); + emit UpdatePaused(_vault, _status); } + /*////////////////////////////////////////////////////////////// + ALLOCATOR MANAGEMENT + //////////////////////////////////////////////////////////////*/ + /** * @notice Set the minimum time to wait before re-updating a strategies debt. * @dev This is only enforced per strategy. @@ -512,37 +611,157 @@ contract DebtAllocator { emit UpdateManager(_address, _allowed); } + /** + * @notice Set the max loss in Basis points to allow on debt updates. + * @dev Withdrawing during debt updates use {redeem} which allows for 100% loss. + * This can be used to assure a loss is not realized on redeem outside the tolerance. + * @param _maxDebtUpdateLoss The max loss to accept on debt updates. + */ + function setMaxDebtUpdateLoss( + uint256 _maxDebtUpdateLoss + ) external virtual onlyGovernance { + require(_maxDebtUpdateLoss <= MAX_BPS, "higher than max"); + maxDebtUpdateLoss = _maxDebtUpdateLoss; + + emit UpdateMaxDebtUpdateLoss(_maxDebtUpdateLoss); + } + + /** + * @notice + * Used to set our baseFeeProvider, which checks the network's current base + * fee price to determine whether it is an optimal time to harvest or tend. + * + * This may only be called by governance. + * @param _baseFeeProvider Address of our baseFeeProvider + */ + function setBaseFeeOracle( + address _baseFeeProvider + ) external virtual onlyGovernance { + baseFeeProvider = _baseFeeProvider; + + emit UpdatedBaseFeeProvider(_baseFeeProvider); + } + + /** + * @notice Set the max acceptable base fee. + * @dev This defaults to max uint256 and will need to + * 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 Get a strategies full config. * @dev Used for customizations by inheriting the contract. + * @param _vault Address of the vault * @param _strategy Address of the strategy. * @return The strategies current Config. */ - function getConfig( + function getStrategyConfig( + address _vault, address _strategy - ) public view virtual returns (Config memory) { - return _configs[_strategy]; + ) public view virtual returns (StrategyConfig memory) { + return _strategyConfigs[_vault][_strategy]; + } + + /** + * @notice Get a vaults full config. + * @dev Used for customizations by inheriting the contract. + * @param _vault Address of the vault. + * @return The vaults current Config. + */ + function getVaultConfig( + address _vault + ) public view virtual returns (VaultConfig memory) { + return _vaultConfigs[_vault]; + } + + /** + * @notice Get a vaults current total debt. + * @param _vault Address of the vault + */ + function totalDebtRatio( + address _vault + ) external view virtual returns (uint256) { + return getVaultConfig(_vault).totalDebtRatio; + } + + /** + * @notice Get a vaults minimum change required. + * @param _vault Address of the vault + */ + function minimumChange( + address _vault + ) external view virtual returns (uint256) { + return getVaultConfig(_vault).minimumChange; + } + + /** + * @notice Get the paused status of a vault + * @param _vault Address of the vault + */ + function isPaused(address _vault) public view virtual returns (bool) { + return getVaultConfig(_vault).paused; } /** * @notice Get a strategies target debt ratio. + * @param _vault Address of the vault * @param _strategy Address of the strategy. * @return The strategies current targetRatio. */ function getStrategyTargetRatio( + address _vault, address _strategy ) external view virtual returns (uint256) { - return getConfig(_strategy).targetRatio; + return getStrategyConfig(_vault, _strategy).targetRatio; } /** * @notice Get a strategies max debt ratio. + * @param _vault Address of the vault * @param _strategy Address of the strategy. * @return The strategies current maxRatio. */ function getStrategyMaxRatio( + address _vault, address _strategy ) external view virtual returns (uint256) { - return getConfig(_strategy).maxRatio; + return getStrategyConfig(_vault, _strategy).maxRatio; + } + + /** + * @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() public view virtual returns (bool) { + address _baseFeeProvider = baseFeeProvider; + if (_baseFeeProvider == address(0)) return true; + return + maxAcceptableBaseFee >= IBaseFee(_baseFeeProvider).basefee_global(); } } diff --git a/contracts/debtAllocators/DebtAllocatorFactory.sol b/contracts/debtAllocators/DebtAllocatorFactory.sol index 5841483..87e4cdc 100644 --- a/contracts/debtAllocators/DebtAllocatorFactory.sol +++ b/contracts/debtAllocators/DebtAllocatorFactory.sol @@ -3,146 +3,40 @@ pragma solidity >=0.8.18; import {DebtAllocator} from "./DebtAllocator.sol"; import {Clonable} from "@periphery/utils/Clonable.sol"; -import {Governance} from "@periphery/utils/Governance.sol"; - -interface IBaseFee { - function basefee_global() external view returns (uint256); -} /** - * @title YearnV3 Debt Allocator Factory + * @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 the base fee provider is set. - event UpdatedBaseFeeProvider(address baseFeeProvider); - - /// @notice An event emitted when a keeper is added or removed. - event UpdateKeeper(address indexed keeper, bool allowed); - - /// @notice An event emitted when the max base fee is updated. - event UpdateMaxAcceptableBaseFee(uint256 newMaxAcceptableBaseFee); - +contract DebtAllocatorFactory is Clonable { /// @notice An event emitted when a new debt allocator is added or deployed. - event NewDebtAllocator(address indexed allocator, address indexed vault); - - /// @notice Provider to read current block's base fee. - address public baseFeeProvider; - - /// @notice Max the chains base fee can be during debt update. - // Will default to max uint256 and need to be set to be used. - uint256 public maxAcceptableBaseFee; - - /// @notice Mapping of addresses that are allowed to update debt. - mapping(address => bool) public keepers; + event NewDebtAllocator( + address indexed allocator, + address indexed governance + ); - constructor(address _governance) Governance(_governance) { + constructor() { // Deploy a dummy allocator as the original. original = address(new DebtAllocator()); - - // 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. + * @param _governance Address to control the debt allocator. * @return newAllocator Address of the new debt allocator */ function newDebtAllocator( - address _vault, - uint256 _minimumChange + address _governance ) public virtual returns (address newAllocator) { // Clone new allocator off the original. newAllocator = _clone(); // Initialize the new allocator. - DebtAllocator(newAllocator).initialize(_vault, _minimumChange); + DebtAllocator(newAllocator).initialize(_governance); // Emit event. - emit NewDebtAllocator(newAllocator, _vault); - } - - /** - * @notice - * Used to set our baseFeeProvider, which checks the network's current base - * fee price to determine whether it is an optimal time to harvest or tend. - * - * This may only be called by governance. - * @param _baseFeeProvider Address of our baseFeeProvider - */ - function setBaseFeeOracle( - address _baseFeeProvider - ) external virtual onlyGovernance { - baseFeeProvider = _baseFeeProvider; - - emit UpdatedBaseFeeProvider(_baseFeeProvider); - } - - /** - * @notice Set the max acceptable base fee. - * @dev This defaults to max uint256 and will need to - * 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) { - address _baseFeeProvider = baseFeeProvider; - if (_baseFeeProvider == address(0)) return true; - return - maxAcceptableBaseFee >= IBaseFee(_baseFeeProvider).basefee_global(); + emit NewDebtAllocator(newAllocator, _governance); } } diff --git a/contracts/debtAllocators/DebtOptimizerApplicator.sol b/contracts/debtAllocators/DebtOptimizerApplicator.sol index 85eccff..f6d41ea 100644 --- a/contracts/debtAllocators/DebtOptimizerApplicator.sol +++ b/contracts/debtAllocators/DebtOptimizerApplicator.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GNU AGPLv3 pragma solidity >=0.8.18; -import {DebtAllocator, DebtAllocatorFactory} from "./DebtAllocator.sol"; +import {DebtAllocator} from "./DebtAllocator.sol"; contract DebtOptimizerApplicator { /// @notice An event emitted when a keeper is added or removed. @@ -29,8 +29,7 @@ contract DebtOptimizerApplicator { /// @notice Check the Factories governance address. function _isGovernance() internal view virtual { require( - msg.sender == - DebtAllocatorFactory(debtAllocatorFactory).governance(), + msg.sender == DebtAllocator(debtAllocator).governance(), "!governance" ); } @@ -39,20 +38,19 @@ contract DebtOptimizerApplicator { function _isManager() internal view virtual { require( managers[msg.sender] || - msg.sender == - DebtAllocatorFactory(debtAllocatorFactory).governance(), + msg.sender == DebtAllocator(debtAllocator).governance(), "!manager" ); } /// @notice The address of the debt allocator factory to use for some role checks. - address public immutable debtAllocatorFactory; + address public immutable debtAllocator; /// @notice Mapping of addresses that are allowed to update debt ratios. mapping(address => bool) public managers; - constructor(address _debtAllocatorFactory) { - debtAllocatorFactory = _debtAllocatorFactory; + constructor(address _debtAllocator) { + debtAllocator = _debtAllocator; } /** @@ -70,17 +68,19 @@ contract DebtOptimizerApplicator { } function setStrategyDebtRatios( - address _debtAllocator, + address _vault, StrategyDebtRatio[] calldata _strategyDebtRatios ) public onlyManagers { for (uint8 i; i < _strategyDebtRatios.length; ++i) { if (_strategyDebtRatios[i].maxRatio == 0) { - DebtAllocator(_debtAllocator).setStrategyDebtRatio( + DebtAllocator(debtAllocator).setStrategyDebtRatio( + _vault, _strategyDebtRatios[i].strategy, _strategyDebtRatios[i].targetRatio ); } else { - DebtAllocator(_debtAllocator).setStrategyDebtRatio( + DebtAllocator(debtAllocator).setStrategyDebtRatio( + _vault, _strategyDebtRatios[i].strategy, _strategyDebtRatios[i].targetRatio, _strategyDebtRatios[i].maxRatio diff --git a/tests/conftest.py b/tests/conftest.py index 16ed48f..c3db590 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -387,9 +387,9 @@ def address_provider(deploy_address_provider): @pytest.fixture(scope="session") -def deploy_debt_allocator_factory(project, daddy, brain): +def deploy_debt_allocator_factory(project, daddy): def deploy_debt_allocator_factory(gov=daddy): - debt_allocator_factory = gov.deploy(project.DebtAllocatorFactory, brain) + debt_allocator_factory = gov.deploy(project.DebtAllocatorFactory) return debt_allocator_factory @@ -404,8 +404,8 @@ def debt_allocator_factory(deploy_debt_allocator_factory): @pytest.fixture(scope="session") -def debt_allocator(debt_allocator_factory, project, vault, daddy): - tx = debt_allocator_factory.newDebtAllocator(vault, sender=daddy) +def debt_allocator(debt_allocator_factory, project, brain, daddy): + tx = debt_allocator_factory.newDebtAllocator(brain, sender=daddy) event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] @@ -415,9 +415,9 @@ def debt_allocator(debt_allocator_factory, project, vault, daddy): @pytest.fixture(scope="session") -def debt_optimizer_applicator(debt_allocator_factory, project, brain): +def debt_optimizer_applicator(debt_allocator, project, brain): - yield brain.deploy(project.DebtOptimizerApplicator, debt_allocator_factory.address) + yield brain.deploy(project.DebtOptimizerApplicator, debt_allocator.address) @pytest.fixture(scope="session") diff --git a/tests/debtAllocators/test_debt_allocator.py b/tests/debtAllocators/test_debt_allocator.py index e074e12..9bd9f6d 100644 --- a/tests/debtAllocators/test_debt_allocator.py +++ b/tests/debtAllocators/test_debt_allocator.py @@ -4,51 +4,50 @@ 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) + tx = debt_allocator_factory.newDebtAllocator(brain, sender=user) events = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator)) assert len(events) == 1 - assert events[0].vault == vault.address + assert events[0].governance == brain debt_allocator = project.DebtAllocator.at(events[0].allocator) + assert debt_allocator.governance() == brain + assert debt_allocator.keepers(brain) == True + assert debt_allocator.maxAcceptableBaseFee() == MAX_INT + assert debt_allocator.minimumWait() == 60 * 60 * 6 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) == (False, 0, 0, 0, 0) - assert debt_allocator.totalDebtRatio() == 0 - (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + assert debt_allocator.getStrategyConfig(vault, strategy) == (False, 0, 0, 0, 0) + assert debt_allocator.getVaultConfig(vault) == (False, 0, 0) + assert debt_allocator.totalDebtRatio(vault) == 0 + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault.address, strategy.address) assert bool == False assert bytes == ("!added").encode("utf-8") -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 +def test_set_keepers(debt_allocator, brain, user): + assert debt_allocator.keepers(brain) == True + assert debt_allocator.keepers(user) == False with ape.reverts("!governance"): - debt_allocator_factory.setKeeper(user, True, sender=user) + debt_allocator.setKeeper(user, True, sender=user) - tx = debt_allocator_factory.setKeeper(user, True, sender=brain) + tx = debt_allocator.setKeeper(user, True, sender=brain) - event = list(tx.decode_logs(debt_allocator_factory.UpdateKeeper))[0] + event = list(tx.decode_logs(debt_allocator.UpdateKeeper))[0] assert event.keeper == user assert event.allowed == True - assert debt_allocator_factory.keepers(user) == True + assert debt_allocator.keepers(user) == True - tx = debt_allocator_factory.setKeeper(brain, False, sender=brain) + tx = debt_allocator.setKeeper(brain, False, sender=brain) - event = list(tx.decode_logs(debt_allocator_factory.UpdateKeeper))[0] + event = list(tx.decode_logs(debt_allocator.UpdateKeeper))[0] assert event.keeper == brain assert event.allowed == False - assert debt_allocator_factory.keepers(brain) == False + assert debt_allocator.keepers(brain) == False def test_set_managers(debt_allocator, brain, user): @@ -75,29 +74,30 @@ def test_set_managers(debt_allocator, brain, user): assert debt_allocator.managers(user) == False -def test_set_minimum_change(debt_allocator, brain, strategy, user): - assert debt_allocator.getConfig(strategy) == (False, 0, 0, 0, 0) - assert debt_allocator.minimumChange() == 0 +def test_set_minimum_change(debt_allocator, vault, brain, strategy, user): + assert debt_allocator.getStrategyConfig(vault, strategy) == (False, 0, 0, 0, 0) + assert debt_allocator.minimumChange(vault) == 0 minimum = int(1e17) with ape.reverts("!governance"): - debt_allocator.setMinimumChange(minimum, sender=user) + debt_allocator.setMinimumChange(vault, minimum, sender=user) with ape.reverts("zero"): - debt_allocator.setMinimumChange(0, sender=brain) + debt_allocator.setMinimumChange(vault, 0, sender=brain) - tx = debt_allocator.setMinimumChange(minimum, sender=brain) + tx = debt_allocator.setMinimumChange(vault, minimum, sender=brain) event = list(tx.decode_logs(debt_allocator.UpdateMinimumChange))[0] assert event.newMinimumChange == minimum - assert debt_allocator.minimumChange() == minimum + assert event.vault == vault + assert debt_allocator.minimumChange(vault) == minimum -def test_set_minimum_wait(debt_allocator, brain, strategy, user): - assert debt_allocator.getConfig(strategy) == (False, 0, 0, 0, 0) - assert debt_allocator.minimumWait() == 0 +def test_set_minimum_wait(debt_allocator, brain, vault, strategy, user): + assert debt_allocator.getStrategyConfig(vault, strategy) == (False, 0, 0, 0, 0) + assert debt_allocator.minimumWait() == 60 * 60 * 6 minimum = int(1e17) @@ -112,8 +112,8 @@ def test_set_minimum_wait(debt_allocator, brain, strategy, user): assert debt_allocator.minimumWait() == minimum -def test_set_max_debt_update_loss(debt_allocator, brain, strategy, user): - assert debt_allocator.getConfig(strategy) == (False, 0, 0, 0, 0) +def test_set_max_debt_update_loss(debt_allocator, brain, vault, strategy, user): + assert debt_allocator.getStrategyConfig(vault, strategy) == (False, 0, 0, 0, 0) assert debt_allocator.maxDebtUpdateLoss() == 1 max = int(69) @@ -135,32 +135,37 @@ def test_set_max_debt_update_loss(debt_allocator, brain, strategy, user): def test_set_ratios( debt_allocator, brain, daddy, vault, strategy, create_strategy, user ): - assert debt_allocator.getConfig(strategy) == (False, 0, 0, 0, 0) + assert debt_allocator.getStrategyConfig(vault, strategy) == (False, 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) + debt_allocator.setStrategyDebtRatio(vault, 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.setStrategyDebtRatio(vault, strategy, target, max, sender=brain) - debt_allocator.setMinimumChange(minimum, sender=brain) + debt_allocator.setMinimumChange(vault, minimum, sender=brain) with ape.reverts("max too high"): - debt_allocator.setStrategyDebtRatio(strategy, target, int(10_001), sender=brain) + debt_allocator.setStrategyDebtRatio( + vault, strategy, target, int(10_001), sender=brain + ) with ape.reverts("max ratio"): - debt_allocator.setStrategyDebtRatio(strategy, int(max + 1), max, sender=brain) + debt_allocator.setStrategyDebtRatio( + vault, strategy, int(max + 1), max, sender=brain + ) - tx = debt_allocator.setStrategyDebtRatio(strategy, target, max, sender=brain) + tx = debt_allocator.setStrategyDebtRatio(vault, strategy, target, max, sender=brain) event = list(tx.decode_logs(debt_allocator.StrategyChanged))[0] + assert event.vault == vault assert event.strategy == strategy assert event.status == 1 @@ -169,18 +174,24 @@ def test_set_ratios( assert event.newTargetRatio == target assert event.newMaxRatio == max assert event.newTotalDebtRatio == target - assert debt_allocator.totalDebtRatio() == target - assert debt_allocator.getConfig(strategy) == (True, target, max, 0, 0) + assert debt_allocator.totalDebtRatio(vault) == target + assert debt_allocator.getStrategyConfig(vault, strategy) == ( + True, + 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 + vault, new_strategy, int(10_000), int(10_000), sender=brain ) target = int(8_000) - tx = debt_allocator.setStrategyDebtRatio(strategy, target, sender=brain) + tx = debt_allocator.setStrategyDebtRatio(vault, strategy, target, sender=brain) event = list(tx.decode_logs(debt_allocator.UpdateStrategyDebtRatio))[0] @@ -188,13 +199,19 @@ def test_set_ratios( assert event.newMaxRatio == target * 1.2 assert event.newTotalDebtRatio == target assert debt_allocator.totalDebtRatio() == target - assert debt_allocator.getConfig(strategy) == (True, target, target * 1.2, 0, 0) + assert debt_allocator.getStrategyConfig(vault, strategy) == ( + True, + 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) == (False, 0, 0, 0, 0) + assert debt_allocator.getStrategyConfig(vault, strategy) == (False, 0, 0, 0, 0) minimum = int(1e17) target = int(5_000) @@ -202,120 +219,166 @@ def test_increase_debt_ratio( max = target * 1.2 with ape.reverts("!manager"): - debt_allocator.increaseStrategyDebtRatio(strategy, increase, sender=user) + debt_allocator.increaseStrategyDebtRatio(vault, strategy, increase, sender=user) vault.add_strategy(strategy.address, sender=daddy) with ape.reverts("!minimum"): - debt_allocator.increaseStrategyDebtRatio(strategy, increase, sender=brain) + debt_allocator.increaseStrategyDebtRatio( + vault, strategy, increase, sender=brain + ) - debt_allocator.setMinimumChange(minimum, sender=brain) + debt_allocator.setMinimumChange(vault, minimum, sender=brain) - tx = debt_allocator.increaseStrategyDebtRatio(strategy, increase, sender=brain) + tx = debt_allocator.increaseStrategyDebtRatio( + vault, 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) == (True, target, max, 0, 0) + assert debt_allocator.totalDebtRatio(vault) == target + assert debt_allocator.getStrategyConfig(vault, strategy) == ( + True, + 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) + debt_allocator.increaseStrategyDebtRatio( + vault, 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) + tx = debt_allocator.increaseStrategyDebtRatio( + vault, 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) == (True, target, max, 0, 0) + assert debt_allocator.totalDebtRatio(vault) == target + assert debt_allocator.getStrategyConfig(vault, strategy) == ( + True, + target, + max, + 0, + 0, + ) target = int(10_000) max = int(10_000) increase = int(2_000) - tx = debt_allocator.increaseStrategyDebtRatio(strategy, increase, sender=brain) + tx = debt_allocator.increaseStrategyDebtRatio( + vault, 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) == (True, target, max, 0, 0) + assert debt_allocator.totalDebtRatio(vault) == target + assert debt_allocator.getStrategyConfig(vault, strategy) == ( + True, + target, + max, + 0, + 0, + ) def test_decrease_debt_ratio( debt_allocator, brain, vault, strategy, daddy, create_strategy, user ): - assert debt_allocator.getConfig(strategy) == (False, 0, 0, 0, 0) + assert debt_allocator.getStrategyConfig(vault, strategy) == (False, 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) + debt_allocator.setMinimumChange(vault, minimum, sender=brain) # Underflow with ape.reverts(): - debt_allocator.decreaseStrategyDebtRatio(strategy, target, sender=brain) + debt_allocator.decreaseStrategyDebtRatio( + vault.address, strategy.address, target, sender=brain + ) # Add the target - tx = debt_allocator.increaseStrategyDebtRatio(strategy, target, sender=brain) + tx = debt_allocator.increaseStrategyDebtRatio(vault, 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) == (True, target, max, 0, 0) + assert debt_allocator.totalDebtRatio(vault) == target + assert debt_allocator.getStrategyConfig(vault, strategy) == ( + True, + 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) + debt_allocator.decreaseStrategyDebtRatio(vault, strategy, decrease, sender=user) - tx = debt_allocator.decreaseStrategyDebtRatio(strategy, decrease, sender=brain) + tx = debt_allocator.decreaseStrategyDebtRatio( + vault, 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) == (True, target, max, 0, 0) + assert debt_allocator.totalDebtRatio(vault) == target + assert debt_allocator.getStrategyConfig(vault, strategy) == ( + True, + target, + max, + 0, + 0, + ) target = int(0) max = int(0) decrease = int(2_000) - tx = debt_allocator.decreaseStrategyDebtRatio(strategy, decrease, sender=brain) + tx = debt_allocator.decreaseStrategyDebtRatio( + vault, 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) == (True, 0, 0, 0, 0) + assert debt_allocator.totalDebtRatio(vault) == target + assert debt_allocator.getStrategyConfig(vault, strategy) == (True, 0, 0, 0, 0) def test_remove_strategy( debt_allocator, brain, vault, strategy, daddy, user, deposit_into_vault, amount ): - assert debt_allocator.getConfig(strategy) == (False, 0, 0, 0, 0) + assert debt_allocator.getStrategyConfig(vault, strategy) == (False, 0, 0, 0, 0) minimum = int(1) max = int(6_000) @@ -323,9 +386,9 @@ def test_remove_strategy( vault.add_strategy(strategy.address, sender=daddy) - debt_allocator.setMinimumChange(minimum, sender=brain) + debt_allocator.setMinimumChange(vault, minimum, sender=brain) - tx = debt_allocator.setStrategyDebtRatio(strategy, target, max, sender=brain) + tx = debt_allocator.setStrategyDebtRatio(vault, strategy, target, max, sender=brain) event = list(tx.decode_logs(debt_allocator.StrategyChanged))[0] @@ -337,37 +400,50 @@ def test_remove_strategy( assert event.newTargetRatio == target assert event.newMaxRatio == max assert event.newTotalDebtRatio == target - assert debt_allocator.totalDebtRatio() == target - assert debt_allocator.getConfig(strategy) == (True, target, max, 0, 0) + assert debt_allocator.totalDebtRatio(vault) == target + assert debt_allocator.getStrategyConfig(vault, strategy) == ( + True, + target, + max, + 0, + 0, + ) deposit_into_vault(vault, amount) vault.update_max_debt_for_strategy(strategy, MAX_INT, sender=daddy) - print(debt_allocator.shouldUpdateDebt(strategy)) - assert debt_allocator.shouldUpdateDebt(strategy)[0] == True + print(debt_allocator.shouldUpdateDebt(vault, strategy)) + assert debt_allocator.shouldUpdateDebt(vault, strategy)[0] == True with ape.reverts("!manager"): - debt_allocator.removeStrategy(strategy, sender=user) + debt_allocator.removeStrategy(vault, strategy, sender=user) - tx = debt_allocator.removeStrategy(strategy, sender=brain) + tx = debt_allocator.removeStrategy(vault, strategy, sender=brain) event = list(tx.decode_logs(debt_allocator.StrategyChanged)) assert len(event) == 1 assert event[0].strategy == strategy assert event[0].status == 2 - assert debt_allocator.totalDebtRatio() == 0 - assert debt_allocator.getConfig(strategy) == (False, 0, 0, 0, 0) - assert debt_allocator.shouldUpdateDebt(strategy)[0] == False + assert debt_allocator.totalDebtRatio(vault) == 0 + assert debt_allocator.getStrategyConfig(vault, strategy) == (False, 0, 0, 0, 0) + assert debt_allocator.shouldUpdateDebt(vault, strategy)[0] == False def test_should_update_debt( debt_allocator, vault, strategy, brain, daddy, deposit_into_vault, amount ): - assert debt_allocator.getConfig(strategy.address) == (False, 0, 0, 0, 0) + debt_allocator.setMinimumWait(0, sender=brain) + assert debt_allocator.getStrategyConfig(vault, strategy.address) == ( + False, + 0, + 0, + 0, + 0, + ) vault.add_role(debt_allocator, ROLES.DEBT_MANAGER, sender=daddy) - (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault, strategy.address) assert bool == False assert bytes == ("!added").encode("utf-8") @@ -377,53 +453,70 @@ def test_should_update_debt( target = int(5_000) max = int(5_000) - debt_allocator.setMinimumChange(minimum, sender=brain) - debt_allocator.setStrategyDebtRatio(strategy, target, max, sender=brain) + debt_allocator.setMinimumChange(vault, minimum, sender=brain) + debt_allocator.setStrategyDebtRatio(vault, strategy, target, max, sender=brain) # Vault has no assets so should be false. - (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault, 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) + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault, 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) + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault, strategy.address) + assert bool == True + assert bytes == debt_allocator.update_debt.encode_input( + vault, strategy.address, amount // 2 + ) + + assert debt_allocator.isPaused(vault) == False + debt_allocator.setPaused(vault, True, sender=brain) + assert debt_allocator.isPaused(vault) + + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault, strategy.address) + assert bool == False + assert bytes == ("Paused").encode("utf-8") + + debt_allocator.setPaused(vault, False, sender=brain) + assert debt_allocator.isPaused(vault) == False + + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault, 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) + assert bytes == debt_allocator.update_debt.encode_input( + vault, strategy.address, amount // 2 + ) - debt_allocator.update_debt(strategy, amount // 2, sender=brain) + debt_allocator.update_debt(vault, strategy, amount // 2, sender=brain) chain.mine(1) # Should now be false again once allocated - (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault, 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 + vault, strategy, int(target + 1), int(target + 1), sender=brain ) - (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault, strategy.address) assert bool == True - assert bytes == vault.update_debt.encode_input( - strategy.address, int(amount * 5_001 // 10_000) + assert bytes == debt_allocator.update_debt.encode_input( + vault, 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) + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault, strategy.address) assert bool == False assert bytes == ("min wait").encode("utf-8") @@ -432,48 +525,49 @@ def test_should_update_debt( # 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) + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault, 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) + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault, strategy.address) assert bool == True - assert bytes == vault.update_debt.encode_input( - strategy.address, int(amount * 5_001 // 10_000) + assert bytes == debt_allocator.update_debt.encode_input( + vault, 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) + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault, 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) + debt_allocator.setMinimumChange(vault, int(1e30), sender=brain) - (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault, 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.setMinimumChange(vault, int(1), sender=brain) debt_allocator.setStrategyDebtRatio( - strategy, int(target // 2), int(target // 2), sender=brain + vault, strategy, int(target // 2), int(target // 2), sender=brain ) - (bool, bytes) = debt_allocator.shouldUpdateDebt(strategy.address) + (bool, bytes) = debt_allocator.shouldUpdateDebt(vault, strategy.address) assert bool == True - assert bytes == vault.update_debt.encode_input(strategy.address, amount // 4) + assert bytes == debt_allocator.update_debt.encode_input( + vault, strategy.address, amount // 4 + ) def test_update_debt( - debt_allocator_factory, debt_allocator, vault, strategy, @@ -483,7 +577,7 @@ def test_update_debt( deposit_into_vault, amount, ): - assert debt_allocator.getConfig(strategy) == (False, 0, 0, 0, 0) + assert debt_allocator.getStrategyConfig(vault, strategy) == (False, 0, 0, 0, 0) deposit_into_vault(vault, amount) assert vault.totalIdle() == amount @@ -494,11 +588,11 @@ def test_update_debt( # This reverts by the allocator with ape.reverts("!keeper"): - debt_allocator.update_debt(strategy, amount, sender=user) + debt_allocator.update_debt(vault, strategy, amount, sender=user) # This reverts by the vault with ape.reverts("not allowed"): - debt_allocator.update_debt(strategy, amount, sender=brain) + debt_allocator.update_debt(vault, strategy, amount, sender=brain) vault.add_role( debt_allocator, @@ -506,17 +600,17 @@ def test_update_debt( sender=daddy, ) - debt_allocator.update_debt(strategy, amount, sender=brain) + debt_allocator.update_debt(vault, strategy, amount, sender=brain) - timestamp = debt_allocator.getConfig(strategy)[3] + timestamp = debt_allocator.getStrategyConfig(vault, strategy)[3] assert timestamp != 0 assert vault.totalIdle() == 0 assert vault.totalDebt() == amount - debt_allocator_factory.setKeeper(user, True, sender=brain) + debt_allocator.setKeeper(user, True, sender=brain) - debt_allocator.update_debt(strategy, 0, sender=user) + debt_allocator.update_debt(vault, strategy, 0, sender=user) - assert debt_allocator.getConfig(strategy)[2] != timestamp + assert debt_allocator.getStrategyConfig(vault, strategy)[2] != timestamp assert vault.totalIdle() == amount assert vault.totalDebt() == 0