Skip to content

Commit

Permalink
feat(auto-line-wipe): add wipe for multiple ilks
Browse files Browse the repository at this point in the history
  • Loading branch information
amusingaxl committed May 22, 2024
1 parent 85a7adc commit efec4e7
Show file tree
Hide file tree
Showing 2 changed files with 249 additions and 0 deletions.
99 changes: 99 additions & 0 deletions src/auto-line-wipe/MultiAutoLineWipeSpell.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-FileCopyrightText: © 2024 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.16;

import {DssEmergencySpell} from "../DssEmergencySpell.sol";

interface IlkRegistryLike {
function count() external view returns (uint256);
function list() external view returns (bytes32[] memory);
function list(uint256 start, uint256 end) external view returns (bytes32[] memory);
}

interface LineMomLike {
function autoLine() external view returns (address);
function ilks(bytes32 ilk) external view returns (uint256);
function wipe(bytes32 ilk) external;
}

interface AutoLineLike {
function ilks(bytes32 ilk)
external
view
returns (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc);
}

contract MultiAutoLineWipeSpell is DssEmergencySpell {
string public constant override description = "Emergency Spell | Multi AutoLine Wipe";

IlkRegistryLike public immutable ilkReg = IlkRegistryLike(_log.getAddress("ILK_REGISTRY"));
LineMomLike public immutable lineMom = LineMomLike(_log.getAddress("LINE_MOM"));

event Wipe(bytes32 indexed ilk);

/**
* @notice Wipes, when possible, all ilks from auto-line;
*/
function _emeregencyActions() internal override {
bytes32[] memory ilks = ilkReg.list();
_doWipe(ilks);
}

/**
* @notice Wipe all ilks in the batch from auto-line.
* @dev This is an escape hatch to prevent this spell from being blocked in case it would hit the block gas limit.
* In case `end` is greater than the ilk registry length, the iteration will be automatically capped.
* @param start The index to start the iteration (inclusive).
* @param end The index to stop the iteration (inclusive).
*/
function stopBatch(uint256 start, uint256 end) external {
uint256 maxEnd = ilkReg.count() - 1;
bytes32[] memory ilks = ilkReg.list(start, end < maxEnd ? end : maxEnd);
_doWipe(ilks);
}

/**
* @notice Stops, when possible, all OSMs that can be found from the `ilks` list.
* @param ilks The list of ilks to consider.
*/
function _doWipe(bytes32[] memory ilks) internal {
for (uint256 i = 0; i < ilks.length; i++) {
if (lineMom.ilks(ilks[i]) == 0) continue;

LineMomLike(lineMom).wipe(ilks[i]);
emit Wipe(ilks[i]);
}
}

/**
* @notice Returns whether the spell is done or not.
* @dev Checks if all possible ilks from the ilk registry are wiped from auto-line.
*/
function done() external view returns (bool) {
AutoLineLike autoLine = AutoLineLike(lineMom.autoLine());
bytes32[] memory ilks = ilkReg.list();
for (uint256 i = 0; i < ilks.length; i++) {
if (lineMom.ilks(ilks[i]) == 0) continue;

(uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc) = autoLine.ilks(ilks[i]);
// If any of the entries in auto-line has non zero values, then the spell can be cast again.
if (!(maxLine == 0 && gap == 0 && ttl == 0 && last == 0 && lastInc == 0)) {
return false;
}
}
return true;
}
}
150 changes: 150 additions & 0 deletions src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// SPDX-FileCopyrightText: © 2024 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.16;

import {stdStorage, StdStorage} from "forge-std/Test.sol";
import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol";
import {MultiAutoLineWipeSpell} from "./MultiAutoLineWipeSpell.sol";

interface LineMomLike {
function ilks(bytes32 ilk) external view returns (uint256);
function autoLine() external view returns (address);
}

interface IlkRegistryLike {
function count() external view returns (uint256);
function list() external view returns (bytes32[] memory);
function list(uint256 start, uint256 end) external view returns (bytes32[] memory);
}

interface AutoLineLike {
function ilks(bytes32 ilk)
external
view
returns (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc);
}

contract MultiAutoLineWipeSpellTest is DssTest {
using stdStorage for StdStorage;

address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
DssInstance dss;
address chief;
LineMomLike lineMom;
AutoLineLike autoLine;
IlkRegistryLike ilkReg;
MultiAutoLineWipeSpell spell;

mapping(bytes32 => bool) ilksToIgnore;

function setUp() public {
vm.createSelectFork("mainnet");

dss = MCD.loadFromChainlog(CHAINLOG);
MCD.giveAdminAccess(dss);
chief = dss.chainlog.getAddress("MCD_ADM");
lineMom = LineMomLike(dss.chainlog.getAddress("LINE_MOM"));
autoLine = AutoLineLike(lineMom.autoLine());
ilkReg = IlkRegistryLike(dss.chainlog.getAddress("ILK_REGISTRY"));
spell = new MultiAutoLineWipeSpell();

stdstore.target(chief).sig("hat()").checked_write(address(spell));

_initIlksToIgnore();

vm.makePersistent(chief);
}

/// @dev Ignore any of:
/// - ilk was not set in LineMom
/// - ilk is already wiped from auto-line
function _initIlksToIgnore() internal {
bytes32[] memory ilks = ilkReg.list();
for (uint256 i = 0; i < ilks.length; i++) {
string memory ilkStr = string(abi.encodePacked(ilks[i]));
if (lineMom.ilks(ilks[i]) == 0) {
ilksToIgnore[ilks[i]] = true;
emit log_named_string("Ignoring ilk | LineMom not set", ilkStr);
continue;
}

(uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc) = autoLine.ilks(ilks[i]);
if (maxLine == 0 && gap == 0 && ttl == 0 && last == 0 && lastInc == 0) {
ilksToIgnore[ilks[i]] = true;
emit log_named_string("Ignoring ilk | Already wiped", ilkStr);
continue;
}
}
}

function testMultiOracleStopOnSchedule() public {
_checkAutoLineWipedStatus({ilks: ilkReg.list(), expected: false});
assertFalse(spell.done(), "before: spell already done");

spell.schedule();

_checkAutoLineWipedStatus({ilks: ilkReg.list(), expected: true});
assertTrue(spell.done(), "after: spell not done");
}

function testMultiOracleStopInBatches_Fuzz(uint256 batchSize) public {
batchSize = bound(batchSize, 1, type(uint128).max);
uint256 count = ilkReg.count();
uint256 maxEnd = count - 1;
uint256 start = 0;
// End is inclusive, so we need to subtract 1
uint256 end = start + batchSize - 1;

_checkAutoLineWipedStatus({ilks: ilkReg.list(), expected: false});

while (start < count) {
spell.stopBatch(start, end);
_checkAutoLineWipedStatus({ilks: ilkReg.list(start, end < maxEnd ? end : maxEnd), expected: true});

start += batchSize;
end += batchSize;
}

// Sanity check: the test iterated over the entire ilk registry.
_checkAutoLineWipedStatus({ilks: ilkReg.list(), expected: true});
}

function testRevertMultiOracleStopWhenItDoesNotHaveTheHat() public {
stdstore.target(chief).sig("hat()").checked_write(address(0));

_checkAutoLineWipedStatus({ilks: ilkReg.list(), expected: false});

vm.expectRevert();
spell.schedule();

_checkAutoLineWipedStatus({ilks: ilkReg.list(), expected: false});
}

function _checkAutoLineWipedStatus(bytes32[] memory ilks, bool expected) internal view {
assertTrue(ilks.length > 0, "empty ilks list");

for (uint256 i = 0; i < ilks.length; i++) {
if (ilksToIgnore[ilks[i]]) continue;

(uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc) = autoLine.ilks(ilks[i]);
assertEq(
maxLine == 0 && gap == 0 && ttl == 0 && last == 0 && lastInc == 0,
expected,
string(abi.encodePacked("invalid wiped status: ", ilks[i]))
);
}
}
}

0 comments on commit efec4e7

Please sign in to comment.