Skip to content
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

Add priority emergency spells #1

Merged
merged 38 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8a9cb25
feat(osm-stop): add single and universal OSM stop spell
amusingaxl May 7, 2024
89a518a
feat(clip-breaker): add universal clip breaker spell
amusingaxl May 8, 2024
b002456
refactor(clip-breaker): add more error handling cases
amusingaxl May 8, 2024
5bc1ab3
refactor(osm-stop): add more error handling cases
amusingaxl May 8, 2024
ba2dfc9
refactor: remove parametrizable expiration
amusingaxl May 9, 2024
43b2d04
refactor: rename `_onSchedule` to `_emergencyActions`
amusingaxl May 9, 2024
fc6b1b9
refactor: rename test files
amusingaxl May 9, 2024
87387db
docs(osm-stop/universal): add documentation to ignored ilks
amusingaxl May 9, 2024
fa32048
feat(osm-top): add batching escape hatch
amusingaxl May 9, 2024
2160801
refactor(osm-stop): remove unused interface import
amusingaxl May 9, 2024
89dcc8c
feat(clip-breaker): add batching escape hatch
amusingaxl May 9, 2024
9587bef
refactor(osm-stop): add missing params to event payload
amusingaxl May 9, 2024
8a9382e
chore: update CI pipeline
amusingaxl May 9, 2024
1b9b6a9
refactor(osm-stop): tests to use factory to deploy the spell
amusingaxl May 9, 2024
69d1027
feat(clip-breaker): add spell to set the clip breaker for a single ilk
amusingaxl May 9, 2024
5d70199
feat(auto-line-wipe): add spell to wipe a single ilk
amusingaxl May 13, 2024
7cd769c
feat: add meaningful `done()` methods
amusingaxl May 16, 2024
206c15c
refactor: reorganize contracts and update natspec
amusingaxl May 17, 2024
d1bee94
refactor: improve universal spells structure and comments
amusingaxl May 17, 2024
6b421dd
refactor: rename "universal" spells to "multi"
amusingaxl May 22, 2024
85a7adc
feat: guard against empty ilks list checks
amusingaxl May 22, 2024
efec4e7
feat(auto-line-wipe): add wipe for multiple ilks
amusingaxl May 22, 2024
5b57421
feat(ddm-disable): add single DDM disable spell
amusingaxl May 22, 2024
1a375b5
refactor(ddm-disable): change `done()` expression for better readability
amusingaxl May 23, 2024
2eeec5b
refactor: fix typo in function name
amusingaxl Jun 25, 2024
cfafa6b
refactor(single-auto-line-wipe): remove `Wipe` event parameters
amusingaxl Jun 25, 2024
359fc0d
refactor(single-ddm-disable): remove unused function from interface
amusingaxl Jun 25, 2024
50493ac
refactor(base-emergency-spell): change tag to `extcodehash(address(th…
amusingaxl Jun 25, 2024
d594174
refactor: remove inline assembly to fetch the code hash
amusingaxl Jun 26, 2024
7ad2715
docs: fix typo in comment
amusingaxl Jul 29, 2024
ce34c28
docs: add README
amusingaxl Jul 29, 2024
0372ffb
refactor: remove unneeded status checks after revert
amusingaxl Sep 19, 2024
04fe1d7
refactor: make `nextCastTime` return `type(uint256).max`
amusingaxl Sep 19, 2024
bc06eeb
docs: add relevant chainlog key references to README
amusingaxl Sep 19, 2024
c810d13
refactor: address audit findings
amusingaxl Oct 15, 2024
a8e2beb
docs: update README
amusingaxl Oct 15, 2024
75c5a06
refactor: additional improvements
amusingaxl Oct 16, 2024
d9fc4ea
fix: apply suggestions from code review
amusingaxl Oct 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
name: test

on: workflow_dispatch
on:
push:
branches:
- master
- main
pull_request:

env:
FOUNDRY_PROFILE: ci
ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }}

jobs:
check:
Expand Down
74 changes: 74 additions & 0 deletions src/DssEmergencySpell.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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;
oddaf marked this conversation as resolved.
Show resolved Hide resolved

interface ChainlogLike {
function getAddress(bytes32 key) external view returns (address);
}

interface DssEmergencySpellLike {
function action() external view returns (address);
function cast() external;
function description() external view returns (string memory);
function done() external view returns (bool);
function eta() external view returns (uint256);
function expiration() external view returns (uint256);
function log() external view returns (address);
function nextCastTime() external view returns (uint256);
function officeHours() external view returns (bool);
function pause() external view returns (address);
function schedule() external;
function sig() external view returns (bytes memory);
function tag() external view returns (bytes32);
}

abstract contract DssEmergencySpell is DssEmergencySpellLike {
ChainlogLike internal constant _log = ChainlogLike(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F);

address public constant log = address(_log);
uint256 public constant eta = 0;
bytes public constant sig = "";
uint256 public constant expiration = type(uint256).max;
// @notice Office Hours is always `false` for emergency spells.
bool public constant officeHours = false;
address public immutable action = address(this);
bytes32 public immutable tag = keccak256(abi.encodePacked(address(this)));
oddaf marked this conversation as resolved.
Show resolved Hide resolved
address public immutable pause = ChainlogLike(log).getAddress("MCD_PAUSE");
amusingaxl marked this conversation as resolved.
Show resolved Hide resolved
uint256 public immutable nextCastTime = block.timestamp;


/**
* @notice Triggers the emergency actions of the spell.
* @dev Emergency spells are triggered when scheduled.
* This function maintains the name for compatibility with regular spells, however nothing is actually being
* scheduled. Emergency spell take affect immediately, so there is no need to call `pause.plot()`.
*/
function schedule() external {
_emeregencyActions();
oddaf marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @notice No-op.
* @dev this function exists only to keep interface compatibility with regular spells.
*/
function cast() external {}

/**
* @notice Implements the emergency actions to be triggered by the spell.
*/
function _emeregencyActions() internal virtual;
oddaf marked this conversation as resolved.
Show resolved Hide resolved

}
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;
oddaf marked this conversation as resolved.
Show resolved Hide resolved

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);
Copy link

@SidestreamColdMelon SidestreamColdMelon Sep 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I would recommend to emit uniform event from all emergency contracts, e.g.: EmergencyAction(bytes32 indexed type, bytes32 indexed what)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you believe this would be beneficial?


/**
* @notice Wipes, when possible, all ilks from auto-line;
*/
function _emeregencyActions() internal override {
oddaf marked this conversation as resolved.
Show resolved Hide resolved
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.
amusingaxl marked this conversation as resolved.
Show resolved Hide resolved
* @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]);
oddaf marked this conversation as resolved.
Show resolved Hide resolved
amusingaxl marked this conversation as resolved.
Show resolved Hide resolved
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";
amusingaxl marked this conversation as resolved.
Show resolved Hide resolved
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]))
);
}
}
}
Loading