diff --git a/contracts/deprecated/vaultManager/OldVaultManager.sol b/contracts/deprecated/vaultManager/OldVaultManager.sol index 043445cd..e8d0e168 100644 --- a/contracts/deprecated/vaultManager/OldVaultManager.sol +++ b/contracts/deprecated/vaultManager/OldVaultManager.sol @@ -1,5 +1,38 @@ // SPDX-License-Identifier: GPL-3.0 +/* + * █ + ***** ▓▓▓ + * ▓▓▓▓▓▓▓ + * ///. ▓▓▓▓▓▓▓▓▓▓▓▓▓ + ***** //////// ▓▓▓▓▓▓▓ + * ///////////// ▓▓▓ + ▓▓ ////////////////// █ ▓▓ + ▓▓ ▓▓ /////////////////////// ▓▓ ▓▓ + ▓▓ ▓▓ //////////////////////////// ▓▓ ▓▓ + ▓▓ ▓▓ /////////▓▓▓///////▓▓▓///////// ▓▓ ▓▓ + ▓▓ ,////////////////////////////////////// ▓▓ ▓▓ + ▓▓ ////////////////////////////////////////// ▓▓ + ▓▓ //////////////////////▓▓▓▓///////////////////// + ,//////////////////////////////////////////////////// + .////////////////////////////////////////////////////////// + .//////////////////////////██.,//////////////////////////█ + .//////////////////////████..,./////////////////////██ + ...////////////////███████.....,.////////////////███ + ,.,////////////████████ ........,///////////████ + .,.,//////█████████ ,.......///////████ + ,..//████████ ........./████ + ..,██████ .....,███ + .██ ,.,█ + + + + ▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓ ▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓ ▓▓ ▓▓▓▓ + ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓ ▓▓▓▓▓ + ▓▓▓ ▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ +*/ + pragma solidity ^0.8.12; import "./OldVaultManagerPermit.sol"; @@ -14,6 +47,14 @@ contract OldVaultManager is OldVaultManagerPermit { using SafeERC20 for IERC20; using Address for address; + uint256 public dust; + + /// @notice Minimum amount of collateral (in stablecoin value, e.g in `BASE_TOKENS = 10**18`) that can be left + /// in a vault during a liquidation where the health factor function is decreasing + uint256 internal _dustCollateral; + + uint256[48] private __gapVaultManager; + function initialize( ITreasury _treasury, IERC20 _collateral, @@ -55,10 +96,7 @@ contract OldVaultManager is OldVaultManagerPermit { paused = true; } - /// @custom:oz-upgrades-unsafe-allow constructor - constructor(uint256 dust_, uint256 dustCollateral_) OldVaultManagerStorage(dust_, dustCollateral_) {} - - // ============================== Modifiers ==================================== + // ================================= MODIFIERS ================================= /// @notice Checks whether the `msg.sender` has the governor role or not modifier onlyGovernor() { @@ -84,9 +122,7 @@ contract OldVaultManager is OldVaultManagerPermit { _; } - // =========================== Vault Functions ================================= - - // ========================= External Access Functions ========================= + // ============================== VAULT FUNCTIONS ============================== function createVault(address toVault) external whenNotPaused returns (uint256) { return _mint(toVault); @@ -223,7 +259,7 @@ contract OldVaultManager is OldVaultManagerPermit { repayData ); } else { - if (stablecoinPayment > 0) stablecoin.burnFrom(stablecoinPayment, from, msg.sender); + if (stablecoinPayment != 0) stablecoin.burnFrom(stablecoinPayment, from, msg.sender); // In this case the collateral amount is necessarily non null collateral.safeTransferFrom( msg.sender, @@ -239,8 +275,8 @@ contract OldVaultManager is OldVaultManagerPermit { collateral.safeTransfer(to, paymentData.collateralAmountToGive - paymentData.collateralAmountToReceive); } else { uint256 collateralPayment = paymentData.collateralAmountToReceive - paymentData.collateralAmountToGive; - if (collateralPayment > 0) { - if (repayData.length > 0) { + if (collateralPayment != 0) { + if (repayData.length != 0) { ISwapper(who).swap( IERC20(address(stablecoin)), collateral, @@ -287,7 +323,7 @@ contract OldVaultManager is OldVaultManagerPermit { _repayDebt(vaultID, stablecoinAmountLessFeePaid, 0); } - // ============================= View Functions ================================ + // =============================== VIEW FUNCTIONS ============================== function getVaultDebt(uint256 vaultID) external view returns (uint256) { return (vaultData[vaultID].normalizedDebt * _calculateCurrentInterestAccumulator()) / BASE_INTEREST; @@ -315,7 +351,7 @@ contract OldVaultManager is OldVaultManagerPermit { ); } - // =================== Internal Utility View Functions ========================= + // ====================== INTERNAL UTILITY VIEW FUNCTIONS ====================== /// @notice Computes the health factor of a given vault. This can later be used to check whether a given vault is solvent /// (i.e. should be liquidated or not) @@ -363,7 +399,7 @@ contract OldVaultManager is OldVaultManagerPermit { return (interestAccumulator * (BASE_INTEREST + ratePerSecond * exp + secondTerm + thirdTerm)) / BASE_INTEREST; } - // =============== Internal Utility State-Modifying Functions ================== + // ================= INTERNAL UTILITY STATE-MODIFYING FUNCTIONS ================ /// @notice Closes a vault without handling the repayment of the concerned address /// @param vaultID ID of the vault to close @@ -392,6 +428,7 @@ contract OldVaultManager is OldVaultManagerPermit { /// @param collateralAmount Amount by which increasing the collateral balance of function _addCollateral(uint256 vaultID, uint256 collateralAmount) internal { if (!_exists(vaultID)) revert NonexistentVault(); + _checkpointCollateral(vaultID, collateralAmount, true); vaultData[vaultID].collateralAmount += collateralAmount; emit CollateralAmountUpdated(vaultID, collateralAmount, 1); } @@ -408,6 +445,7 @@ contract OldVaultManager is OldVaultManagerPermit { uint256 oracleValue, uint256 interestAccumulator_ ) internal onlyApprovedOrOwner(msg.sender, vaultID) { + _checkpointCollateral(vaultID, collateralAmount, false); vaultData[vaultID].collateralAmount -= collateralAmount; (uint256 healthFactor, , ) = _isSolvent(vaultData[vaultID], oracleValue, interestAccumulator_); if (healthFactor <= BASE_PARAMS) revert InsolventVault(); @@ -552,9 +590,9 @@ contract OldVaultManager is OldVaultManagerPermit { address who, bytes memory data ) internal { - if (collateralAmountToGive > 0) collateral.safeTransfer(to, collateralAmountToGive); - if (stableAmountToRepay > 0) { - if (data.length > 0) { + if (collateralAmountToGive != 0) collateral.safeTransfer(to, collateralAmountToGive); + if (stableAmountToRepay != 0) { + if (data.length != 0) { ISwapper(who).swap( collateral, IERC20(address(stablecoin)), @@ -568,7 +606,7 @@ contract OldVaultManager is OldVaultManagerPermit { } } - // =================== Treasury Relationship Functions ========================= + // ====================== TREASURY RELATIONSHIP FUNCTIONS ====================== function accrueInterestToTreasury() external onlyTreasury returns (uint256 surplusValue, uint256 badDebtValue) { _accrue(); @@ -602,7 +640,7 @@ contract OldVaultManager is OldVaultManagerPermit { return newInterestAccumulator; } - // ============================ Liquidations =================================== + // ================================ LIQUIDATIONS =============================== /// @notice Liquidates an ensemble of vaults specified by their IDs /// @dev This function is a simplified wrapper of the function below. It is built to remove for liquidators the need to specify @@ -637,12 +675,13 @@ contract OldVaultManager is OldVaultManagerPermit { address who, bytes memory data ) public whenNotPaused nonReentrant returns (LiquidatorData memory liqData) { - if (vaultIDs.length != amounts.length || amounts.length == 0) revert IncompatibleLengths(); + uint256 vaultIDsLength = vaultIDs.length; + if (vaultIDsLength != amounts.length || vaultIDsLength == 0) revert IncompatibleLengths(); // Stores all the data about an ongoing liquidation of multiple vaults liqData.oracleValue = oracle.read(); liqData.newInterestAccumulator = _accrue(); emit LiquidatedVaults(vaultIDs); - for (uint256 i; i < vaultIDs.length; ++i) { + for (uint256 i; i < vaultIDsLength; ++i) { Vault memory vault = vaultData[vaultIDs[i]]; // Computing if liquidation can take place for a vault LiquidationOpportunity memory liqOpp = _checkLiquidation( @@ -655,13 +694,19 @@ contract OldVaultManager is OldVaultManagerPermit { // Makes sure not to leave a dusty amount in the vault by either not liquidating too much // or everything if ( - (liqOpp.thresholdRepayAmount > 0 && amounts[i] > liqOpp.thresholdRepayAmount) || + (liqOpp.thresholdRepayAmount != 0 && amounts[i] >= liqOpp.thresholdRepayAmount) || amounts[i] > liqOpp.maxStablecoinAmountToRepay ) amounts[i] = liqOpp.maxStablecoinAmountToRepay; // liqOpp.discount stores in fact `1-discount` uint256 collateralReleased = (amounts[i] * BASE_PARAMS * _collatBase) / (liqOpp.discount * liqData.oracleValue); + + _checkpointCollateral( + vaultIDs[i], + vault.collateralAmount <= collateralReleased ? vault.collateralAmount : collateralReleased, + false + ); // Because we're rounding up in some divisions, `collateralReleased` can be greater than the `collateralAmount` of the vault // In this case, `stablecoinAmountToReceive` is still rounded up if (vault.collateralAmount <= collateralReleased) { @@ -670,10 +715,12 @@ contract OldVaultManager is OldVaultManagerPermit { totalNormalizedDebt -= vault.normalizedDebt; // Reinitializing the `vaultID`: we're not burning the vault in this case for integration purposes delete vaultData[vaultIDs[i]]; - liqData.badDebtFromLiquidation += - liqOpp.currentDebt - - (amounts[i] * liquidationSurcharge) / - BASE_PARAMS; + { + uint256 debtReimbursed = (amounts[i] * liquidationSurcharge) / BASE_PARAMS; + liqData.badDebtFromLiquidation += debtReimbursed < liqOpp.currentDebt + ? liqOpp.currentDebt - debtReimbursed + : 0; + } // There may be an edge case in which: `amounts[i] = (currentDebt * BASE_PARAMS) / surcharge + 1` // In this case, as long as `surcharge < BASE_PARAMS`, there cannot be any underflow in the operation // above @@ -716,7 +763,7 @@ contract OldVaultManager is OldVaultManagerPermit { uint256 liquidationDiscount = (_computeLiquidationBoost(liquidator) * (BASE_PARAMS - healthFactor)) / BASE_PARAMS; // In fact `liquidationDiscount` is stored here as 1 minus discount to save some computation costs - // This value is necessarily > 0 as `maxLiquidationDiscount < BASE_PARAMS` + // This value is necessarily != 0 as `maxLiquidationDiscount < BASE_PARAMS` liquidationDiscount = liquidationDiscount >= maxLiquidationDiscount ? BASE_PARAMS - maxLiquidationDiscount : BASE_PARAMS - liquidationDiscount; @@ -749,13 +796,14 @@ contract OldVaultManager is OldVaultManagerPermit { (vault.normalizedDebt * newInterestAccumulator * BASE_PARAMS) / (surcharge * BASE_INTEREST) + 1; - // In this case the threshold amount is such that it leaves just enough dust - // This line cannot underflow as long as the `dust` parameter is constant: it is always checked that - // the debt is greater than the `dust`. If the `dust` was to increase due to an implementation upgrade - // we would need to add some extra checks to avoid underflows - // Here the `thresholdRepayAmount` is also rounded down: which means that if a liquidator repays this amount - // then there would be more than `dust` left in the vault - thresholdRepayAmount = ((currentDebt - dust) * BASE_PARAMS) / surcharge; + // In this case the threshold amount is such that it leaves just enough dust: amount is rounded + // down such that if a liquidator repays this amount then there would be more than `dust` left in + // the liquidated vault + if (currentDebt > dust) + thresholdRepayAmount = ((currentDebt - dust) * BASE_PARAMS) / surcharge; + // If there is from the beginning a dusty debt (because of an implementation upgrade), then + // liquidator should repay everything that's left + else thresholdRepayAmount = 1; } } else { // In all cases the liquidator can repay stablecoins such that they'll end up getting exactly the collateral @@ -774,7 +822,7 @@ contract OldVaultManager is OldVaultManagerPermit { ((collateralAmountInStable - _dustCollateral) * liquidationDiscount) / BASE_PARAMS; // If there is from the beginning a dusty amount of collateral, liquidator should repay everything that's left - else thresholdRepayAmount = maxAmountToRepay; + else thresholdRepayAmount = 1; } liqOpp.maxStablecoinAmountToRepay = maxAmountToRepay; liqOpp.maxCollateralAmountGiven = @@ -785,25 +833,7 @@ contract OldVaultManager is OldVaultManagerPermit { liqOpp.currentDebt = currentDebt; } - /// @notice Computes the liquidation boost of a given address, that is the slope of the discount function - /// @param liquidator Address for which boost should be computed - /// @return The slope of the discount function - function _computeLiquidationBoost(address liquidator) internal view returns (uint256) { - if (address(veBoostProxy) == address(0)) { - return yLiquidationBoost[0]; - } else { - uint256 adjustedBalance = veBoostProxy.adjusted_balance_of(liquidator); - if (adjustedBalance >= xLiquidationBoost[1]) return yLiquidationBoost[1]; - else if (adjustedBalance <= xLiquidationBoost[0]) return yLiquidationBoost[0]; - else - return - yLiquidationBoost[0] + - ((yLiquidationBoost[1] - yLiquidationBoost[0]) * (adjustedBalance - xLiquidationBoost[0])) / - (xLiquidationBoost[1] - xLiquidationBoost[0]); - } - } - - // ============================== Setters ====================================== + // ================================== SETTERS ================================== /// @notice Sets parameters encoded as uint64 /// @param param Value for the parameter @@ -853,26 +883,12 @@ contract OldVaultManager is OldVaultManagerPermit { } /// @notice Sets the parameters for the liquidation booster which encodes the slope of the discount - /// @param _veBoostProxy Address which queries veANGLE balances and adjusted balances from delegation - /// @param xBoost Threshold values of veANGLE adjusted balances - /// @param yBoost Values of the liquidation boost at the threshold values of x - /// @dev There are 2 modes: - /// When boost is enabled, `xBoost` and `yBoost` should have a length of 2, but if they have a - /// higher length contract will still work as expected. Contract will also work as expected if their - /// length differ - /// When boost is disabled, `_veBoostProxy` needs to be zero address and `yBoost[0]` is the base boost function setLiquidationBoostParameters( address _veBoostProxy, uint256[] memory xBoost, uint256[] memory yBoost - ) external onlyGovernorOrGuardian { - if ( - (xBoost.length != yBoost.length) || - (yBoost[0] == 0) || - ((_veBoostProxy != address(0)) && (xBoost[1] <= xBoost[0] || yBoost[1] < yBoost[0])) - ) revert InvalidSetOfParameters(); - veBoostProxy = IVeBoostProxy(_veBoostProxy); - xLiquidationBoost = xBoost; + ) external virtual onlyGovernorOrGuardian { + if (yBoost[0] == 0) revert InvalidSetOfParameters(); yLiquidationBoost = yBoost; emit LiquidationBoostParametersUpdated(_veBoostProxy, xBoost, yBoost); } @@ -906,10 +922,38 @@ contract OldVaultManager is OldVaultManagerPermit { oracle = IOracle(_oracle); } + /// @notice Sets the dust variables + /// @param _dust New minimum debt allowed + /// @param dustCollateral_ New minimum collateral allowed in a vault after a liquidation + /// @dev dustCollateral_ is in stable value + function setDusts(uint256 _dust, uint256 dustCollateral_) external onlyGovernor { + dust = _dust; + _dustCollateral = dustCollateral_; + } + function setTreasury(address _treasury) external onlyTreasury { treasury = ITreasury(_treasury); // This function makes sure to propagate the change to the associated contract // even though a single oracle contract could be used in different places oracle.setTreasury(_treasury); } + + // ============================= VIRTUAL FUNCTIONS ============================= + + /// @notice Returns the liquidation boost of a given address, that is the slope of the discount function + /// @return The slope of the discount function + function _computeLiquidationBoost(address) internal view virtual returns (uint256) { + return yLiquidationBoost[0]; + } + + /// @notice Hook called before any collateral internal changes + /// @param vaultID Vault which sees its collateral amount changed + /// @param amount Collateral amount balance of the owner of vaultID increase/decrease + /// @param add Whether the balance should be increased/decreased + /// @param vaultID Vault which sees its collateral amount changed + function _checkpointCollateral( + uint256 vaultID, + uint256 amount, + bool add + ) internal virtual {} } diff --git a/contracts/deprecated/vaultManager/OldVaultManagerStorage.sol b/contracts/deprecated/vaultManager/OldVaultManagerStorage.sol index bd42a141..e2b843ec 100644 --- a/contracts/deprecated/vaultManager/OldVaultManagerStorage.sol +++ b/contracts/deprecated/vaultManager/OldVaultManagerStorage.sol @@ -50,10 +50,6 @@ contract OldVaultManagerStorage is IVaultManagerStorage, Initializable, Reentran // ================================= PARAMETERS ================================ // Unless specified otherwise, parameters of this contract are expressed in `BASE_PARAMS` - uint256 public immutable dust; - /// @notice Minimum amount of collateral (in stablecoin value, e.g in `BASE_TOKENS = 10**18`) that can be left - /// in a vault during a liquidation where the health factor function is decreasing - uint256 internal immutable _dustCollateral; /// @notice Maximum amount of stablecoins that can be issued with this contract (in `BASE_TOKENS`). This parameter should /// not be bigger than `type(uint256).max / BASE_INTEREST` otherwise there may be some overflows in the `increaseDebt` function uint256 public debtCeiling; @@ -167,12 +163,6 @@ contract OldVaultManagerStorage is IVaultManagerStorage, Initializable, Reentran error TooSmallParameterValue(); error ZeroAddress(); - /// @param _dust Minimum amount of debt a vault from this implementation can have - /// @param dustCollateral_ Minimum amount of collateral (in stablecoin value) that can be left in a vault during a liquidation - /// where the health factor function is decreasing - /// @dev Run only at the implementation level - constructor(uint256 _dust, uint256 dustCollateral_) initializer { - dust = _dust; - _dustCollateral = dustCollateral_; - } + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() initializer {} } diff --git a/contracts/vaultManager/VaultManager.sol b/contracts/vaultManager/VaultManager.sol index ce481701..dad43d59 100644 --- a/contracts/vaultManager/VaultManager.sol +++ b/contracts/vaultManager/VaultManager.sol @@ -54,7 +54,11 @@ contract VaultManager is VaultManagerPermit, IVaultManagerFunctions { /// in a vault during a liquidation where the health factor function is decreasing uint256 internal _dustCollateral; - uint256[48] private __gapVaultManager; + /// @notice If the amount of debt of a vault that gets liquidated is below this amount, then the liquidator + /// can liquidate all the debt of the vault (and not just what's needed to get to the target health factor) + uint256 public dustLiquidation; + + uint256[47] private __gapVaultManager; /// @inheritdoc IVaultManagerFunctions function initialize( @@ -719,6 +723,7 @@ contract VaultManager is VaultManagerPermit, IVaultManagerFunctions { // Because we're rounding up in some divisions, `collateralReleased` can be greater than the `collateralAmount` of the vault // In this case, `stablecoinAmountToReceive` is still rounded up if (vault.collateralAmount <= collateralReleased) { + // Liquidators should never get more collateral than what's in the vault collateralReleased = vault.collateralAmount; // Remove all the vault's debt (debt repayed + bad debt) from VaultManager totalDebt totalNormalizedDebt -= vault.normalizedDebt; @@ -778,12 +783,11 @@ contract VaultManager is VaultManagerPermit, IVaultManagerFunctions { : BASE_PARAMS - liquidationDiscount; // Same for the surcharge here: it's in fact 1 - the fee taken by the protocol uint256 surcharge = liquidationSurcharge; - // Checking if we're in a situation where the health factor is an increasing or a decreasing function of the - // amount repaid uint256 maxAmountToRepay; uint256 thresholdRepayAmount; - // In the first case, the health factor is an increasing function of the stablecoin amount to repay, - // this means that the liquidator can bring the vault to the target health ratio + // Checking if we're in a situation where the health factor is an increasing or a decreasing function of the + // amount repaid. In the first case, the health factor is an increasing function which means that the liquidator + // can bring the vault to the target health ratio if (healthFactor * liquidationDiscount * surcharge >= collateralFactor * BASE_PARAMS**2) { // This is the max amount to repay that will bring the person to the target health factor // Denom is always positive when a vault gets liquidated in this case and when the health factor @@ -794,32 +798,27 @@ contract VaultManager is VaultManagerPermit, IVaultManagerFunctions { BASE_PARAMS * liquidationDiscount) / (surcharge * targetHealthFactor * liquidationDiscount - (BASE_PARAMS**2) * collateralFactor); - // The quantity below tends to be rounded in the above direction, which means that governance or guardians should - // set the `targetHealthFactor` accordingly - // Need to check for the dust: liquidating should not leave a dusty amount in the vault - if (currentDebt * BASE_PARAMS <= maxAmountToRepay * surcharge + dust * BASE_PARAMS) { - // If liquidating to the target threshold would leave a dusty amount: the liquidator can repay all - // We're rounding up the max amount to repay to make sure all the debt ends up being paid - // and we're computing again the real value of the debt to avoid propagation of rounding errors + // Need to check for the dustas liquidating should not leave a dusty amount in the vault + uint256 dustParameter = dustLiquidation; + if (currentDebt * BASE_PARAMS <= maxAmountToRepay * surcharge + dustParameter * BASE_PARAMS) { + // If liquidating to the target threshold would leave a dusty amount: the liquidator can repay all. + // We're avoiding here propagation of rounding errors and rounding up the max amount to repay to make + // sure all the debt ends up being paid maxAmountToRepay = (vault.normalizedDebt * newInterestAccumulator * BASE_PARAMS) / (surcharge * BASE_INTEREST) + 1; // In this case the threshold amount is such that it leaves just enough dust: amount is rounded - // down such that if a liquidator repays this amount then there would be more than `dust` left in + // down such that if a liquidator repays this amount then there is more than `dustLiquidation` left in // the liquidated vault - if (currentDebt > dust) - thresholdRepayAmount = ((currentDebt - dust) * BASE_PARAMS) / surcharge; - // If there is from the beginning a dusty debt (because of an implementation upgrade), then - // liquidator should repay everything that's left + if (currentDebt > dustParameter) + thresholdRepayAmount = ((currentDebt - dustParameter) * BASE_PARAMS) / surcharge; + // If there is from the beginning a dusty debt, then liquidator should repay everything that's left else thresholdRepayAmount = 1; } } else { - // In all cases the liquidator can repay stablecoins such that they'll end up getting exactly the collateral + // In this case, the liquidator can repay stablecoins such that they'll end up getting exactly the collateral // in the liquidated vault - // Rounding up to make sure all gets liquidated in this case: the liquidator will never get more than the collateral - // amount in the vault however: we're performing the computation of the `collateralAmountInStable` again to avoid - // propagation of rounding errors maxAmountToRepay = (vault.collateralAmount * liquidationDiscount * oracleValue) / (BASE_PARAMS * _collatBase) + @@ -864,7 +863,7 @@ contract VaultManager is VaultManagerPermit, IVaultManagerFunctions { borrowFee = param; } else if (what == "RF") { // As liquidation surcharge is stored as `1-fee` and as we need `repayFee` to be smaller - // then the liquidation surcharge, then we need to have: + // than the liquidation surcharge, we need to have: // `liquidationSurcharge <= BASE_PARAMS - repayFee` and as such `liquidationSurcharge + repayFee <= BASE_PARAMS` if (param + liquidationSurcharge > BASE_PARAMS) revert TooHighParameterValue(); repayFee = param; @@ -933,10 +932,17 @@ contract VaultManager is VaultManagerPermit, IVaultManagerFunctions { /// @notice Sets the dust variables /// @param _dust New minimum debt allowed + /// @param _dustLiquidation New `dustLiquidation` value /// @param dustCollateral_ New minimum collateral allowed in a vault after a liquidation /// @dev dustCollateral_ is in stable value - function setDusts(uint256 _dust, uint256 dustCollateral_) external onlyGovernor { + function setDusts( + uint256 _dust, + uint256 _dustLiquidation, + uint256 dustCollateral_ + ) external onlyGovernor { + if (_dust > _dustLiquidation) revert InvalidParameterValue(); dust = _dust; + dustLiquidation = _dustLiquidation; _dustCollateral = dustCollateral_; } diff --git a/deploy/5_vaultManagerImplementation.ts b/deploy/5_vaultManagerImplementation.ts index 5c387bf8..ca37657a 100644 --- a/deploy/5_vaultManagerImplementation.ts +++ b/deploy/5_vaultManagerImplementation.ts @@ -7,14 +7,14 @@ const func: DeployFunction = async ({ deployments, ethers }) => { const { deployer } = await ethers.getNamedSigners(); console.log('Now deploying the implementation for VaultManager'); - await deploy('VaultManager_Implementation', { + await deploy('VaultManager_V2_0_Implementation', { contract: 'VaultManagerLiquidationBoost', from: deployer.address, args: [], log: !argv.ci, }); - const vaultManagerImplementation = (await ethers.getContract('VaultManager_Implementation')).address; + const vaultManagerImplementation = (await ethers.getContract('VaultManager_V2_0_Implementation')).address; console.log(`Successfully deployed the implementation for VaultManager at ${vaultManagerImplementation}`); console.log(''); diff --git a/hardhat.config.ts b/hardhat.config.ts index 10d81d06..72f570b0 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -143,10 +143,10 @@ const config: HardhatUserConfig = { forking: { enabled: argv.fork || false, // Mainnet - /* + url: nodeUrl('fork'), - blockNumber: 15975107, - */ + blockNumber: 16218353, + // Polygon /* url: nodeUrl('forkpolygon'), @@ -164,9 +164,10 @@ const config: HardhatUserConfig = { blockNumber: 19356874, */ // Avalanche - + /* url: nodeUrl('avalanche'), blockNumber: 23545788, + */ }, mining: argv.disableAutoMining ? { diff --git a/scripts/mainnet-fork/upgradeVaultManager.ts b/scripts/mainnet-fork/upgradeVaultManager.ts index 6acf33b6..bb10dedd 100644 --- a/scripts/mainnet-fork/upgradeVaultManager.ts +++ b/scripts/mainnet-fork/upgradeVaultManager.ts @@ -14,7 +14,9 @@ async function main() { const vaultManagerAddress = '0x73aaf8694BA137a7537E7EF544fcf5E2475f227B'; - const implementation = (await deployments.get('VaultManagerNoDust_Implementation')).address; + const implementation = (await deployments.get('VaultManager_V2_0_Implementation')).address; + + console.log(implementation); const proxyAdminAddress = CONTRACTS_ADDRESSES[ChainId.MAINNET].ProxyAdmin!; @@ -58,8 +60,9 @@ async function main() { console.log(await vaultManager.name()); console.log(await vaultManager.symbol()); - await vaultManager.connect(signer).setDusts(10, 10); + await vaultManager.connect(signer).setDusts(10, 10, 10); console.log((await vaultManager.dust()).toString()); + console.log((await vaultManager.dustLiquidation()).toString()); console.log((await vaultManager.vaultData(8)).collateralAmount.toString()); console.log((await vaultManager.vaultData(8)).normalizedDebt.toString()); diff --git a/test/hardhat/reactor/reactor.test.ts b/test/hardhat/reactor/reactor.test.ts index c211cc2d..98e3bef0 100644 --- a/test/hardhat/reactor/reactor.test.ts +++ b/test/hardhat/reactor/reactor.test.ts @@ -122,7 +122,7 @@ contract('Reactor', () => { stableMaster = await new MockStableMaster__factory(deployer).deploy(); await vaultManager.initialize(treasury.address, ANGLE.address, oracle.address, params, 'USDC/agEUR'); - await vaultManager.connect(governor).setDusts(0.1e15, 0.1e15); + await vaultManager.connect(governor).setDusts(0.1e15, 0.1e15, 0.1e15); await vaultManager.connect(guardian).togglePause(); reactor = (await deployUpgradeable(new Reactor__factory(deployer))) as Reactor; diff --git a/test/hardhat/vaultManager/vaultManager.test.ts b/test/hardhat/vaultManager/vaultManager.test.ts index d18aab4b..fa27b038 100644 --- a/test/hardhat/vaultManager/vaultManager.test.ts +++ b/test/hardhat/vaultManager/vaultManager.test.ts @@ -130,7 +130,7 @@ contract('VaultManagerLiquidationBoost', () => { await vaultManager.initialize(treasury.address, collateral.address, oracle.address, params, 'USDC/agEUR'); await vaultManager.connect(guardian).togglePause(); await vaultManager.connect(governor).setUint64(params.borrowFee, formatBytes32String('BF')); - await vaultManager.connect(governor).setDusts(0.1e9, 0.1e9); + await vaultManager.connect(governor).setDusts(0.1e9, 0.1e9, 0.1e9); }); describe('oracle', () => { it('success - read', async () => { @@ -1178,7 +1178,7 @@ contract('VaultManagerLiquidationBoost', () => { await treasury.addMinter(agToken.address, vaultManager.address); oracle = await new MockOracle__factory(deployer).deploy(parseUnits('2', 18), treasury.address); await vaultManager.initialize(treasury.address, agToken.address, oracle.address, params, 'USDC/agEUR'); - await vaultManager.connect(governor).setDusts(0.1e9, 0.1e9); + await vaultManager.connect(governor).setDusts(0.1e9, 0.1e9, 0.1e9); await vaultManager.connect(guardian).togglePause(); }); it('success - allowance given', async () => { @@ -2263,7 +2263,7 @@ contract('VaultManagerLiquidationBoost', () => { params.interestRate = parseEther('0'); params.borrowFee = 0; await vaultManager.initialize(treasury.address, collateral.address, oracle.address, params, 'USDC/agEUR'); - await vaultManager.connect(governor).setDusts(parseEther('0.5'), parseEther('0.5')); + await vaultManager.connect(governor).setDusts(parseEther('0.5'), parseEther('0.5'), parseEther('0.5')); await vaultManager.connect(guardian).togglePause(); await treasury.setVaultManager2(vaultManager.address); await treasury.addMinter(agToken.address, vaultManager.address); @@ -2289,7 +2289,7 @@ contract('VaultManagerLiquidationBoost', () => { params.interestRate = parseEther('0'); params.borrowFee = 0; await vaultManager.initialize(treasury.address, collateral.address, oracle.address, params, 'USDC/agEUR'); - await vaultManager.connect(governor).setDusts(parseEther('0.5'), parseEther('0.5')); + await vaultManager.connect(governor).setDusts(parseEther('0.5'), parseEther('0.5'), parseEther('0.5')); await vaultManager.connect(guardian).togglePause(); await treasury.setVaultManager2(vaultManager.address); await treasury.addMinter(agToken.address, vaultManager.address); diff --git a/test/hardhat/vaultManager/vaultManagerDust.test.ts b/test/hardhat/vaultManager/vaultManagerDust.test.ts index ff89df57..ed29f9e9 100644 --- a/test/hardhat/vaultManager/vaultManagerDust.test.ts +++ b/test/hardhat/vaultManager/vaultManagerDust.test.ts @@ -128,12 +128,19 @@ contract('VaultManager - Dust Modification interactions', () => { }); describe('setDusts', () => { it('reverts - non governor', async () => { - await expect(vaultManager.connect(alice).setDusts(0, 0)).to.be.revertedWith('NotGovernor'); - await expect(vaultManager.connect(guardian).setDusts(0, 0)).to.be.revertedWith('NotGovernor'); + await expect(vaultManager.connect(alice).setDusts(0, 0, 0)).to.be.revertedWith('NotGovernor'); + await expect(vaultManager.connect(guardian).setDusts(0, 0, 0)).to.be.revertedWith('NotGovernor'); }); it('success - when governor is calling', async () => { - await vaultManager.connect(governor).setDusts(1, 1); + await vaultManager.connect(governor).setDusts(1, 2, 1); expect(await vaultManager.dust()).to.be.equal(1); + expect(await vaultManager.dustLiquidation()).to.be.equal(2); + await vaultManager.connect(governor).setDusts(parseEther('0.1'), parseEther('10'), 1); + expect(await vaultManager.dust()).to.be.equal(parseEther('0.1')); + expect(await vaultManager.dustLiquidation()).to.be.equal(parseEther('10')); + }); + it('reverts - invalid parameter value', async () => { + await expect(vaultManager.connect(governor).setDusts(2, 1, 1)).to.be.revertedWith('InvalidParameterValue'); }); }); describe('repayDebt - when dust has increased', () => { @@ -164,18 +171,18 @@ contract('VaultManager - Dust Modification interactions', () => { expect(await vaultManager.lastInterestAccumulatorUpdated()).to.be.equal(await latestTime()); expect(await vaultManager.getVaultDebt(2)).to.be.equal(parseEther('0.5')); expect(await agToken.balanceOf(alice.address)).to.be.equal(parseEther('0.5')); - await vaultManager.connect(governor).setDusts(parseEther('1'), parseEther('1')); + await vaultManager.connect(governor).setDusts(parseEther('1'), parseEther('1'), parseEther('1')); await expect(angle(vaultManager, alice, [repayDebt(2, parseEther('0.3'))])).to.be.revertedWith( 'DustyLeftoverAmount', ); - await vaultManager.connect(governor).setDusts(parseEther('0.1'), parseEther('0.1')); + await vaultManager.connect(governor).setDusts(parseEther('0.1'), parseEther('0.1'), parseEther('0.1')); await angle(vaultManager, alice, [repayDebt(2, parseEther('0.3'))]); expect((await vaultManager.vaultData(2)).collateralAmount).to.be.equal(collatAmount); expect((await vaultManager.vaultData(2)).normalizedDebt).to.be.equal(parseEther('0.2')); expect(await vaultManager.lastInterestAccumulatorUpdated()).to.be.equal(await latestTime()); expect(await vaultManager.getVaultDebt(2)).to.be.equal(parseEther('0.2')); expect(await agToken.balanceOf(alice.address)).to.be.equal(parseEther('0.2')); - await vaultManager.connect(governor).setDusts(parseEther('1'), parseEther('1')); + await vaultManager.connect(governor).setDusts(parseEther('1'), parseEther('1'), parseEther('1')); await expect(angle(vaultManager, alice, [repayDebt(2, parseEther('0.1'))])).to.be.revertedWith( 'DustyLeftoverAmount', ); @@ -217,11 +224,11 @@ contract('VaultManager - Dust Modification interactions', () => { expect(await vaultManager.lastInterestAccumulatorUpdated()).to.be.equal(await latestTime()); expect(await vaultManager.getVaultDebt(2)).to.be.equal(parseEther('1')); expect(await vaultManager.getVaultDebt(1)).to.be.equal(parseEther('1')); - await vaultManager.connect(governor).setDusts(parseEther('0.5'), parseEther('0.5')); + await vaultManager.connect(governor).setDusts(parseEther('0.5'), parseEther('0.5'), parseEther('0.5')); await expect( angle(vaultManager, alice, [getDebtIn(1, vaultManager.address, 2, parseEther('0.6'))]), ).to.be.revertedWith('DustyLeftoverAmount'); - await vaultManager.connect(governor).setDusts(parseEther('1.5'), parseEther('1.5')); + await vaultManager.connect(governor).setDusts(parseEther('1.5'), parseEther('1.5'), parseEther('1.5')); // You cannot reduce your debt await expect( angle(vaultManager, alice, [getDebtIn(1, vaultManager.address, 2, parseEther('0.1'))]), @@ -274,7 +281,7 @@ contract('VaultManager - Dust Modification interactions', () => { ); // Now if dust increases, we may be in a situation where it's too high await displayVaultState(vaultManager, 2, log, collatBase); - await vaultManager.connect(governor).setDusts(parseEther('10'), parseEther('10')); + await vaultManager.connect(governor).setDusts(parseEther('10'), parseEther('10'), parseEther('10')); const stableAmountToRepay = (await vaultManager.checkLiquidation(2, bob.address)).maxStablecoinAmountToRepay; const collatGiven = (await vaultManager.checkLiquidation(2, bob.address)).maxCollateralAmountGiven; expectApprox(stableAmountToRepay, parseEther(maxStablecoinAmountToRepay.toString()), 0.0001); @@ -335,7 +342,7 @@ contract('VaultManager - Dust Modification interactions', () => { ); expect((await vaultManager.checkLiquidation(2, bob.address)).thresholdRepayAmount).to.be.equal(0); - await vaultManager.connect(governor).setDusts(parseEther('0.5'), parseEther('0.5')); + await vaultManager.connect(governor).setDusts(parseEther('0.5'), parseEther('0.5'), parseEther('0.5')); await displayVaultState(vaultManager, 2, log, collatBase); @@ -363,7 +370,7 @@ contract('VaultManager - Dust Modification interactions', () => { 0.0001, ); - await vaultManager.connect(governor).setDusts(parseEther('1.5'), parseEther('1.5')); + await vaultManager.connect(governor).setDusts(parseEther('1.5'), parseEther('1.5'), parseEther('1.5')); await displayVaultState(vaultManager, 2, log, collatBase); @@ -408,6 +415,129 @@ contract('VaultManager - Dust Modification interactions', () => { await displayVaultState(vaultManager, 2, log, collatBase); + await expect(vaultManager.checkLiquidation(2, bob.address)).to.be.reverted; + expect(await vaultManager.totalNormalizedDebt()).to.be.equal(0); + }); + it('success - dustLiquidation but no dust', async () => { + const collatAmount = parseUnits('2', collatBase); + const borrowAmount = parseEther('1'); + + await collateral.connect(alice).mint(alice.address, collatAmount); + await collateral.connect(alice).approve(vaultManager.address, collatAmount); + + await stableMaster.connect(bob).mint(agToken.address, bob.address, borrowAmount.mul(10)); + await agToken.connect(bob).approve(vaultManager.address, borrowAmount); + + await angle(vaultManager, alice, [ + createVault(alice.address), + createVault(alice.address), + addCollateral(2, collatAmount), + borrow(2, borrowAmount), + ]); + const rate = 0.6; + await oracle.update(parseEther(rate.toString())); + + // This time discount is maxed + const discount = Math.max((2 * rate * 0.8) / 1, 0.9); + const maxStablecoinAmountToRepay = (1.1 - rate * 2 * 0.8) / (0.9 * 1.1 - 0.8 / discount); + + await displayVaultState(vaultManager, 2, log, collatBase); + + // This is approx equal to 0.89 over 1 + expectApprox( + (await vaultManager.checkLiquidation(2, bob.address)).maxStablecoinAmountToRepay, + parseEther(maxStablecoinAmountToRepay.toString()), + 0.0001, + ); + expectApprox( + (await vaultManager.checkLiquidation(2, bob.address)).maxCollateralAmountGiven, + parseUnits((maxStablecoinAmountToRepay / rate / discount).toFixed(10), collatBase), + 0.0001, + ); + expect((await vaultManager.checkLiquidation(2, bob.address)).thresholdRepayAmount).to.be.equal(0); + + // There is no dust on the debt but there is on + await vaultManager.connect(governor).setDusts(parseEther('0'), parseEther('0.5'), parseEther('0')); + + await displayVaultState(vaultManager, 2, log, collatBase); + + console.log((await vaultManager.checkLiquidation(2, bob.address)).maxStablecoinAmountToRepay.toString()); + + const maxStablecoinAmountToRepay2 = 1 / 0.9; + + expectApprox( + (await vaultManager.checkLiquidation(2, bob.address)).maxStablecoinAmountToRepay, + parseEther(maxStablecoinAmountToRepay2.toString()), + 0.0001, + ); + expectApprox( + (await vaultManager.checkLiquidation(2, bob.address)).maxCollateralAmountGiven, + parseUnits((maxStablecoinAmountToRepay2 / rate / discount).toFixed(10), collatBase), + 0.0001, + ); + // Threshold repay amount is (debt - dust) / surcharge + expectApprox( + (await vaultManager.checkLiquidation(2, bob.address)).thresholdRepayAmount, + // 0.5 is debt - dust and surcharge is 0.9 + parseEther('0.555555555555555555'), + 0.0001, + ); + + await vaultManager.connect(governor).setDusts(parseEther('0'), parseEther('1.5'), parseEther('0')); + + await displayVaultState(vaultManager, 2, log, collatBase); + + expectApprox( + (await vaultManager.checkLiquidation(2, bob.address)).maxStablecoinAmountToRepay, + parseEther(maxStablecoinAmountToRepay2.toString()), + 0.0001, + ); + expectApprox( + (await vaultManager.checkLiquidation(2, bob.address)).maxCollateralAmountGiven, + parseUnits((maxStablecoinAmountToRepay2 / rate / discount).toFixed(10), collatBase), + 0.0001, + ); + + expect((await vaultManager.checkLiquidation(2, bob.address)).thresholdRepayAmount).to.be.equal(1); + + await vaultManager.connect(bob)[ + // We still enter a really small amount + 'liquidate(uint256[],uint256[],address,address)' + ]([2], [0], bob.address, bob.address); + expectApprox( + (await vaultManager.checkLiquidation(2, bob.address)).maxStablecoinAmountToRepay, + parseEther(maxStablecoinAmountToRepay2.toString()), + 0.0001, + ); + expectApprox( + (await vaultManager.checkLiquidation(2, bob.address)).maxCollateralAmountGiven, + parseUnits((maxStablecoinAmountToRepay2 / rate / discount).toFixed(10), collatBase), + 0.0001, + ); + + expect((await vaultManager.checkLiquidation(2, bob.address)).thresholdRepayAmount).to.be.equal(1); + + expect(await agToken.balanceOf(bob.address)).to.be.equal(borrowAmount.mul(10)); + expect(await collateral.balanceOf(bob.address)).to.be.equal(0); + + await vaultManager.connect(bob)[ + // We still enter a really small amount + 'liquidate(uint256[],uint256[],address,address)' + ]([2], [4], bob.address, bob.address); + + expectApprox( + await agToken.balanceOf(bob.address), + borrowAmount.mul(10).sub(parseEther(maxStablecoinAmountToRepay2.toString())), + 0.1, + ); + expectApprox( + await collateral.balanceOf(bob.address), + parseUnits((maxStablecoinAmountToRepay2 / rate / discount).toFixed(10), collatBase), + 0.1, + ); + + await displayVaultState(vaultManager, 2, log, collatBase); + await expect(vaultManager.checkLiquidation(2, bob.address)).to.be.reverted; expect(await vaultManager.totalNormalizedDebt()).to.be.equal(0); }); diff --git a/test/hardhat/vaultManager/vaultManagerERC721.test.ts b/test/hardhat/vaultManager/vaultManagerERC721.test.ts index 5d6959b1..08e69a83 100644 --- a/test/hardhat/vaultManager/vaultManagerERC721.test.ts +++ b/test/hardhat/vaultManager/vaultManagerERC721.test.ts @@ -112,7 +112,7 @@ contract('VaultManagerLiquidationBoost - ERC721', () => { it('success - setters', async () => { await vaultManager.initialize(treasury.address, collateral.address, oracle.address, params, 'USDC/agEUR'); - await vaultManager.connect(governor).setDusts(0.1e9, 0.1e9); + await vaultManager.connect(governor).setDusts(0.1e9, 0.1e9, 0.1e9); expect(await vaultManager.oracle()).to.be.equal(oracle.address); expect(await vaultManager.treasury()).to.be.equal(treasury.address); expect(await vaultManager.collateral()).to.be.equal(collateral.address); @@ -126,7 +126,7 @@ contract('VaultManagerLiquidationBoost - ERC721', () => { it('success - access control', async () => { await vaultManager.initialize(treasury.address, collateral.address, oracle.address, params, 'USDC/agEUR'); - await vaultManager.connect(governor).setDusts(0.1e9, 0.1e9); + await vaultManager.connect(governor).setDusts(0.1e9, 0.1e9, 0.1e9); await expect(vaultManager.connect(alice).togglePause()).to.be.reverted; await expect(vaultManager.connect(deployer).togglePause()).to.be.reverted; await expect(vaultManager.connect(proxyAdmin).togglePause()).to.be.reverted; @@ -177,7 +177,7 @@ contract('VaultManagerLiquidationBoost - ERC721', () => { describe('ERC721', () => { beforeEach(async () => { await vaultManager.initialize(treasury.address, collateral.address, oracle.address, params, 'USDC/agEUR'); - await vaultManager.connect(governor).setDusts(0.1e9, 0.1e9); + await vaultManager.connect(governor).setDusts(0.1e9, 0.1e9, 0.1e9); await vaultManager.connect(guardian).togglePause(); }); describe('getControlledVaults & vaultIDCount', () => { diff --git a/test/hardhat/vaultManager/vaultManagerPermit.test.ts b/test/hardhat/vaultManager/vaultManagerPermit.test.ts index da04d857..2c7267b7 100644 --- a/test/hardhat/vaultManager/vaultManagerPermit.test.ts +++ b/test/hardhat/vaultManager/vaultManagerPermit.test.ts @@ -104,7 +104,7 @@ contract('VaultManager - Permit', () => { oracle = await new MockOracle__factory(deployer).deploy(parseUnits('2', 18), treasury.address); await vaultManager.initialize(treasury.address, collateral.address, oracle.address, params, 'USDC/agEUR'); await vaultManager.connect(guardian).togglePause(); - await vaultManager.connect(governor).setDusts(0.1e9, 0.1e9); + await vaultManager.connect(governor).setDusts(0.1e9, 0.1e9, 0.1e9); name = await vaultManager.name(); }); diff --git a/test/hardhat/vaultManager/vaultManagerSetters.test.ts b/test/hardhat/vaultManager/vaultManagerSetters.test.ts index eab44ada..23eb8c2e 100644 --- a/test/hardhat/vaultManager/vaultManagerSetters.test.ts +++ b/test/hardhat/vaultManager/vaultManagerSetters.test.ts @@ -95,7 +95,7 @@ contract('VaultManagerLiquidationBoost - Setters', () => { oracle = await new MockOracle__factory(deployer).deploy(parseUnits('2', 18), treasury.address); await vaultManager.initialize(treasury.address, collateral.address, oracle.address, params, 'USDC/agEUR'); await vaultManager.connect(guardian).togglePause(); - await vaultManager.connect(governor).setDusts(0.1e9, 0.1e9); + await vaultManager.connect(governor).setDusts(0.1e9, 0.1e9, 0.1e9); }); describe('setUint64', () => {