-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
build: max debt in allocator #26
Merged
Merged
Changes from 8 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
f4ba391
build: max debt in allocator
Schlagonia cdc35cb
test: test new allocator
Schlagonia 094b26f
test: debt update
Schlagonia 2460df6
feat: virtualize
Schlagonia 9a9e947
chore: update comments
Schlagonia 1a15619
fix: ci vyper version
Schlagonia 934d375
feat: check debt loss
Schlagonia d8915eb
fix: vyper ci
Schlagonia 19bad7e
feat: use max redeem
Schlagonia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,81 +16,164 @@ import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; | |
* | ||
* 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 `minimumChange` and a `targetRatio`. | ||
* manually 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 | ||
* points and represent the percent of total assets that specific | ||
* strategy should hold. | ||
* | ||
* 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` | ||
* more than its `maxRatio`. | ||
*/ | ||
contract GenericDebtAllocator is Governance { | ||
event SetTargetDebtRatio( | ||
/// @notice An event emitted when a strategies debt ratios are Updated. | ||
event UpdateStrategyDebtRatios( | ||
address indexed strategy, | ||
uint256 targetRatio, | ||
uint256 totalDebtRatio | ||
uint256 newTargetRatio, | ||
uint256 newMaxRatio, | ||
uint256 newTotalDebtRatio | ||
); | ||
|
||
event SetMinimumChange(address indexed strategy, uint256 minimumChange); | ||
/// @notice An event emitted when the minimum change is Updated. | ||
event UpdateMinimumChange(uint256 newMinimumChange); | ||
|
||
/// @notice An event emitted when the max base fee is Updated. | ||
event UpdateMaxAcceptableBaseFee(uint256 newMaxAcceptableBaseFee); | ||
|
||
/// @notice An event emitted when the max debt update loss is Updated. | ||
event UpdateMaxDebtUpdateLoss(uint256 newMaxDebtUpdateLoss); | ||
|
||
event SetMaxAcceptableBaseFee(uint256 maxAcceptableBaseFee); | ||
/// @notice An event emitted when the minimum time to wait is Updated. | ||
event UpdateMinimumWait(uint256 newMinimumWait); | ||
|
||
// Struct for each strategies info. | ||
/// @notice Struct for each strategies info. | ||
struct Config { | ||
// The percent in Basis Points the strategy should have. | ||
// The ideal percent in Basis Points the strategy should have. | ||
uint256 targetRatio; | ||
// The minimum amount denominated in asset that will | ||
// need to be moved to trigger a debt update. | ||
uint256 minimumChange; | ||
// The max percent of assets the strategy should hold. | ||
uint256 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; | ||
} | ||
|
||
uint256 internal constant MAX_BPS = 10_000; | ||
|
||
// Mapping of strategy => its config. | ||
/// @notice Vaults DEBT_MANAGER enumerator. | ||
uint256 internal constant DEBT_MANAGER = 64; | ||
|
||
/// @notice Mapping of strategy => its config. | ||
mapping(address => Config) public configs; | ||
|
||
// Address of the vault this serves as allocator for. | ||
/// @notice Address of the vault this serves as allocator for. | ||
address public vault; | ||
|
||
// Total debt ratio currently allocated in basis points. | ||
/// @notice Total debt ratio currently allocated in basis points. | ||
// Can't be more than 10_000. | ||
uint256 public debtRatio; | ||
|
||
// Max the chains base fee can be during debt update. | ||
/// @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 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; | ||
|
||
constructor(address _vault, address _governance) Governance(_governance) { | ||
initialize(_vault, _governance); | ||
constructor( | ||
address _vault, | ||
address _governance, | ||
uint256 _minimumChange | ||
) Governance(_governance) { | ||
initialize(_vault, _governance, _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) public { | ||
function initialize( | ||
address _vault, | ||
address _governance, | ||
uint256 _minimumChange | ||
) public virtual { | ||
require(address(vault) == address(0), "!initialized"); | ||
vault = _vault; | ||
governance = _governance; | ||
minimumChange = _minimumChange; | ||
// Default max base fee to uint256 max | ||
maxAcceptableBaseFee = type(uint256).max; | ||
// Default max loss on debt updates to 1 BP. | ||
maxDebtUpdateLoss = 1; | ||
} | ||
|
||
/** | ||
* @notice Debt update wrapper for the vault. | ||
* @dev This can be used if a minimum time between debt updates | ||
* is desired to be enforced. | ||
* | ||
* This contract and the msg.sender must have the DEBT_MANAGER | ||
* role assigned to them. | ||
* | ||
* The function signature matches the vault so no update to the | ||
* call data is required. | ||
* | ||
* This will also run checks on losses realized during debt | ||
* updates to assure decreases did not realize profits outside | ||
* of the allowed range. | ||
*/ | ||
function update_debt( | ||
address _strategy, | ||
uint256 _targetDebt | ||
) external virtual { | ||
IVault _vault = IVault(vault); | ||
require( | ||
(_vault.roles(msg.sender) & DEBT_MANAGER) == DEBT_MANAGER, | ||
"not allowed" | ||
); | ||
uint256 initialAssets = _vault.totalAssets(); | ||
_vault.update_debt(_strategy, _targetDebt); | ||
uint256 afterAssets = _vault.totalAssets(); | ||
|
||
// If a loss was realized. | ||
if (afterAssets < initialAssets) { | ||
// Make sure its within the range. | ||
require( | ||
initialAssets - afterAssets <= | ||
(initialAssets * maxDebtUpdateLoss) / MAX_BPS, | ||
"too much loss" | ||
); | ||
} | ||
configs[_strategy].lastUpdate = block.timestamp; | ||
} | ||
|
||
/** | ||
* @notice Check if a strategy's debt should be updated. | ||
* @dev This should be called by a keeper to decide if a strategies | ||
* debt should be updated and if so by how much. | ||
* | ||
* This cannot be used to withdraw down to 0 debt. | ||
* 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 returns (bool, bytes memory) { | ||
) external view virtual returns (bool, bytes memory) { | ||
// Check the base fee isn't too high. | ||
if (block.basefee > maxAcceptableBaseFee) { | ||
return (false, bytes("Base Fee")); | ||
|
@@ -108,9 +191,22 @@ contract GenericDebtAllocator is Governance { | |
// Make sure we have a target debt. | ||
require(config.targetRatio != 0, "no targetRatio"); | ||
|
||
if (block.timestamp - config.lastUpdate <= minimumWait) { | ||
return (false, "min wait"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need to cast the string to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
|
||
uint256 vaultAssets = _vault.totalAssets(); | ||
|
||
// Get the target debt for the strategy based on vault assets. | ||
uint256 targetDebt = Math.min( | ||
(_vault.totalAssets() * config.targetRatio) / MAX_BPS, | ||
(vaultAssets * config.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, | ||
// Make sure it is not more than the max allowed. | ||
params.max_debt | ||
); | ||
|
@@ -125,8 +221,9 @@ contract GenericDebtAllocator is Governance { | |
return (false, bytes("No Idle")); | ||
} | ||
|
||
// Add up to the max if possible | ||
uint256 toAdd = Math.min( | ||
targetDebt - params.current_debt, | ||
maxDebt - params.current_debt, | ||
// Can't take more than is available. | ||
Math.min( | ||
currentIdle - minIdle, | ||
|
@@ -135,7 +232,7 @@ contract GenericDebtAllocator is Governance { | |
); | ||
|
||
// If the amount to add is over our threshold. | ||
if (toAdd > config.minimumChange) { | ||
if (toAdd > minimumChange) { | ||
// Return true and the calldata. | ||
return ( | ||
true, | ||
|
@@ -146,7 +243,7 @@ contract GenericDebtAllocator is Governance { | |
); | ||
} | ||
// If target debt is lower than the current. | ||
} else if (targetDebt < params.current_debt) { | ||
} else if (maxDebt < params.current_debt) { | ||
// Find out by how much. | ||
uint256 toPull = Math.min( | ||
params.current_debt - targetDebt, | ||
|
@@ -155,7 +252,7 @@ contract GenericDebtAllocator is Governance { | |
); | ||
|
||
// Check if it's over the threshold. | ||
if (toPull > config.minimumChange) { | ||
if (toPull > minimumChange) { | ||
// Can't lower debt if there is unrealised losses. | ||
if ( | ||
_vault.assess_share_of_unrealised_losses( | ||
|
@@ -188,15 +285,21 @@ contract GenericDebtAllocator is Governance { | |
* | ||
* @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 setTargetDebtRatio( | ||
function setStrategyDebtRatios( | ||
address _strategy, | ||
uint256 _targetRatio | ||
) external onlyGovernance { | ||
uint256 _targetRatio, | ||
uint256 _maxRatio | ||
) external virtual onlyGovernance { | ||
// Make sure the strategy is added to the vault. | ||
require(IVault(vault).strategies(_strategy).activation != 0, "!active"); | ||
// Make sure a minimumChange has been set. | ||
require(configs[_strategy].minimumChange != 0, "!minimum"); | ||
require(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 what will be the new total debt ratio. | ||
uint256 newDebtRatio = debtRatio - | ||
|
@@ -208,30 +311,61 @@ contract GenericDebtAllocator is Governance { | |
|
||
// Write to storage. | ||
configs[_strategy].targetRatio = _targetRatio; | ||
configs[_strategy].maxRatio = _maxRatio; | ||
|
||
debtRatio = newDebtRatio; | ||
|
||
emit SetTargetDebtRatio(_strategy, _targetRatio, newDebtRatio); | ||
emit UpdateStrategyDebtRatios( | ||
_strategy, | ||
_targetRatio, | ||
_maxRatio, | ||
newDebtRatio | ||
); | ||
} | ||
|
||
/** | ||
* @notice Set the minimum change variable for a strategy. | ||
* @dev This is the amount of debt that will needed to be | ||
* added or pulled for it to trigger an update. | ||
* | ||
* @param _strategy The address of the strategy to update. | ||
* @param _minimumChange The new minimum to set for the strategy. | ||
*/ | ||
function setMinimumChange( | ||
address _strategy, | ||
uint256 _minimumChange | ||
) external onlyGovernance { | ||
// Make sure the strategy is added to the vault. | ||
require(IVault(vault).strategies(_strategy).activation != 0, "!active"); | ||
|
||
) external virtual onlyGovernance { | ||
require(_minimumChange > 0, "zero"); | ||
// Set the new minimum. | ||
configs[_strategy].minimumChange = _minimumChange; | ||
minimumChange = _minimumChange; | ||
|
||
emit UpdateMinimumChange(_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. | ||
*/ | ||
function setMaxDebtUpdateLoss( | ||
uint256 _maxDebtUpdateLoss | ||
) external virtual onlyGovernance { | ||
require(_maxDebtUpdateLoss <= MAX_BPS, "higher than max"); | ||
maxDebtUpdateLoss = _maxDebtUpdateLoss; | ||
|
||
emit UpdateMaxDebtUpdateLoss(_maxDebtUpdateLoss); | ||
} | ||
|
||
/** | ||
* @notice Set the minimum time to wait before re-updating a strategies debt. | ||
* @dev This is only enforced per strategy. | ||
* @param _minimumWait The minimum time in seconds to wait. | ||
*/ | ||
function setMinimumWait( | ||
uint256 _minimumWait | ||
) external virtual onlyGovernance { | ||
minimumWait = _minimumWait; | ||
|
||
emit SetMinimumChange(_strategy, _minimumChange); | ||
emit UpdateMinimumWait(_minimumWait); | ||
} | ||
|
||
/** | ||
|
@@ -245,9 +379,9 @@ contract GenericDebtAllocator is Governance { | |
*/ | ||
function setMaxAcceptableBaseFee( | ||
uint256 _maxAcceptableBaseFee | ||
) external onlyGovernance { | ||
) external virtual onlyGovernance { | ||
maxAcceptableBaseFee = _maxAcceptableBaseFee; | ||
|
||
emit SetMaxAcceptableBaseFee(_maxAcceptableBaseFee); | ||
emit UpdateMaxAcceptableBaseFee(_maxAcceptableBaseFee); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make "updated" lowercase
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
19bad7e