From ba637c1a3c56057d37dc209fc4a10d3a78bfe02d Mon Sep 17 00:00:00 2001 From: 0xjoao Date: Fri, 31 Jul 2026 21:29:09 -0300 Subject: [PATCH 1/2] feat: add autoline synchronization --- README.md | 8 ++- src/DssAction.t.sol | 142 +++++++++++++++++++++++++++++++++++--------- src/DssExecLib.sol | 16 +++-- 3 files changed, 131 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index bcf328b..e2a1730 100644 --- a/README.md +++ b/README.md @@ -166,9 +166,9 @@ 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. +- `setIlkAutoLineParameters(bytes32 _ilk, uint256 _amount, uint256 _gap, uint256 _ttl)`: Configure the parameters for the Debt Ceiling auto line module and immediately execute the updated configuration. +- `setIlkAutoLineParameters(bytes32 _ilk, uint256 _amount, uint256 _gap)`: Configure the amount and gap parameters while keeping the existing TTL value, then immediately execute the updated configuration. +- `setIlkAutoLineDebtCeiling(bytes32 _ilk, uint256 _amount)`: Adjust the maximum debt ceiling while keeping the existing gap and TTL values, then immediately execute the updated configuration. - `removeIlkFromAutoLine(bytes32 _ilk)`: Remove the management of an ilk by the debt ceiling auto line module. - `setIlkMinVaultAmount(bytes32 _ilk, uint256 _amount)`: Set a collateral minimum vault amount. - `setIlkLiquidationPenalty(bytes32 _ilk, uint256 _pct_bps)`: Set a collateral liquidation penalty. @@ -182,6 +182,8 @@ Below is an outline of all functions used in the library. - `setLiquidationBreakerPriceTolerance(address _clip, uint256 _pct_bps)`: Set the tolerance for the liquidation circuit-breaker in the clipper IAM. - `setIlkStabilityFee(bytes32 _ilk, uint256 _rate)`: Set the stability fee for a given ilk. +The three AutoLine setters above call `DssAutoLine.exec` immediately after updating the configuration. Execution uses the rate stored in the Vat and retains AutoLine's existing valid no-op behavior. If execution reverts, the complete helper call is rolled back. Workflows that require separate configuration and execution should call `DssAutoLine` directly. `removeIlkFromAutoLine` remains configuration-only. + ### Abacus Management - `setLinearDecrease(address _calc, uint256 _duration)`: Set the variables in a LinearDecrease calculator. 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..66e2fd5 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,7 @@ 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 immediately execute AutoLine /// @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 +778,13 @@ 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 immediately execute AutoLine. + /// 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 +795,11 @@ 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 immediately execute AutoLine. + /// 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 +807,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 From 7306201338c2c2962a77940d5eb66b8a6cf2e7c7 Mon Sep 17 00:00:00 2001 From: 0xjoao Date: Fri, 31 Jul 2026 21:42:18 -0300 Subject: [PATCH 2/2] docs: clarify autoline exec behavior --- README.md | 10 ++++------ src/DssExecLib.sol | 9 ++++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e2a1730..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 and immediately execute the updated configuration. -- `setIlkAutoLineParameters(bytes32 _ilk, uint256 _amount, uint256 _gap)`: Configure the amount and gap parameters while keeping the existing TTL value, then immediately execute the updated configuration. -- `setIlkAutoLineDebtCeiling(bytes32 _ilk, uint256 _amount)`: Adjust the maximum debt ceiling while keeping the existing gap and TTL values, then immediately execute the updated configuration. -- `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. @@ -182,8 +182,6 @@ Below is an outline of all functions used in the library. - `setLiquidationBreakerPriceTolerance(address _clip, uint256 _pct_bps)`: Set the tolerance for the liquidation circuit-breaker in the clipper IAM. - `setIlkStabilityFee(bytes32 _ilk, uint256 _rate)`: Set the stability fee for a given ilk. -The three AutoLine setters above call `DssAutoLine.exec` immediately after updating the configuration. Execution uses the rate stored in the Vat and retains AutoLine's existing valid no-op behavior. If execution reverts, the complete helper call is rolled back. Workflows that require separate configuration and execution should call `DssAutoLine` directly. `removeIlkFromAutoLine` remains configuration-only. - ### Abacus Management - `setLinearDecrease(address _calc, uint256 _duration)`: Set the variables in a LinearDecrease calculator. diff --git a/src/DssExecLib.sol b/src/DssExecLib.sol index 66e2fd5..35550e2 100644 --- a/src/DssExecLib.sol +++ b/src/DssExecLib.sol @@ -770,7 +770,8 @@ library DssExecLib { updateCollateralPrice(_ilk); } - /// @dev Set the parameters for an ilk in the "MCD_IAM_AUTO_LINE" auto-line and immediately execute AutoLine + /// @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) @@ -783,7 +784,8 @@ library DssExecLib { IAMLike(_autoLine).exec(_ilk); } - /// @dev Set the parameters for an ilk in the "MCD_IAM_AUTO_LINE" auto-line and immediately execute AutoLine. + /// @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) @@ -798,7 +800,8 @@ library DssExecLib { IAMLike(_autoLine).exec(_ilk); } - /// @dev Set the debt ceiling for an ilk in the "MCD_IAM_AUTO_LINE" auto-line and immediately execute AutoLine. + /// @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)