diff --git a/README.md b/README.md index bcf328b..0f8d1ad 100644 --- a/README.md +++ b/README.md @@ -166,10 +166,10 @@ Below is an outline of all functions used in the library. - `increaseIlkDebtCeiling(bytes32 _ilk, uint256 _amount, bool _global)`: Raise the debt ceiling of a particular ilk. - `decreaseIlkDebtCeiling(bytes32 _ilk, uint256 _amount, bool _global)`: Lower the debt ceiling of a particular ilk. - `setRWAIlkDebtCeiling(bytes32 _ilk, uint256 _ceiling, uint256 _price)`: Set the debt ceiling for a RWA collateral. This requires also a new oracle price. -- `setIlkAutoLineParameters(bytes32 _ilk, uint256 _amount, uint256 _gap, uint256 _ttl)`: Configure the parameters for the Debt Ceiling auto line module for a particular ilk. -- `setIlkAutoLineParameters(bytes32 _ilk, uint256 _amount, uint256 _gap)`: Configure the amount and gap parameters for the Debt Ceiling auto line module while keeping the existing TTL value. -- `setIlkAutoLineDebtCeiling(bytes32 _ilk, uint256 _amount)`: Adjust the debt ceiling in the auto line module. -- `removeIlkFromAutoLine(bytes32 _ilk)`: Remove the management of an ilk by the debt ceiling auto line module. +- `setIlkAutoLineParameters(bytes32 _ilk, uint256 _amount, uint256 _gap, uint256 _ttl)`: Configure the Debt Ceiling auto line parameters, then call `DssAutoLine.exec(ilk)` to update the live Vat debt ceilings when AutoLine permits a change. +- `setIlkAutoLineParameters(bytes32 _ilk, uint256 _amount, uint256 _gap)`: Configure the amount and gap while keeping the existing TTL, then call `DssAutoLine.exec(ilk)` to update the live Vat debt ceilings when AutoLine permits a change. +- `setIlkAutoLineDebtCeiling(bytes32 _ilk, uint256 _amount)`: Configure the maximum debt ceiling while keeping the existing gap and TTL, then call `DssAutoLine.exec(ilk)` to update the live Vat debt ceilings when AutoLine permits a change. +- `removeIlkFromAutoLine(bytes32 _ilk)`: Remove the AutoLine configuration for an ilk without updating the live Vat debt ceilings. - `setIlkMinVaultAmount(bytes32 _ilk, uint256 _amount)`: Set a collateral minimum vault amount. - `setIlkLiquidationPenalty(bytes32 _ilk, uint256 _pct_bps)`: Set a collateral liquidation penalty. - `setIlkMaxLiquidationAmount(bytes32 _ilk, uint256 _amount)`: Set max DAI amount for liquidation per vault for a collateral type. diff --git a/src/DssAction.t.sol b/src/DssAction.t.sol index 6507a79..383597a 100644 --- a/src/DssAction.t.sol +++ b/src/DssAction.t.sol @@ -738,56 +738,144 @@ contract DssActionTest is Test { } function test_setIlkAutoLineParameters() public { - action.setIlkAutoLineParameters_test("gold", 150 * MILLION, 5 * MILLION, 10000); // Setup + (,,, uint256 initialIlkLine,) = vat.ilks("gold"); + uint256 initialGlobalLine = vat.Line(); - (,,, uint256 line,) = vat.ilks("gold"); - assertEq(line, 1000 * RAD); // does not change line + action.setIlkAutoLineParameters_test("gold", 150 * MILLION, 5 * MILLION, 10000); // Increase, gap-bound + + (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc) = autoLine.ilks("gold"); + assertEq(maxLine, 150 * MILLION * RAD); + assertEq(gap, 5 * MILLION * RAD); + assertEq(uint256(ttl), 10000); + assertEq(uint256(last), block.number); // Records execution block + assertEq(uint256(lastInc), block.timestamp); // Records increase timestamp + + (,,, uint256 ilkLine,) = vat.ilks("gold"); + assertEq(ilkLine, 5 * MILLION * RAD); + assertEq(vat.Line(), initialGlobalLine + ilkLine - initialIlkLine); // also increased + + action.setIlkAutoLineParameters_test("gold", 2 * MILLION, 1 * MILLION, 10000); // Decrease, gap-bound + + (maxLine, gap, ttl, last, lastInc) = autoLine.ilks("gold"); + assertEq(maxLine, 2 * MILLION * RAD); + assertEq(gap, 1 * MILLION * RAD); + assertEq(uint256(ttl), 10000); + assertEq(uint256(last), block.number); // Records execution block + assertEq(uint256(lastInc), 0); // Decreases do not update lastInc + + (,,, ilkLine,) = vat.ilks("gold"); + assertEq(ilkLine, 1 * MILLION * RAD); + assertEq(vat.Line(), initialGlobalLine + ilkLine - initialIlkLine); // also decreased - autoLine.exec("gold"); - (,,, line,) = vat.ilks("gold"); - assertEq(line, 5 * MILLION * RAD); // Change to match the gap + action.setIlkAutoLineParameters_test("gold", 2 * MILLION, 1 * MILLION, 10000); // Unchanged target + + (maxLine, gap, ttl, last, lastInc) = autoLine.ilks("gold"); + assertEq(maxLine, 2 * MILLION * RAD); + assertEq(gap, 1 * MILLION * RAD); + assertEq(uint256(ttl), 10000); + assertEq(uint256(last), 0); // Reset by setIlk before exec no-op + assertEq(uint256(lastInc), 0); // Reset by setIlk before exec no-op + + (,,, ilkLine,) = vat.ilks("gold"); + assertEq(ilkLine, 1 * MILLION * RAD); + assertEq(vat.Line(), initialGlobalLine + ilkLine - initialIlkLine); // unchanged } function test_setIlkAutoLineParametersKeepTtl() public { - // First set up with initial values including ttl - action.setIlkAutoLineParameters_test("gold", 150 * MILLION, 5 * MILLION, 10000); + action.setIlkAutoLineParameters_test("gold", 150 * MILLION, 5 * MILLION, 10000); // Setup - // Get the initial ttl value (,, uint48 initialTtl,,) = autoLine.ilks("gold"); assertEq(uint256(initialTtl), 10000); + (,,, uint256 initialIlkLine,) = vat.ilks("gold"); + uint256 initialGlobalLine = vat.Line(); - // Now use the overloaded function that should keep the ttl unchanged - action.setIlkAutoLineParameters_test("gold", 200 * MILLION, 10 * MILLION); + action.setIlkAutoLineParameters_test("gold", 200 * MILLION, 10 * MILLION); // Increase, keep ttl - // Verify line and gap were updated but ttl remains the same - (uint256 line, uint256 gap, uint48 ttl,,) = autoLine.ilks("gold"); - assertEq(line, 200 * MILLION * RAD); + (uint256 maxLine, uint256 gap, uint48 ttl,,) = autoLine.ilks("gold"); + assertEq(maxLine, 200 * MILLION * RAD); assertEq(gap, 10 * MILLION * RAD); - assertEq(uint256(ttl), initialTtl); // ttl should remain unchanged + assertEq(uint256(ttl), initialTtl); // ttl unchanged + + (,,, uint256 ilkLine,) = vat.ilks("gold"); + assertEq(ilkLine, 10 * MILLION * RAD); + assertEq(vat.Line(), initialGlobalLine + ilkLine - initialIlkLine); // also increased } - function test_RevertSetIlkAutoLineParametersKeepTtl_WhenNotConfigured() public { + function test_revert_setIlkAutoLineParametersKeepTtl_WhenNotConfigured() public { vm.expectRevert(); - action.setIlkAutoLineParameters_test("gold", 200 * MILLION, 10 * MILLION); + action.setIlkAutoLineParameters_test("gold", 200 * MILLION, 10 * MILLION); // Fail if not configured } function test_setIlkAutoLineDebtCeiling() public { - action.setIlkAutoLineParameters_test("gold", 1, 5 * MILLION, 10000); // gap and ttl must be configured already - action.setIlkAutoLineDebtCeiling_test("gold", 150 * MILLION); // Setup + action.setIlkAutoLineParameters_test("gold", 2 * MILLION, 5 * MILLION, 10000); // Setup, maxLine-bound - (,,, uint256 line,) = vat.ilks("gold"); - assertEq(line, 1000 * RAD); // does not change line + (,,, uint256 initialIlkLine,) = vat.ilks("gold"); + uint256 initialGlobalLine = vat.Line(); + + action.setIlkAutoLineDebtCeiling_test("gold", 4 * MILLION); // Increase - autoLine.exec("gold"); - (,,, line,) = vat.ilks("gold"); - assertEq(line, 5 * MILLION * RAD); // Change to match the gap + (uint256 maxLine, uint256 gap, uint48 ttl,,) = autoLine.ilks("gold"); + assertEq(maxLine, 4 * MILLION * RAD); + assertEq(gap, 5 * MILLION * RAD); // unchanged + assertEq(uint256(ttl), 10000); // unchanged + + (,,, uint256 ilkLine,) = vat.ilks("gold"); + assertEq(ilkLine, 4 * MILLION * RAD); + assertEq(vat.Line(), initialGlobalLine + ilkLine - initialIlkLine); // also increased + + action.setIlkAutoLineDebtCeiling_test("gold", 3 * MILLION); // Decrease + + (maxLine, gap, ttl,,) = autoLine.ilks("gold"); + assertEq(maxLine, 3 * MILLION * RAD); + assertEq(gap, 5 * MILLION * RAD); // unchanged + assertEq(uint256(ttl), 10000); // unchanged + + (,,, ilkLine,) = vat.ilks("gold"); + assertEq(ilkLine, 3 * MILLION * RAD); + assertEq(vat.Line(), initialGlobalLine + ilkLine - initialIlkLine); // also decreased + } + + function test_revert_setIlkAutoLineParameters_WhenAutoLineIsNotAuthorizedOnVat() public { + (uint256 initialMaxLine, uint256 initialGap, uint48 initialTtl, uint48 initialLast, uint48 initialLastInc) = + autoLine.ilks("gold"); + (,,, uint256 initialIlkLine,) = vat.ilks("gold"); + uint256 initialGlobalLine = vat.Line(); + + vat.deny(address(autoLine)); // Remove AutoLine authorization + + vm.expectRevert("Vat/not-authorized"); + action.setIlkAutoLineParameters_test("gold", 150 * MILLION, 5 * MILLION, 10000); // Fail on exec + + (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc) = autoLine.ilks("gold"); + assertEq(maxLine, initialMaxLine); // setIlk rolled back + assertEq(gap, initialGap); + assertEq(uint256(ttl), uint256(initialTtl)); + assertEq(uint256(last), uint256(initialLast)); + assertEq(uint256(lastInc), uint256(initialLastInc)); + + (,,, uint256 ilkLine,) = vat.ilks("gold"); + assertEq(ilkLine, initialIlkLine); + assertEq(vat.Line(), initialGlobalLine); // live ceilings unchanged } function test_setRemoveIlkFromAutoLine() public { - action.setIlkAutoLineParameters_test("gold", 100 * MILLION, 5 * MILLION, 10000); // gap and ttl must be configured already - action.removeIlkFromAutoLine_test("gold"); + action.setIlkAutoLineParameters_test("gold", 100 * MILLION, 5 * MILLION, 10000); // Setup + + (,,, uint256 initialIlkLine,) = vat.ilks("gold"); + uint256 initialGlobalLine = vat.Line(); + + action.removeIlkFromAutoLine_test("gold"); // Remove configuration only + + (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc) = autoLine.ilks("gold"); + assertEq(maxLine, 0); // AutoLine configuration removed + assertEq(gap, 0); + assertEq(uint256(ttl), 0); + assertEq(uint256(last), 0); + assertEq(uint256(lastInc), 0); - assertEq(autoLine.exec("gold"), 1000 * RAD); + (,,, uint256 ilkLine,) = vat.ilks("gold"); + assertEq(ilkLine, initialIlkLine); + assertEq(vat.Line(), initialGlobalLine); // live ceilings unchanged } function test_setIlkMinVaultAmountLt() public { diff --git a/src/DssExecLib.sol b/src/DssExecLib.sol index 57eb9c2..35550e2 100644 --- a/src/DssExecLib.sol +++ b/src/DssExecLib.sol @@ -118,6 +118,7 @@ interface IAMLike { function ilks(bytes32) external view returns (uint256, uint256, uint48, uint48, uint48); function setIlk(bytes32, uint256, uint256, uint256) external; function remIlk(bytes32) external; + function exec(bytes32) external returns (uint256); } interface LerpFactoryLike { @@ -769,7 +770,8 @@ library DssExecLib { updateCollateralPrice(_ilk); } - /// @dev Set the parameters for an ilk in the "MCD_IAM_AUTO_LINE" auto-line + /// @dev Set the parameters for an ilk in the "MCD_IAM_AUTO_LINE" auto-line and call exec. + /// exec updates the live Vat debt ceilings when AutoLine permits a change. /// @param _ilk The ilk to update (ex. bytes32("ETH-A")) /// @param _amount The Maximum value (ex. 100m amount == 100000000) /// @param _gap The amount per step (ex. 5m gap == 5000000) @@ -777,11 +779,14 @@ library DssExecLib { function setIlkAutoLineParameters(bytes32 _ilk, uint256 _amount, uint256 _gap, uint256 _ttl) public { require(_amount < WAD); // "LibDssExec/incorrect-auto-line-amount-precision" require(_gap < WAD); // "LibDssExec/incorrect-auto-line-gap-precision" - IAMLike(autoLine()).setIlk(_ilk, _amount * RAD, _gap * RAD, _ttl); + address _autoLine = autoLine(); + IAMLike(_autoLine).setIlk(_ilk, _amount * RAD, _gap * RAD, _ttl); + IAMLike(_autoLine).exec(_ilk); } - /// @dev Set the parameters for an ilk in the "MCD_IAM_AUTO_LINE" auto-line. Keeps the ttl unchanged. - /// Requires the auto-line to be already configured for the ilk. + /// @dev Set the parameters for an ilk in the "MCD_IAM_AUTO_LINE" auto-line and call exec. + /// exec updates the live Vat debt ceilings when AutoLine permits a change. + /// Keeps the ttl unchanged. Requires the auto-line to be already configured for the ilk. /// @param _ilk The ilk to update (ex. bytes32("ETH-A")) /// @param _amount The Maximum value (ex. 100m amount == 100000000) /// @param _gap The amount per step (ex. 5m gap == 5000000) @@ -792,9 +797,12 @@ library DssExecLib { (,, uint48 ttl,,) = IAMLike(_autoLine).ilks(_ilk); require(ttl != 0); // "LibDssExec/auto-line-not-configured" IAMLike(_autoLine).setIlk(_ilk, _amount * RAD, _gap * RAD, uint256(ttl)); + IAMLike(_autoLine).exec(_ilk); } - /// @dev Set the debt ceiling for an ilk in the "MCD_IAM_AUTO_LINE" auto-line without updating the time values + /// @dev Set the debt ceiling for an ilk in the "MCD_IAM_AUTO_LINE" auto-line and call exec. + /// exec updates the live Vat debt ceilings when AutoLine permits a change. + /// Keeps the gap and ttl unchanged. Requires the auto-line to be already configured for the ilk. /// @param _ilk The ilk to update (ex. bytes32("ETH-A")) /// @param _amount The Maximum value (ex. 100m amount == 100000000) function setIlkAutoLineDebtCeiling(bytes32 _ilk, uint256 _amount) public { @@ -802,6 +810,7 @@ library DssExecLib { (, uint256 gap, uint48 ttl,,) = IAMLike(_autoLine).ilks(_ilk); require(gap != 0 && ttl != 0); // "LibDssExec/auto-line-not-configured" IAMLike(_autoLine).setIlk(_ilk, _amount * RAD, uint256(gap), uint256(ttl)); + IAMLike(_autoLine).exec(_ilk); } /// @dev Remove an ilk in the "MCD_IAM_AUTO_LINE" auto-line