From 8a9cb255f7df132ab83739b817b3b0d8ec437ea8 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Tue, 7 May 2024 16:27:49 -0300
Subject: [PATCH 01/38] feat(osm-stop): add single and universal OSM stop spell

---
 src/DssEmergencySpell.sol                     | 77 +++++++++++++++
 .../SingleOsmStopSpell.integration.t.sol      | 75 +++++++++++++++
 src/osm-stop/SingleOsmStopSpell.sol           | 54 +++++++++++
 .../UniversalOsmStopSpell.integration.t.sol   | 95 +++++++++++++++++++
 src/osm-stop/UniversalOsmStopSpell.sol        | 56 +++++++++++
 5 files changed, 357 insertions(+)
 create mode 100644 src/DssEmergencySpell.sol
 create mode 100644 src/osm-stop/SingleOsmStopSpell.integration.t.sol
 create mode 100644 src/osm-stop/SingleOsmStopSpell.sol
 create mode 100644 src/osm-stop/UniversalOsmStopSpell.integration.t.sol
 create mode 100644 src/osm-stop/UniversalOsmStopSpell.sol

diff --git a/src/DssEmergencySpell.sol b/src/DssEmergencySpell.sol
new file mode 100644
index 0000000..05635fb
--- /dev/null
+++ b/src/DssEmergencySpell.sol
@@ -0,0 +1,77 @@
+// 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;
+
+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 = "";
+    bool public constant done = false;
+    address public immutable action = address(this);
+    bytes32 public immutable tag = keccak256(abi.encodePacked(address(this)));
+    address public immutable pause = ChainlogLike(log).getAddress("MCD_PAUSE");
+    uint256 public immutable expiration;
+
+    // @notice Office Hours is always `false` for emergency spells.
+    bool public constant officeHours = false;
+
+    constructor(uint256 _expiration) {
+        expiration = _expiration;
+    }
+
+    function nextCastTime() external virtual view returns (uint256) {
+        return block.timestamp;
+    }
+
+    /**
+     * @notice Emergency spell are triggered when scheduled.
+     * @dev 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 virtual {
+        require(block.timestamp <= expiration, "dss-emergency-spell/expired");
+        _onSchedule();
+    }
+
+    function _onSchedule() internal virtual;
+
+    /**
+     * @notice This function is a no-op. It exists only to keep interface compatibility with regular spells.
+     */
+    function cast() external virtual {}
+}
diff --git a/src/osm-stop/SingleOsmStopSpell.integration.t.sol b/src/osm-stop/SingleOsmStopSpell.integration.t.sol
new file mode 100644
index 0000000..103023d
--- /dev/null
+++ b/src/osm-stop/SingleOsmStopSpell.integration.t.sol
@@ -0,0 +1,75 @@
+// 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 {DssEmergencySpellLike} from "../DssEmergencySpell.sol";
+import {SingleOsmStopSpell} from "./SingleOsmStopSpell.sol";
+
+interface OsmMomLike {
+    function osms(bytes32) external view returns (address);
+}
+
+interface OsmLike {
+    function stopped() external view returns (uint256);
+}
+
+contract SingleOsmStopTest is DssTest {
+    using stdStorage for StdStorage;
+
+    address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
+    DssInstance dss;
+    address chief;
+    bytes32 ilk = "ETH-A";
+    OsmMomLike osmMom;
+    OsmLike osm;
+    DssEmergencySpellLike spell;
+
+    function setUp() public {
+        vm.createSelectFork("mainnet");
+
+        dss = MCD.loadFromChainlog(CHAINLOG);
+        MCD.giveAdminAccess(dss);
+        chief = dss.chainlog.getAddress("MCD_ADM");
+        osmMom = OsmMomLike(dss.chainlog.getAddress("OSM_MOM"));
+        osm = OsmLike(osmMom.osms(ilk));
+        spell = new SingleOsmStopSpell(ilk);
+
+        stdstore.target(chief).sig("hat()").checked_write(address(spell));
+
+        vm.makePersistent(chief);
+    }
+
+    function testOracleStopOnSchedule() public {
+        assertEq(osm.stopped(), 0, "before: oracle already frozen");
+
+        spell.schedule();
+
+        assertEq(osm.stopped(), 1, "after: oracle not frozen");
+    }
+
+    function testRevertOracleStopWhenItDoesNotHaveTheHat() public {
+        stdstore.target(chief).sig("hat()").checked_write(address(0));
+
+        assertEq(osm.stopped(), 0, "before: oracle already frozen");
+
+        vm.expectRevert();
+        spell.schedule();
+
+        assertEq(osm.stopped(), 0, "after: oracle frozen unexpectedly");
+    }
+}
diff --git a/src/osm-stop/SingleOsmStopSpell.sol b/src/osm-stop/SingleOsmStopSpell.sol
new file mode 100644
index 0000000..f7832d3
--- /dev/null
+++ b/src/osm-stop/SingleOsmStopSpell.sol
@@ -0,0 +1,54 @@
+// 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 OsmMomLike {
+    function stop(bytes32 ilk) external;
+}
+
+contract SingleOsmStopSpell is DssEmergencySpell {
+    OsmMomLike public immutable osmMom = OsmMomLike(_log.getAddress("OSM_MOM"));
+    bytes32 public immutable ilk;
+
+    event Stop();
+
+    constructor(bytes32 _ilk)
+        // In practice, this spell would never expire
+        DssEmergencySpell(type(uint256).max)
+    {
+        ilk = _ilk;
+    }
+
+    function description() external view returns (string memory) {
+        return string(abi.encodePacked("Emergency Spell | OSM Stop: ", ilk));
+    }
+
+    function _onSchedule() internal override {
+        osmMom.stop(ilk);
+        emit Stop();
+    }
+}
+
+contract SingleOsmStopFactory {
+    event Deploy(bytes32 indexed ilk, address spell);
+
+    function deploy(bytes32 ilk) external returns (address spell) {
+        spell = address(new SingleOsmStopSpell(ilk));
+        emit Deploy(ilk, spell);
+    }
+}
diff --git a/src/osm-stop/UniversalOsmStopSpell.integration.t.sol b/src/osm-stop/UniversalOsmStopSpell.integration.t.sol
new file mode 100644
index 0000000..77b9b44
--- /dev/null
+++ b/src/osm-stop/UniversalOsmStopSpell.integration.t.sol
@@ -0,0 +1,95 @@
+// 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 {DssEmergencySpellLike} from "../DssEmergencySpell.sol";
+import {UniversalOsmStopSpell} from "./UniversalOsmStopSpell.sol";
+
+interface OsmMomLike {
+    function osms(bytes32) external view returns (address);
+}
+
+interface OsmLike {
+    function stopped() external view returns (uint256);
+    function osms(bytes32 ilk) external view returns (address);
+}
+
+interface IlkRegistryLike {
+    function list() external view returns (bytes32[] memory);
+    function pip(bytes32 ilk) external view returns (address);
+}
+
+contract UniversalOsmStopSpellTest is DssTest {
+    using stdStorage for StdStorage;
+
+    address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
+    DssInstance dss;
+    address chief;
+    OsmMomLike osmMom;
+    IlkRegistryLike ilkReg;
+    OsmLike osm;
+    DssEmergencySpellLike spell;
+
+    function setUp() public {
+        vm.createSelectFork("mainnet");
+
+        dss = MCD.loadFromChainlog(CHAINLOG);
+        MCD.giveAdminAccess(dss);
+        chief = dss.chainlog.getAddress("MCD_ADM");
+        osmMom = OsmMomLike(dss.chainlog.getAddress("OSM_MOM"));
+        ilkReg = IlkRegistryLike(dss.chainlog.getAddress("ILK_REGISTRY"));
+        spell = new UniversalOsmStopSpell();
+
+        stdstore.target(chief).sig("hat()").checked_write(address(spell));
+
+        vm.makePersistent(chief);
+    }
+
+    function testUniversalOracleStopOnSchedule() public {
+        _checkAllOsmStoppedStatus({expected: 0});
+
+        spell.schedule();
+
+        _checkAllOsmStoppedStatus({expected: 1});
+    }
+
+    function testRevertUniversalOracleStopWhenItDoesNotHaveTheHat() public {
+        stdstore.target(chief).sig("hat()").checked_write(address(0));
+
+        _checkAllOsmStoppedStatus({expected: 0});
+
+        vm.expectRevert();
+        spell.schedule();
+
+        _checkAllOsmStoppedStatus({expected: 0});
+    }
+
+    function _checkAllOsmStoppedStatus(uint256 expected) internal view {
+        bytes32[] memory ilks = ilkReg.list();
+        for (uint256 i = 0; i < ilks.length; i++) {
+            if (osmMom.osms(ilks[i]) != address(0)) {
+                OsmLike pip = OsmLike(ilkReg.pip(ilks[i]));
+                try pip.stopped() returns (uint256 stopped) {
+                    assertEq(stopped, expected, string(abi.encodePacked("invalid stopped state: ", ilks[i])));
+                } catch {
+                    // Most likely not an OSM.
+                }
+            }
+        }
+    }
+}
diff --git a/src/osm-stop/UniversalOsmStopSpell.sol b/src/osm-stop/UniversalOsmStopSpell.sol
new file mode 100644
index 0000000..856c45d
--- /dev/null
+++ b/src/osm-stop/UniversalOsmStopSpell.sol
@@ -0,0 +1,56 @@
+// 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 OsmMomLike {
+    function stop(bytes32 ilk) external;
+    function osms(bytes32 ilk) external view returns (address);
+}
+
+interface IlkRegistryLike {
+    function list() external view returns (bytes32[] memory);
+}
+
+contract UniversalOsmStopSpell is DssEmergencySpell {
+    OsmMomLike public immutable osmMom = OsmMomLike(_log.getAddress("OSM_MOM"));
+    IlkRegistryLike public immutable ilkReg = IlkRegistryLike(_log.getAddress("ILK_REGISTRY"));
+
+    string public constant override description = "Emergency Spell | Universal OSM Stop";
+
+    event Stop(bytes32 ilk);
+
+    constructor()
+        // In practice, this spell would never expire
+        DssEmergencySpell(type(uint256).max)
+    {}
+
+    function _onSchedule() internal override {
+        bytes32[] memory ilks = ilkReg.list();
+        for (uint256 i = 0; i < ilks.length; i++) {
+            if (osmMom.osms(ilks[i]) != address(0)) {
+                // There might be some duplicate calls to the same OSM, however they are idempotent.
+                try OsmMomLike(osmMom).stop(ilks[i]) {
+                    emit Stop(ilks[i]);
+                } catch Error(string memory reason) {
+                    // Ignore any failing calls to `osmMom.stop` with no error being returned.
+                    require(bytes(reason).length == 0, reason);
+                }
+            }
+        }
+    }
+}

From 89a518a0ea41d14e783ad6f8795b43a51ab6ec32 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Wed, 8 May 2024 19:50:58 -0300
Subject: [PATCH 02/38] feat(clip-breaker): add universal clip breaker spell

---
 ...niversalClipBreakerSpell.integration.t.sol | 164 ++++++++++++++++++
 .../UniversalClipBreakerSpell.sol             |  68 ++++++++
 2 files changed, 232 insertions(+)
 create mode 100644 src/clip-breaker/UniversalClipBreakerSpell.integration.t.sol
 create mode 100644 src/clip-breaker/UniversalClipBreakerSpell.sol

diff --git a/src/clip-breaker/UniversalClipBreakerSpell.integration.t.sol b/src/clip-breaker/UniversalClipBreakerSpell.integration.t.sol
new file mode 100644
index 0000000..4cdfd72
--- /dev/null
+++ b/src/clip-breaker/UniversalClipBreakerSpell.integration.t.sol
@@ -0,0 +1,164 @@
+// 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 {DssEmergencySpellLike} from "../DssEmergencySpell.sol";
+import {UniversalClipBreakerSpell} from "./UniversalClipBreakerSpell.sol";
+
+interface IlkRegistryLike {
+    function list() external view returns (bytes32[] memory);
+    function xlip(bytes32 ilk) external view returns (address);
+}
+
+interface WardsLike {
+    function wards(address who) external view returns (uint256);
+}
+
+interface ClipLike {
+    function stopped() external view returns (uint256);
+}
+
+contract UniversalClipBreakerSpellTest is DssTest {
+    using stdStorage for StdStorage;
+
+    address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
+    DssInstance dss;
+    address chief;
+    IlkRegistryLike ilkReg;
+    address clipperMom;
+    address spell;
+
+    mapping(bytes32 => bool) ilksToIgnore;
+
+    function setUp() public {
+        vm.createSelectFork("mainnet");
+
+        dss = MCD.loadFromChainlog(CHAINLOG);
+        MCD.giveAdminAccess(dss);
+        chief = dss.chainlog.getAddress("MCD_ADM");
+        ilkReg = IlkRegistryLike(dss.chainlog.getAddress("ILK_REGISTRY"));
+        clipperMom = dss.chainlog.getAddress("CLIPPER_MOM");
+        spell = address(new UniversalClipBreakerSpell());
+
+        stdstore.target(chief).sig("hat()").checked_write(address(spell));
+
+        _initIlksToIgnore();
+
+        vm.makePersistent(chief);
+    }
+
+    /// @dev Ignore any of:
+    ///      - non-Clip contracts.
+    ///      - Clip contracts that are already stopped at some level.
+    ///      - Clip contracts that did not rely on ClipperMom.
+    function _initIlksToIgnore() internal {
+        bytes32[] memory ilks = ilkReg.list();
+        for (uint256 i = 0; i < ilks.length; i++) {
+            address clip = ilkReg.xlip(ilks[i]);
+            string memory ilkStr = string(abi.encodePacked(ilks[i]));
+            if (clip == address(0)) {
+                ilksToIgnore[ilks[i]] = true;
+                emit log_named_string("Ignoring ilk | No clipper", ilkStr);
+                continue;
+            }
+
+            if (WardsLike(clip).wards(clipperMom) == 0) {
+                ilksToIgnore[ilks[i]] = true;
+                emit log_named_string("Ignoring ilk | ClipperMom not authorized", ilkStr);
+                continue;
+            }
+
+            try ClipLike(clip).stopped() returns (uint256 stopped) {
+                if (stopped == 3) {
+                    ilksToIgnore[ilks[i]] = true;
+                    emit log_named_string("Ignoring ilk | Already stopped = 3", ilkStr);
+                }
+            } catch {
+                // Most likely not a Clip instance.
+                ilksToIgnore[ilks[i]] = true;
+                emit log_named_string("Ignoring ilk | Not a Clip", ilkStr);
+            }
+        }
+    }
+
+    function testUniversalOracleStopOnSchedule() public {
+        _checkAllClipMaxStoppedStatus({maxExpected: 2});
+
+        DssEmergencySpellLike(spell).schedule();
+
+        _checkAllClipStoppedStatus({expected: 3});
+    }
+
+    function testUnauthorizedClipperMomShouldNotRevertTheSpell() public {
+        address clipEthA = ilkReg.xlip("ETH-A");
+        // De-auth ClipperMom to force the error:
+        stdstore.target(clipEthA).sig("wards(address)").with_key(clipperMom).checked_write(bytes32(0));
+        // Updates the list of ilks to be ignored.
+        _initIlksToIgnore();
+
+        _checkAllClipMaxStoppedStatus({maxExpected: 2});
+
+        DssEmergencySpellLike(spell).schedule();
+
+        _checkAllClipStoppedStatus({expected: 3});
+        assertEq(ClipLike(clipEthA).stopped(), 0, "ETH-A Clip was not ignored");
+    }
+
+    function testRevertUniversalClipBreakerWhenItDoesNotHaveTheHat() public {
+        stdstore.target(chief).sig("hat()").checked_write(address(0));
+
+        _checkAllClipMaxStoppedStatus({maxExpected: 2});
+
+        vm.expectRevert();
+        DssEmergencySpellLike(spell).schedule();
+
+        _checkAllClipMaxStoppedStatus({maxExpected: 2});
+    }
+
+    function _checkAllClipMaxStoppedStatus(uint256 maxExpected) internal view {
+        bytes32[] memory ilks = ilkReg.list();
+        for (uint256 i = 0; i < ilks.length; i++) {
+            if (ilksToIgnore[ilks[i]]) continue;
+
+            address clip = ilkReg.xlip(ilks[i]);
+            if (clip == address(0)) continue;
+
+            try ClipLike(clip).stopped() returns (uint256 stopped) {
+                assertLe(stopped, maxExpected, string(abi.encodePacked("invalid stopped status: ", ilks[i])));
+            } catch {
+                // Most likely not a Clip instance.
+            }
+        }
+    }
+
+    function _checkAllClipStoppedStatus(uint256 expected) internal view {
+        bytes32[] memory ilks = ilkReg.list();
+        for (uint256 i = 0; i < ilks.length; i++) {
+            if (ilksToIgnore[ilks[i]]) continue;
+
+            address clip = ilkReg.xlip(ilks[i]);
+            if (clip == address(0)) continue;
+
+            try ClipLike(clip).stopped() returns (uint256 stopped) {
+                assertEq(stopped, expected, string(abi.encodePacked("invalid stopped status: ", ilks[i])));
+            } catch {
+                // Most likely not a Clip instance.
+            }
+        }
+    }
+}
diff --git a/src/clip-breaker/UniversalClipBreakerSpell.sol b/src/clip-breaker/UniversalClipBreakerSpell.sol
new file mode 100644
index 0000000..bcf64e6
--- /dev/null
+++ b/src/clip-breaker/UniversalClipBreakerSpell.sol
@@ -0,0 +1,68 @@
+// 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 list() external view returns (bytes32[] memory);
+    function xlip(bytes32 ilk) external view returns (address);
+}
+
+interface ClipperMomLike {
+    function setBreaker(address clip, uint256 level, uint256 delay) external;
+}
+
+interface WardsLike {
+    function wards(address who) external view returns (uint256);
+}
+
+contract UniversalClipBreakerSpell is DssEmergencySpell {
+    IlkRegistryLike public immutable ilkReg = IlkRegistryLike(_log.getAddress("ILK_REGISTRY"));
+    ClipperMomLike public immutable clipperMom = ClipperMomLike(_log.getAddress("CLIPPER_MOM"));
+
+    string public constant override description = "Emergency Spell | Universal Clip Breaker";
+
+    uint256 public constant BREAKER_LEVEL = 3;
+    // For level 3 breakers, the delay is not applicable, so we set it to zero.
+    uint256 public constant BREAKER_DELAY = 0;
+
+    event SetBreaker(bytes32 indexed ilk, address indexed clip);
+
+    constructor()
+        // In practice, this spell would never expire
+        DssEmergencySpell(type(uint256).max)
+    {}
+
+    function _onSchedule() internal override {
+        bytes32[] memory ilks = ilkReg.list();
+        for (uint256 i = 0; i < ilks.length; i++) {
+            bytes32 ilk = ilks[i];
+            address clip = ilkReg.xlip(ilk);
+
+            if (clip == address(0)) continue;
+            // Ignore Clip instances that have not relied on ClipperMom.
+            if (WardsLike(clip).wards(address(clipperMom)) == 0) continue;
+
+            try clipperMom.setBreaker(clip, BREAKER_LEVEL, BREAKER_DELAY) {
+                emit SetBreaker(ilk, clip);
+            } catch Error(string memory reason) {
+                // Ignore any failing calls to `clipeprMom.setBreaker` with no revert reason.
+                require(bytes(reason).length == 0, reason);
+            }
+        }
+    }
+}

From b002456cf63d207b26372b1d899146c4f721416e Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Wed, 8 May 2024 20:35:11 -0300
Subject: [PATCH 03/38] refactor(clip-breaker): add more error handling cases

Make sure the spell does not revert if any of the `Clip` contracts has not relied on `ClipperMom`.
---
 ...niversalClipBreakerSpell.integration.t.sol | 42 +++++++++----------
 .../UniversalClipBreakerSpell.sol             | 10 ++++-
 2 files changed, 28 insertions(+), 24 deletions(-)

diff --git a/src/clip-breaker/UniversalClipBreakerSpell.integration.t.sol b/src/clip-breaker/UniversalClipBreakerSpell.integration.t.sol
index 4cdfd72..af380c4 100644
--- a/src/clip-breaker/UniversalClipBreakerSpell.integration.t.sol
+++ b/src/clip-breaker/UniversalClipBreakerSpell.integration.t.sol
@@ -69,29 +69,37 @@ contract UniversalClipBreakerSpellTest is DssTest {
     function _initIlksToIgnore() internal {
         bytes32[] memory ilks = ilkReg.list();
         for (uint256 i = 0; i < ilks.length; i++) {
-            address clip = ilkReg.xlip(ilks[i]);
             string memory ilkStr = string(abi.encodePacked(ilks[i]));
+            address clip = ilkReg.xlip(ilks[i]);
             if (clip == address(0)) {
                 ilksToIgnore[ilks[i]] = true;
                 emit log_named_string("Ignoring ilk | No clipper", ilkStr);
                 continue;
             }
 
-            if (WardsLike(clip).wards(clipperMom) == 0) {
+            try ClipLike(clip).stopped() returns (uint256 stopped) {
+                if (stopped == 3) {
+                    ilksToIgnore[ilks[i]] = true;
+                    emit log_named_string("Ignoring ilk | Clip already has stopped = 3", ilkStr);
+                    continue;
+                }
+            } catch {
+                // Most likely not a Clip instance.
                 ilksToIgnore[ilks[i]] = true;
-                emit log_named_string("Ignoring ilk | ClipperMom not authorized", ilkStr);
+                emit log_named_string("Ignoring ilk | Not a Clip", ilkStr);
                 continue;
             }
 
-            try ClipLike(clip).stopped() returns (uint256 stopped) {
-                if (stopped == 3) {
+            try WardsLike(clip).wards(clipperMom) returns (uint256 ward) {
+                if (ward == 0) {
                     ilksToIgnore[ilks[i]] = true;
-                    emit log_named_string("Ignoring ilk | Already stopped = 3", ilkStr);
+                    emit log_named_string("Ignoring ilk | ClipperMom not authorized", ilkStr);
+                    continue;
                 }
             } catch {
-                // Most likely not a Clip instance.
                 ilksToIgnore[ilks[i]] = true;
                 emit log_named_string("Ignoring ilk | Not a Clip", ilkStr);
+                continue;
             }
         }
     }
@@ -104,7 +112,7 @@ contract UniversalClipBreakerSpellTest is DssTest {
         _checkAllClipStoppedStatus({expected: 3});
     }
 
-    function testUnauthorizedClipperMomShouldNotRevertTheSpell() public {
+    function testUnauthorizedClipperMomShouldNotRevert() public {
         address clipEthA = ilkReg.xlip("ETH-A");
         // De-auth ClipperMom to force the error:
         stdstore.target(clipEthA).sig("wards(address)").with_key(clipperMom).checked_write(bytes32(0));
@@ -136,13 +144,9 @@ contract UniversalClipBreakerSpellTest is DssTest {
             if (ilksToIgnore[ilks[i]]) continue;
 
             address clip = ilkReg.xlip(ilks[i]);
-            if (clip == address(0)) continue;
-
-            try ClipLike(clip).stopped() returns (uint256 stopped) {
-                assertLe(stopped, maxExpected, string(abi.encodePacked("invalid stopped status: ", ilks[i])));
-            } catch {
-                // Most likely not a Clip instance.
-            }
+            assertLe(
+                ClipLike(clip).stopped(), maxExpected, string(abi.encodePacked("invalid stopped status: ", ilks[i]))
+            );
         }
     }
 
@@ -152,13 +156,7 @@ contract UniversalClipBreakerSpellTest is DssTest {
             if (ilksToIgnore[ilks[i]]) continue;
 
             address clip = ilkReg.xlip(ilks[i]);
-            if (clip == address(0)) continue;
-
-            try ClipLike(clip).stopped() returns (uint256 stopped) {
-                assertEq(stopped, expected, string(abi.encodePacked("invalid stopped status: ", ilks[i])));
-            } catch {
-                // Most likely not a Clip instance.
-            }
+            assertEq(ClipLike(clip).stopped(), expected, string(abi.encodePacked("invalid stopped status: ", ilks[i])));
         }
     }
 }
diff --git a/src/clip-breaker/UniversalClipBreakerSpell.sol b/src/clip-breaker/UniversalClipBreakerSpell.sol
index bcf64e6..5ed9a94 100644
--- a/src/clip-breaker/UniversalClipBreakerSpell.sol
+++ b/src/clip-breaker/UniversalClipBreakerSpell.sol
@@ -54,8 +54,14 @@ contract UniversalClipBreakerSpell is DssEmergencySpell {
             address clip = ilkReg.xlip(ilk);
 
             if (clip == address(0)) continue;
-            // Ignore Clip instances that have not relied on ClipperMom.
-            if (WardsLike(clip).wards(address(clipperMom)) == 0) continue;
+
+            try WardsLike(clip).wards(address(clipperMom)) returns (uint256 ward) {
+                // Ignore Clip instances that have not relied on ClipperMom.
+                if (ward != 1) continue;
+            } catch Error(string memory reason) {
+                // If the reason is empty, it means the contract is most likely not a Clip instance.
+                require(bytes(reason).length == 0, reason);
+            }
 
             try clipperMom.setBreaker(clip, BREAKER_LEVEL, BREAKER_DELAY) {
                 emit SetBreaker(ilk, clip);

From 5bc1ab369b055606fbab1c999b936d729b906e6a Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Wed, 8 May 2024 20:35:32 -0300
Subject: [PATCH 04/38] refactor(osm-stop): add more error handling cases

Make sure the spell does not revert if any of the `Clip` contracts has not relied on `ClipperMom`.
---
 .../UniversalOsmStopSpell.integration.t.sol   | 77 ++++++++++++++++---
 src/osm-stop/UniversalOsmStopSpell.sol        | 36 ++++++---
 2 files changed, 93 insertions(+), 20 deletions(-)

diff --git a/src/osm-stop/UniversalOsmStopSpell.integration.t.sol b/src/osm-stop/UniversalOsmStopSpell.integration.t.sol
index 77b9b44..45dc65b 100644
--- a/src/osm-stop/UniversalOsmStopSpell.integration.t.sol
+++ b/src/osm-stop/UniversalOsmStopSpell.integration.t.sol
@@ -29,6 +29,11 @@ interface OsmLike {
     function osms(bytes32 ilk) external view returns (address);
 }
 
+
+interface WardsLike {
+    function wards(address who) external view returns (uint256);
+}
+
 interface IlkRegistryLike {
     function list() external view returns (bytes32[] memory);
     function pip(bytes32 ilk) external view returns (address);
@@ -42,9 +47,10 @@ contract UniversalOsmStopSpellTest is DssTest {
     address chief;
     OsmMomLike osmMom;
     IlkRegistryLike ilkReg;
-    OsmLike osm;
     DssEmergencySpellLike spell;
 
+    mapping(bytes32 => bool) ilksToIgnore;
+
     function setUp() public {
         vm.createSelectFork("mainnet");
 
@@ -57,9 +63,50 @@ contract UniversalOsmStopSpellTest is DssTest {
 
         stdstore.target(chief).sig("hat()").checked_write(address(spell));
 
+        _initIlksToIgnore();
+
         vm.makePersistent(chief);
     }
 
+    /// @dev Ignore any of:
+    function _initIlksToIgnore() internal {
+        bytes32[] memory ilks = ilkReg.list();
+        for (uint256 i = 0; i < ilks.length; i++) {
+            string memory ilkStr = string(abi.encodePacked(ilks[i]));
+            address osm = ilkReg.pip(ilks[i]);
+            if (osm == address(0)) {
+                ilksToIgnore[ilks[i]] = true;
+                emit log_named_string("Ignoring ilk | No OSM", ilkStr);
+                continue;
+            }
+
+            try OsmLike(osm).stopped() returns (uint256 stopped) {
+                if (stopped == 1) {
+                    ilksToIgnore[ilks[i]] = true;
+                    emit log_named_string("Ignoring ilk | OSM already stopped", ilkStr);
+                    continue;
+                }
+            } catch {
+                // Most likely not an OSM instance.
+                ilksToIgnore[ilks[i]] = true;
+                emit log_named_string("Ignoring ilk | Not an OSM", ilkStr);
+                continue;
+            }
+
+            try WardsLike(osm).wards(address(osmMom)) returns (uint256 ward) {
+                if (ward == 0) {
+                    ilksToIgnore[ilks[i]] = true;
+                    emit log_named_string("Ignoring ilk | OsmMom not authorized", ilkStr);
+                    continue;
+                }
+            } catch {
+                ilksToIgnore[ilks[i]] = true;
+                emit log_named_string("Ignoring ilk | Not an OSM", ilkStr);
+                continue;
+            }
+        }
+    }
+
     function testUniversalOracleStopOnSchedule() public {
         _checkAllOsmStoppedStatus({expected: 0});
 
@@ -68,6 +115,21 @@ contract UniversalOsmStopSpellTest is DssTest {
         _checkAllOsmStoppedStatus({expected: 1});
     }
 
+    function testUnauthorizedOsmMomShouldNotRevert() public {
+        address pipEth = ilkReg.pip("ETH-A");
+        // De-auth OsmMom to force the error:
+        stdstore.target(pipEth).sig("wards(address)").with_key(address(osmMom)).checked_write(bytes32(0));
+        // Updates the list of ilks to be ignored.
+        _initIlksToIgnore();
+
+        _checkAllOsmStoppedStatus({expected: 0});
+
+        DssEmergencySpellLike(spell).schedule();
+
+        _checkAllOsmStoppedStatus({expected: 1});
+        assertEq(OsmLike(pipEth).stopped(), 0, "ETH-A pip was not ignored");
+    }
+
     function testRevertUniversalOracleStopWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 
@@ -79,17 +141,14 @@ contract UniversalOsmStopSpellTest is DssTest {
         _checkAllOsmStoppedStatus({expected: 0});
     }
 
+
     function _checkAllOsmStoppedStatus(uint256 expected) internal view {
         bytes32[] memory ilks = ilkReg.list();
         for (uint256 i = 0; i < ilks.length; i++) {
-            if (osmMom.osms(ilks[i]) != address(0)) {
-                OsmLike pip = OsmLike(ilkReg.pip(ilks[i]));
-                try pip.stopped() returns (uint256 stopped) {
-                    assertEq(stopped, expected, string(abi.encodePacked("invalid stopped state: ", ilks[i])));
-                } catch {
-                    // Most likely not an OSM.
-                }
-            }
+            if (ilksToIgnore[ilks[i]]) continue;
+
+            address pip = ilkReg.pip(ilks[i]);
+            assertEq(OsmLike(pip).stopped(), expected, string(abi.encodePacked("invalid stopped status: ", ilks[i])));
         }
     }
 }
diff --git a/src/osm-stop/UniversalOsmStopSpell.sol b/src/osm-stop/UniversalOsmStopSpell.sol
index 856c45d..7fd5339 100644
--- a/src/osm-stop/UniversalOsmStopSpell.sol
+++ b/src/osm-stop/UniversalOsmStopSpell.sol
@@ -17,18 +17,22 @@ pragma solidity ^0.8.16;
 
 import {DssEmergencySpell} from "../DssEmergencySpell.sol";
 
+interface IlkRegistryLike {
+    function list() external view returns (bytes32[] memory);
+}
+
 interface OsmMomLike {
     function stop(bytes32 ilk) external;
     function osms(bytes32 ilk) external view returns (address);
 }
 
-interface IlkRegistryLike {
-    function list() external view returns (bytes32[] memory);
+interface WardsLike {
+    function wards(address who) external view returns (uint256);
 }
 
 contract UniversalOsmStopSpell is DssEmergencySpell {
-    OsmMomLike public immutable osmMom = OsmMomLike(_log.getAddress("OSM_MOM"));
     IlkRegistryLike public immutable ilkReg = IlkRegistryLike(_log.getAddress("ILK_REGISTRY"));
+    OsmMomLike public immutable osmMom = OsmMomLike(_log.getAddress("OSM_MOM"));
 
     string public constant override description = "Emergency Spell | Universal OSM Stop";
 
@@ -42,14 +46,24 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
     function _onSchedule() internal override {
         bytes32[] memory ilks = ilkReg.list();
         for (uint256 i = 0; i < ilks.length; i++) {
-            if (osmMom.osms(ilks[i]) != address(0)) {
-                // There might be some duplicate calls to the same OSM, however they are idempotent.
-                try OsmMomLike(osmMom).stop(ilks[i]) {
-                    emit Stop(ilks[i]);
-                } catch Error(string memory reason) {
-                    // Ignore any failing calls to `osmMom.stop` with no error being returned.
-                    require(bytes(reason).length == 0, reason);
-                }
+            address osm = osmMom.osms(ilks[i]);
+
+            if (osm == address(0)) continue;
+
+            try WardsLike(osm).wards(address(osmMom)) returns (uint256 ward) {
+                // Ignore Osm instances that have not relied on OsmMom.
+                if (ward != 1) continue;
+            } catch Error(string memory reason) {
+                // If the reason is empty, it means the contract is most likely not an OSM instance.
+                require(bytes(reason).length == 0, reason);
+            }
+
+            // There might be some duplicate calls to the same OSM, however they are idempotent.
+            try OsmMomLike(osmMom).stop(ilks[i]) {
+                emit Stop(ilks[i]);
+            } catch Error(string memory reason) {
+                // Ignore any failing calls to `osmMom.stop` with no revert reason.
+                require(bytes(reason).length == 0, reason);
             }
         }
     }

From ba2dfc94f50d2e75441dd42be48ea7abe286a6d2 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 9 May 2024 15:03:52 -0300
Subject: [PATCH 05/38] refactor: remove parametrizable expiration

Define the expiration as `type(uint26).max`, meaning the emergency spells should never expire.
---
 src/DssEmergencySpell.sol                      | 11 +++--------
 src/clip-breaker/UniversalClipBreakerSpell.sol |  5 -----
 src/osm-stop/SingleOsmStopSpell.sol            |  5 +----
 src/osm-stop/UniversalOsmStopSpell.sol         |  5 -----
 4 files changed, 4 insertions(+), 22 deletions(-)

diff --git a/src/DssEmergencySpell.sol b/src/DssEmergencySpell.sol
index 05635fb..fc3b2fc 100644
--- a/src/DssEmergencySpell.sol
+++ b/src/DssEmergencySpell.sol
@@ -42,18 +42,14 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
     uint256 public constant eta = 0;
     bytes public constant sig = "";
     bool public constant done = false;
+    uint256 public constant expiration = type(uint256).max;
     address public immutable action = address(this);
     bytes32 public immutable tag = keccak256(abi.encodePacked(address(this)));
     address public immutable pause = ChainlogLike(log).getAddress("MCD_PAUSE");
-    uint256 public immutable expiration;
 
     // @notice Office Hours is always `false` for emergency spells.
     bool public constant officeHours = false;
 
-    constructor(uint256 _expiration) {
-        expiration = _expiration;
-    }
-
     function nextCastTime() external virtual view returns (uint256) {
         return block.timestamp;
     }
@@ -63,8 +59,7 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
      * @dev 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 virtual {
-        require(block.timestamp <= expiration, "dss-emergency-spell/expired");
+    function schedule() external {
         _onSchedule();
     }
 
@@ -73,5 +68,5 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
     /**
      * @notice This function is a no-op. It exists only to keep interface compatibility with regular spells.
      */
-    function cast() external virtual {}
+    function cast() external {}
 }
diff --git a/src/clip-breaker/UniversalClipBreakerSpell.sol b/src/clip-breaker/UniversalClipBreakerSpell.sol
index 5ed9a94..840bb52 100644
--- a/src/clip-breaker/UniversalClipBreakerSpell.sol
+++ b/src/clip-breaker/UniversalClipBreakerSpell.sol
@@ -42,11 +42,6 @@ contract UniversalClipBreakerSpell is DssEmergencySpell {
 
     event SetBreaker(bytes32 indexed ilk, address indexed clip);
 
-    constructor()
-        // In practice, this spell would never expire
-        DssEmergencySpell(type(uint256).max)
-    {}
-
     function _onSchedule() internal override {
         bytes32[] memory ilks = ilkReg.list();
         for (uint256 i = 0; i < ilks.length; i++) {
diff --git a/src/osm-stop/SingleOsmStopSpell.sol b/src/osm-stop/SingleOsmStopSpell.sol
index f7832d3..8c25543 100644
--- a/src/osm-stop/SingleOsmStopSpell.sol
+++ b/src/osm-stop/SingleOsmStopSpell.sol
@@ -27,10 +27,7 @@ contract SingleOsmStopSpell is DssEmergencySpell {
 
     event Stop();
 
-    constructor(bytes32 _ilk)
-        // In practice, this spell would never expire
-        DssEmergencySpell(type(uint256).max)
-    {
+    constructor(bytes32 _ilk) {
         ilk = _ilk;
     }
 
diff --git a/src/osm-stop/UniversalOsmStopSpell.sol b/src/osm-stop/UniversalOsmStopSpell.sol
index 7fd5339..cecd9d5 100644
--- a/src/osm-stop/UniversalOsmStopSpell.sol
+++ b/src/osm-stop/UniversalOsmStopSpell.sol
@@ -38,11 +38,6 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
 
     event Stop(bytes32 ilk);
 
-    constructor()
-        // In practice, this spell would never expire
-        DssEmergencySpell(type(uint256).max)
-    {}
-
     function _onSchedule() internal override {
         bytes32[] memory ilks = ilkReg.list();
         for (uint256 i = 0; i < ilks.length; i++) {

From 43b2d048fb08ab65879e46999f0e38454b698966 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 9 May 2024 15:04:44 -0300
Subject: [PATCH 06/38] refactor: rename `_onSchedule` to  `_emergencyActions`

The new name conveys the meaning better.
---
 src/DssEmergencySpell.sol                      | 4 ++--
 src/clip-breaker/UniversalClipBreakerSpell.sol | 2 +-
 src/osm-stop/SingleOsmStopSpell.sol            | 2 +-
 src/osm-stop/UniversalOsmStopSpell.sol         | 2 +-
 4 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/DssEmergencySpell.sol b/src/DssEmergencySpell.sol
index fc3b2fc..cb357bf 100644
--- a/src/DssEmergencySpell.sol
+++ b/src/DssEmergencySpell.sol
@@ -60,10 +60,10 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
      *      scheduled. Emergency spell take affect immediately, so there is no need to call `pause.plot()`.
      */
     function schedule() external {
-        _onSchedule();
+        _emeregencyActions();
     }
 
-    function _onSchedule() internal virtual;
+    function _emeregencyActions() internal virtual;
 
     /**
      * @notice This function is a no-op. It exists only to keep interface compatibility with regular spells.
diff --git a/src/clip-breaker/UniversalClipBreakerSpell.sol b/src/clip-breaker/UniversalClipBreakerSpell.sol
index 840bb52..b28003d 100644
--- a/src/clip-breaker/UniversalClipBreakerSpell.sol
+++ b/src/clip-breaker/UniversalClipBreakerSpell.sol
@@ -42,7 +42,7 @@ contract UniversalClipBreakerSpell is DssEmergencySpell {
 
     event SetBreaker(bytes32 indexed ilk, address indexed clip);
 
-    function _onSchedule() internal override {
+    function _emeregencyActions() internal override {
         bytes32[] memory ilks = ilkReg.list();
         for (uint256 i = 0; i < ilks.length; i++) {
             bytes32 ilk = ilks[i];
diff --git a/src/osm-stop/SingleOsmStopSpell.sol b/src/osm-stop/SingleOsmStopSpell.sol
index 8c25543..8c3b067 100644
--- a/src/osm-stop/SingleOsmStopSpell.sol
+++ b/src/osm-stop/SingleOsmStopSpell.sol
@@ -35,7 +35,7 @@ contract SingleOsmStopSpell is DssEmergencySpell {
         return string(abi.encodePacked("Emergency Spell | OSM Stop: ", ilk));
     }
 
-    function _onSchedule() internal override {
+    function _emeregencyActions() internal override {
         osmMom.stop(ilk);
         emit Stop();
     }
diff --git a/src/osm-stop/UniversalOsmStopSpell.sol b/src/osm-stop/UniversalOsmStopSpell.sol
index cecd9d5..f589670 100644
--- a/src/osm-stop/UniversalOsmStopSpell.sol
+++ b/src/osm-stop/UniversalOsmStopSpell.sol
@@ -38,7 +38,7 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
 
     event Stop(bytes32 ilk);
 
-    function _onSchedule() internal override {
+    function _emeregencyActions() internal override {
         bytes32[] memory ilks = ilkReg.list();
         for (uint256 i = 0; i < ilks.length; i++) {
             address osm = osmMom.osms(ilks[i]);

From fc6b1b9b2e2b3d3f8b7b2a8ac0d15139b6eb5d45 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 9 May 2024 15:15:32 -0300
Subject: [PATCH 07/38] refactor: rename test files

---
 ...egration.t.sol => UniversalClipBreakerSpell.t.integration.sol} | 0
 ...ell.integration.t.sol => SingleOsmStopSpell.t.integration.sol} | 0
 ....integration.t.sol => UniversalOsmStopSpell.t.integration.sol} | 0
 3 files changed, 0 insertions(+), 0 deletions(-)
 rename src/clip-breaker/{UniversalClipBreakerSpell.integration.t.sol => UniversalClipBreakerSpell.t.integration.sol} (100%)
 rename src/osm-stop/{SingleOsmStopSpell.integration.t.sol => SingleOsmStopSpell.t.integration.sol} (100%)
 rename src/osm-stop/{UniversalOsmStopSpell.integration.t.sol => UniversalOsmStopSpell.t.integration.sol} (100%)

diff --git a/src/clip-breaker/UniversalClipBreakerSpell.integration.t.sol b/src/clip-breaker/UniversalClipBreakerSpell.t.integration.sol
similarity index 100%
rename from src/clip-breaker/UniversalClipBreakerSpell.integration.t.sol
rename to src/clip-breaker/UniversalClipBreakerSpell.t.integration.sol
diff --git a/src/osm-stop/SingleOsmStopSpell.integration.t.sol b/src/osm-stop/SingleOsmStopSpell.t.integration.sol
similarity index 100%
rename from src/osm-stop/SingleOsmStopSpell.integration.t.sol
rename to src/osm-stop/SingleOsmStopSpell.t.integration.sol
diff --git a/src/osm-stop/UniversalOsmStopSpell.integration.t.sol b/src/osm-stop/UniversalOsmStopSpell.t.integration.sol
similarity index 100%
rename from src/osm-stop/UniversalOsmStopSpell.integration.t.sol
rename to src/osm-stop/UniversalOsmStopSpell.t.integration.sol

From 87387dbb7d00d0afbc6cd9243b29adc56cf14f31 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 9 May 2024 15:18:00 -0300
Subject: [PATCH 08/38] docs(osm-stop/universal): add documentation to ignored
 ilks

---
 src/osm-stop/UniversalOsmStopSpell.t.integration.sol | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/osm-stop/UniversalOsmStopSpell.t.integration.sol b/src/osm-stop/UniversalOsmStopSpell.t.integration.sol
index 45dc65b..ce6af9d 100644
--- a/src/osm-stop/UniversalOsmStopSpell.t.integration.sol
+++ b/src/osm-stop/UniversalOsmStopSpell.t.integration.sol
@@ -69,6 +69,10 @@ contract UniversalOsmStopSpellTest is DssTest {
     }
 
     /// @dev Ignore any of:
+    ///      - OSM does not exist for the ilk.
+    ///      - OSM is already stopped.
+    ///      - The `pip` for the ilk is not an OSM instance.
+    ///      - OSMMom is not authorized in the OSM instance.
     function _initIlksToIgnore() internal {
         bytes32[] memory ilks = ilkReg.list();
         for (uint256 i = 0; i < ilks.length; i++) {

From fa320487fb01557c4a80c716c6cf41e660743090 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 9 May 2024 17:00:58 -0300
Subject: [PATCH 09/38] feat(osm-top): add batching escape hatch

In the unlikely case the spell emergency actions would revert due to block gas limit, there is an escape hatch through a custom method that would allow users to split the execution into batches.
---
 src/osm-stop/UniversalOsmStopSpell.sol        | 26 +++++++++++
 .../UniversalOsmStopSpell.t.integration.sol   | 45 ++++++++++++++-----
 2 files changed, 59 insertions(+), 12 deletions(-)

diff --git a/src/osm-stop/UniversalOsmStopSpell.sol b/src/osm-stop/UniversalOsmStopSpell.sol
index f589670..445ff7e 100644
--- a/src/osm-stop/UniversalOsmStopSpell.sol
+++ b/src/osm-stop/UniversalOsmStopSpell.sol
@@ -18,7 +18,9 @@ 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 OsmMomLike {
@@ -38,8 +40,32 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
 
     event Stop(bytes32 ilk);
 
+    /**
+     * @notice Stop all OSMs that can be found through the ilk registry.
+     */
     function _emeregencyActions() internal override {
         bytes32[] memory ilks = ilkReg.list();
+        _doStop(ilks);
+    }
+
+    /**
+     * @notice Stop all OSM in the batch.
+     * @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);
+        _doStop(ilks);
+    }
+
+    /**
+     * @notice Stop all OSMs that can be found from the `ilks` list.
+     * @param ilks The list of ilks to consider.
+     */
+    function _doStop(bytes32[] memory ilks) internal {
         for (uint256 i = 0; i < ilks.length; i++) {
             address osm = osmMom.osms(ilks[i]);
 
diff --git a/src/osm-stop/UniversalOsmStopSpell.t.integration.sol b/src/osm-stop/UniversalOsmStopSpell.t.integration.sol
index ce6af9d..8ee4e72 100644
--- a/src/osm-stop/UniversalOsmStopSpell.t.integration.sol
+++ b/src/osm-stop/UniversalOsmStopSpell.t.integration.sol
@@ -29,13 +29,14 @@ interface OsmLike {
     function osms(bytes32 ilk) external view returns (address);
 }
 
-
 interface WardsLike {
     function wards(address who) external view returns (uint256);
 }
 
 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);
     function pip(bytes32 ilk) external view returns (address);
 }
 
@@ -47,7 +48,7 @@ contract UniversalOsmStopSpellTest is DssTest {
     address chief;
     OsmMomLike osmMom;
     IlkRegistryLike ilkReg;
-    DssEmergencySpellLike spell;
+    UniversalOsmStopSpell spell;
 
     mapping(bytes32 => bool) ilksToIgnore;
 
@@ -112,11 +113,33 @@ contract UniversalOsmStopSpellTest is DssTest {
     }
 
     function testUniversalOracleStopOnSchedule() public {
-        _checkAllOsmStoppedStatus({expected: 0});
+        _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 0});
 
         spell.schedule();
 
-        _checkAllOsmStoppedStatus({expected: 1});
+        _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 1});
+    }
+
+    function testUniversalOracleStopInBatches_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;
+
+        _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 0});
+
+        while (start < count) {
+            spell.stopBatch(start, end);
+            _checkOsmStoppedStatus({ilks: ilkReg.list(start, end < maxEnd ? end : maxEnd), expected: 1});
+
+            start += batchSize;
+            end += batchSize;
+        }
+
+        // Sanity check: the test iterated over the entire ilk registry.
+        _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 1});
     }
 
     function testUnauthorizedOsmMomShouldNotRevert() public {
@@ -126,28 +149,26 @@ contract UniversalOsmStopSpellTest is DssTest {
         // Updates the list of ilks to be ignored.
         _initIlksToIgnore();
 
-        _checkAllOsmStoppedStatus({expected: 0});
+        _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 0});
 
-        DssEmergencySpellLike(spell).schedule();
+        spell.schedule();
 
-        _checkAllOsmStoppedStatus({expected: 1});
+        _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 1});
         assertEq(OsmLike(pipEth).stopped(), 0, "ETH-A pip was not ignored");
     }
 
     function testRevertUniversalOracleStopWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 
-        _checkAllOsmStoppedStatus({expected: 0});
+        _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 0});
 
         vm.expectRevert();
         spell.schedule();
 
-        _checkAllOsmStoppedStatus({expected: 0});
+        _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 0});
     }
 
-
-    function _checkAllOsmStoppedStatus(uint256 expected) internal view {
-        bytes32[] memory ilks = ilkReg.list();
+    function _checkOsmStoppedStatus(bytes32[] memory ilks, uint256 expected) internal view {
         for (uint256 i = 0; i < ilks.length; i++) {
             if (ilksToIgnore[ilks[i]]) continue;
 

From 2160801a8d480dfaf072529470c4cd99114e1975 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 9 May 2024 17:07:12 -0300
Subject: [PATCH 10/38] refactor(osm-stop): remove unused interface import

---
 src/osm-stop/UniversalOsmStopSpell.t.integration.sol | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/osm-stop/UniversalOsmStopSpell.t.integration.sol b/src/osm-stop/UniversalOsmStopSpell.t.integration.sol
index 8ee4e72..09803f9 100644
--- a/src/osm-stop/UniversalOsmStopSpell.t.integration.sol
+++ b/src/osm-stop/UniversalOsmStopSpell.t.integration.sol
@@ -17,7 +17,6 @@ pragma solidity ^0.8.16;
 
 import {stdStorage, StdStorage} from "forge-std/Test.sol";
 import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol";
-import {DssEmergencySpellLike} from "../DssEmergencySpell.sol";
 import {UniversalOsmStopSpell} from "./UniversalOsmStopSpell.sol";
 
 interface OsmMomLike {

From 89dcc8c6bc065c7e70a4ccd18700424aac2efd00 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 9 May 2024 17:34:07 -0300
Subject: [PATCH 11/38] feat(clip-breaker): add batching escape hatch

In the unlikely case the spell emergency actions would revert due to block gas limit, there is an escape hatch through a custom method that would allow users to split the execution into batches.
---
 .../UniversalClipBreakerSpell.sol             | 26 +++++++++
 ...niversalClipBreakerSpell.t.integration.sol | 56 +++++++++++++------
 2 files changed, 65 insertions(+), 17 deletions(-)

diff --git a/src/clip-breaker/UniversalClipBreakerSpell.sol b/src/clip-breaker/UniversalClipBreakerSpell.sol
index b28003d..79bce24 100644
--- a/src/clip-breaker/UniversalClipBreakerSpell.sol
+++ b/src/clip-breaker/UniversalClipBreakerSpell.sol
@@ -18,7 +18,9 @@ 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);
     function xlip(bytes32 ilk) external view returns (address);
 }
 
@@ -42,8 +44,32 @@ contract UniversalClipBreakerSpell is DssEmergencySpell {
 
     event SetBreaker(bytes32 indexed ilk, address indexed clip);
 
+    /**
+     * @notice Set breakers, when possible, for all Clip instances that can be found in the ilk registry.
+     */
     function _emeregencyActions() internal override {
         bytes32[] memory ilks = ilkReg.list();
+        _doSetBreaker(ilks);
+    }
+
+    /**
+     * @notice Set breakers for all Clips in the batch.
+     * @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 setBreakerInBatch(uint256 start, uint256 end) external {
+        uint256 maxEnd = ilkReg.count() - 1;
+        bytes32[] memory ilks = ilkReg.list(start, end < maxEnd ? end : maxEnd);
+        _doSetBreaker(ilks);
+    }
+
+    /**
+     * @notice Set breakers, when possible, for all Clip instances that can be found from the `ilks` list.
+     * @param ilks The list of ilks to consider.
+     */
+    function _doSetBreaker(bytes32[] memory ilks) internal {
         for (uint256 i = 0; i < ilks.length; i++) {
             bytes32 ilk = ilks[i];
             address clip = ilkReg.xlip(ilk);
diff --git a/src/clip-breaker/UniversalClipBreakerSpell.t.integration.sol b/src/clip-breaker/UniversalClipBreakerSpell.t.integration.sol
index af380c4..e9be2dd 100644
--- a/src/clip-breaker/UniversalClipBreakerSpell.t.integration.sol
+++ b/src/clip-breaker/UniversalClipBreakerSpell.t.integration.sol
@@ -17,11 +17,12 @@ pragma solidity ^0.8.16;
 
 import {stdStorage, StdStorage} from "forge-std/Test.sol";
 import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol";
-import {DssEmergencySpellLike} from "../DssEmergencySpell.sol";
 import {UniversalClipBreakerSpell} from "./UniversalClipBreakerSpell.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);
     function xlip(bytes32 ilk) external view returns (address);
 }
 
@@ -41,7 +42,7 @@ contract UniversalClipBreakerSpellTest is DssTest {
     address chief;
     IlkRegistryLike ilkReg;
     address clipperMom;
-    address spell;
+    UniversalClipBreakerSpell spell;
 
     mapping(bytes32 => bool) ilksToIgnore;
 
@@ -53,7 +54,7 @@ contract UniversalClipBreakerSpellTest is DssTest {
         chief = dss.chainlog.getAddress("MCD_ADM");
         ilkReg = IlkRegistryLike(dss.chainlog.getAddress("ILK_REGISTRY"));
         clipperMom = dss.chainlog.getAddress("CLIPPER_MOM");
-        spell = address(new UniversalClipBreakerSpell());
+        spell = new UniversalClipBreakerSpell();
 
         stdstore.target(chief).sig("hat()").checked_write(address(spell));
 
@@ -104,12 +105,35 @@ contract UniversalClipBreakerSpellTest is DssTest {
         }
     }
 
-    function testUniversalOracleStopOnSchedule() public {
-        _checkAllClipMaxStoppedStatus({maxExpected: 2});
+    function testUniversalClipBreakerOnSchedule() public {
+        _checkClipMaxStoppedStatus({ilks: ilkReg.list(), maxExpected: 2});
 
-        DssEmergencySpellLike(spell).schedule();
+        spell.schedule();
 
-        _checkAllClipStoppedStatus({expected: 3});
+        _checkClipStoppedStatus({ilks: ilkReg.list(), expected: 3});
+    }
+
+    function testUniversalClipBreakerInBatches_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;
+
+        _checkClipMaxStoppedStatus({ilks: ilkReg.list(), maxExpected: 2});
+
+        while (start < count) {
+            spell.setBreakerInBatch(start, end);
+
+            _checkClipStoppedStatus({ilks: ilkReg.list(start, end < maxEnd ? end : maxEnd), expected: 3});
+
+            start += batchSize;
+            end += batchSize;
+        }
+
+        // Sanity check: the test iterated over the entire ilk registry.
+        _checkClipStoppedStatus({ilks: ilkReg.list(), expected: 3});
     }
 
     function testUnauthorizedClipperMomShouldNotRevert() public {
@@ -119,27 +143,26 @@ contract UniversalClipBreakerSpellTest is DssTest {
         // Updates the list of ilks to be ignored.
         _initIlksToIgnore();
 
-        _checkAllClipMaxStoppedStatus({maxExpected: 2});
+        _checkClipMaxStoppedStatus({ilks: ilkReg.list(), maxExpected: 2});
 
-        DssEmergencySpellLike(spell).schedule();
+        spell.schedule();
 
-        _checkAllClipStoppedStatus({expected: 3});
+        _checkClipStoppedStatus({ilks: ilkReg.list(), expected: 3});
         assertEq(ClipLike(clipEthA).stopped(), 0, "ETH-A Clip was not ignored");
     }
 
     function testRevertUniversalClipBreakerWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 
-        _checkAllClipMaxStoppedStatus({maxExpected: 2});
+        _checkClipMaxStoppedStatus({ilks: ilkReg.list(), maxExpected: 2});
 
         vm.expectRevert();
-        DssEmergencySpellLike(spell).schedule();
+        spell.schedule();
 
-        _checkAllClipMaxStoppedStatus({maxExpected: 2});
+        _checkClipMaxStoppedStatus({ilks: ilkReg.list(), maxExpected: 2});
     }
 
-    function _checkAllClipMaxStoppedStatus(uint256 maxExpected) internal view {
-        bytes32[] memory ilks = ilkReg.list();
+    function _checkClipMaxStoppedStatus(bytes32[] memory ilks, uint256 maxExpected) internal view {
         for (uint256 i = 0; i < ilks.length; i++) {
             if (ilksToIgnore[ilks[i]]) continue;
 
@@ -150,8 +173,7 @@ contract UniversalClipBreakerSpellTest is DssTest {
         }
     }
 
-    function _checkAllClipStoppedStatus(uint256 expected) internal view {
-        bytes32[] memory ilks = ilkReg.list();
+    function _checkClipStoppedStatus(bytes32[] memory ilks, uint256 expected) internal view {
         for (uint256 i = 0; i < ilks.length; i++) {
             if (ilksToIgnore[ilks[i]]) continue;
 

From 9587bef24bdf557dd4d4f4208afdb945419630e0 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 9 May 2024 17:35:59 -0300
Subject: [PATCH 12/38] refactor(osm-stop): add missing params to event payload

---
 src/osm-stop/SingleOsmStopSpell.sol               |  5 +++--
 src/osm-stop/SingleOsmStopSpell.t.integration.sol |  4 ++++
 src/osm-stop/UniversalOsmStopSpell.sol            | 10 +++++-----
 3 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/src/osm-stop/SingleOsmStopSpell.sol b/src/osm-stop/SingleOsmStopSpell.sol
index 8c3b067..2bfd991 100644
--- a/src/osm-stop/SingleOsmStopSpell.sol
+++ b/src/osm-stop/SingleOsmStopSpell.sol
@@ -18,6 +18,7 @@ pragma solidity ^0.8.16;
 import {DssEmergencySpell} from "../DssEmergencySpell.sol";
 
 interface OsmMomLike {
+    function osms(bytes32 ilk) external view returns (address);
     function stop(bytes32 ilk) external;
 }
 
@@ -25,7 +26,7 @@ contract SingleOsmStopSpell is DssEmergencySpell {
     OsmMomLike public immutable osmMom = OsmMomLike(_log.getAddress("OSM_MOM"));
     bytes32 public immutable ilk;
 
-    event Stop();
+    event Stop(address indexed osm);
 
     constructor(bytes32 _ilk) {
         ilk = _ilk;
@@ -37,7 +38,7 @@ contract SingleOsmStopSpell is DssEmergencySpell {
 
     function _emeregencyActions() internal override {
         osmMom.stop(ilk);
-        emit Stop();
+        emit Stop(osmMom.osms(ilk));
     }
 }
 
diff --git a/src/osm-stop/SingleOsmStopSpell.t.integration.sol b/src/osm-stop/SingleOsmStopSpell.t.integration.sol
index 103023d..103686e 100644
--- a/src/osm-stop/SingleOsmStopSpell.t.integration.sol
+++ b/src/osm-stop/SingleOsmStopSpell.t.integration.sol
@@ -57,6 +57,8 @@ contract SingleOsmStopTest is DssTest {
     function testOracleStopOnSchedule() public {
         assertEq(osm.stopped(), 0, "before: oracle already frozen");
 
+        vm.expectEmit(true, true, true, true);
+        emit Stop(address(osm));
         spell.schedule();
 
         assertEq(osm.stopped(), 1, "after: oracle not frozen");
@@ -72,4 +74,6 @@ contract SingleOsmStopTest is DssTest {
 
         assertEq(osm.stopped(), 0, "after: oracle frozen unexpectedly");
     }
+
+    event Stop(address indexed osm);
 }
diff --git a/src/osm-stop/UniversalOsmStopSpell.sol b/src/osm-stop/UniversalOsmStopSpell.sol
index 445ff7e..9abe664 100644
--- a/src/osm-stop/UniversalOsmStopSpell.sol
+++ b/src/osm-stop/UniversalOsmStopSpell.sol
@@ -38,10 +38,10 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
 
     string public constant override description = "Emergency Spell | Universal OSM Stop";
 
-    event Stop(bytes32 ilk);
+    event Stop(bytes32 indexed ilk, address indexed osm);
 
     /**
-     * @notice Stop all OSMs that can be found through the ilk registry.
+     * @notice Stop, when possible, all OSMs that can be found through the ilk registry.
      */
     function _emeregencyActions() internal override {
         bytes32[] memory ilks = ilkReg.list();
@@ -49,7 +49,7 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
     }
 
     /**
-     * @notice Stop all OSM in the batch.
+     * @notice Stop all OSMs in the batch.
      * @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).
@@ -62,7 +62,7 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
     }
 
     /**
-     * @notice Stop all OSMs that can be found from the `ilks` list.
+     * @notice Stop, when possible, all OSMs that can be found from the `ilks` list.
      * @param ilks The list of ilks to consider.
      */
     function _doStop(bytes32[] memory ilks) internal {
@@ -81,7 +81,7 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
 
             // There might be some duplicate calls to the same OSM, however they are idempotent.
             try OsmMomLike(osmMom).stop(ilks[i]) {
-                emit Stop(ilks[i]);
+                emit Stop(ilks[i], osm);
             } catch Error(string memory reason) {
                 // Ignore any failing calls to `osmMom.stop` with no revert reason.
                 require(bytes(reason).length == 0, reason);

From 8a9382eaf50186c01da57cb0a568c3418fb4ed3d Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 9 May 2024 17:38:59 -0300
Subject: [PATCH 13/38] chore: update CI pipeline

Add config to run tests automatically on pull requests
---
 .github/workflows/test.yml | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 9282e82..88b52e6 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -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:

From 1b9b6a9877a60f0c347cf4b084c9a6f7e8b86b3d Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 9 May 2024 19:15:46 -0300
Subject: [PATCH 14/38] refactor(osm-stop): tests to use factory to deploy the
 spell

---
 src/osm-stop/SingleOsmStopSpell.t.integration.sol | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/osm-stop/SingleOsmStopSpell.t.integration.sol b/src/osm-stop/SingleOsmStopSpell.t.integration.sol
index 103686e..efd7bf4 100644
--- a/src/osm-stop/SingleOsmStopSpell.t.integration.sol
+++ b/src/osm-stop/SingleOsmStopSpell.t.integration.sol
@@ -18,7 +18,7 @@ pragma solidity ^0.8.16;
 import {stdStorage, StdStorage} from "forge-std/Test.sol";
 import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol";
 import {DssEmergencySpellLike} from "../DssEmergencySpell.sol";
-import {SingleOsmStopSpell} from "./SingleOsmStopSpell.sol";
+import {SingleOsmStopFactory} from "./SingleOsmStopSpell.sol";
 
 interface OsmMomLike {
     function osms(bytes32) external view returns (address);
@@ -28,7 +28,7 @@ interface OsmLike {
     function stopped() external view returns (uint256);
 }
 
-contract SingleOsmStopTest is DssTest {
+contract SingleOsmStopSpellTest is DssTest {
     using stdStorage for StdStorage;
 
     address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
@@ -37,6 +37,7 @@ contract SingleOsmStopTest is DssTest {
     bytes32 ilk = "ETH-A";
     OsmMomLike osmMom;
     OsmLike osm;
+    SingleOsmStopFactory factory;
     DssEmergencySpellLike spell;
 
     function setUp() public {
@@ -47,7 +48,8 @@ contract SingleOsmStopTest is DssTest {
         chief = dss.chainlog.getAddress("MCD_ADM");
         osmMom = OsmMomLike(dss.chainlog.getAddress("OSM_MOM"));
         osm = OsmLike(osmMom.osms(ilk));
-        spell = new SingleOsmStopSpell(ilk);
+        factory = new SingleOsmStopFactory();
+        spell = DssEmergencySpellLike(factory.deploy(ilk));
 
         stdstore.target(chief).sig("hat()").checked_write(address(spell));
 

From 69d1027c09b2838d71df14a35f3219699081f34d Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 9 May 2024 19:16:22 -0300
Subject: [PATCH 15/38] feat(clip-breaker): add spell to set the clip breaker
 for a single ilk

---
 src/clip-breaker/SingleClipBreakerSpell.sol   | 62 +++++++++++++
 .../SingleClipBreakerSpell.t.integration.sol  | 87 +++++++++++++++++++
 2 files changed, 149 insertions(+)
 create mode 100644 src/clip-breaker/SingleClipBreakerSpell.sol
 create mode 100644 src/clip-breaker/SingleClipBreakerSpell.t.integration.sol

diff --git a/src/clip-breaker/SingleClipBreakerSpell.sol b/src/clip-breaker/SingleClipBreakerSpell.sol
new file mode 100644
index 0000000..f51b4bf
--- /dev/null
+++ b/src/clip-breaker/SingleClipBreakerSpell.sol
@@ -0,0 +1,62 @@
+// 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 ClipperMomLike {
+    function setBreaker(address clip, uint256 level, uint256 delay) external;
+}
+
+interface IlkRegistryLike {
+    function xlip(bytes32 ilk) external view returns (address);
+}
+
+contract SingleClipBreakerSpell is DssEmergencySpell {
+    ClipperMomLike public immutable clipperMom = ClipperMomLike(_log.getAddress("CLIPPER_MOM"));
+    IlkRegistryLike public immutable ilkReg = IlkRegistryLike(_log.getAddress("ILK_REGISTRY"));
+
+    uint256 public constant BREAKER_LEVEL = 3;
+    // For level 3 breakers, the delay is not applicable, so we set it to zero.
+    uint256 public constant BREAKER_DELAY = 0;
+
+    bytes32 public immutable ilk;
+
+    event SetBreaker(address indexed clip);
+
+    constructor(bytes32 _ilk) {
+        ilk = _ilk;
+    }
+
+    function description() external view returns (string memory) {
+        return string(abi.encodePacked("Emergency Spell | Set Clip Breaker: ", ilk));
+    }
+
+    function _emeregencyActions() internal override {
+        address clip = ilkReg.xlip(ilk);
+        clipperMom.setBreaker(clip, BREAKER_LEVEL, BREAKER_DELAY);
+        emit SetBreaker(clip);
+    }
+}
+
+contract SingleClipBreakerFactory {
+    event Deploy(bytes32 indexed ilk, address spell);
+
+    function deploy(bytes32 ilk) external returns (address spell) {
+        spell = address(new SingleClipBreakerSpell(ilk));
+        emit Deploy(ilk, spell);
+    }
+}
diff --git a/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol b/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol
new file mode 100644
index 0000000..f14c8b1
--- /dev/null
+++ b/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol
@@ -0,0 +1,87 @@
+// 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 {DssEmergencySpellLike} from "../DssEmergencySpell.sol";
+import {SingleClipBreakerFactory} from "./SingleClipBreakerSpell.sol";
+
+interface IlkRegistryLike {
+    function xlip(bytes32 ilk) external view returns (address);
+}
+
+interface ClipperMomLike {
+    function setBreaker(address clip, uint256 level, uint256 delay) external;
+}
+
+interface ClipLike {
+    function stopped() external view returns (uint256);
+}
+
+contract SingleClipBreakerSpellTest is DssTest {
+    using stdStorage for StdStorage;
+
+    address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
+    DssInstance dss;
+    address chief;
+    IlkRegistryLike ilkReg;
+    bytes32 ilk = "ETH-A";
+    ClipperMomLike clipMom;
+    ClipLike clip;
+    SingleClipBreakerFactory factory;
+    DssEmergencySpellLike spell;
+
+    function setUp() public {
+        vm.createSelectFork("mainnet");
+
+        dss = MCD.loadFromChainlog(CHAINLOG);
+        MCD.giveAdminAccess(dss);
+        chief = dss.chainlog.getAddress("MCD_ADM");
+        ilkReg = IlkRegistryLike(dss.chainlog.getAddress("ILK_REGISTRY"));
+        clipMom = ClipperMomLike(dss.chainlog.getAddress("CLIPPER_MOM"));
+        clip = ClipLike(ilkReg.xlip(ilk));
+        factory = new SingleClipBreakerFactory();
+        spell = DssEmergencySpellLike(factory.deploy(ilk));
+
+        stdstore.target(chief).sig("hat()").checked_write(address(spell));
+
+        vm.makePersistent(chief);
+    }
+
+    function testClipBreakerOnSchedule() public {
+        assertEq(clip.stopped(), 0, "before: clip already stopped");
+
+        vm.expectEmit(true, true, true, true);
+        emit SetBreaker(address(clip));
+        spell.schedule();
+
+        assertEq(clip.stopped(), 3, "after: clip not stopped");
+    }
+
+    function testRevertClipBreakerWhenItDoesNotHaveTheHat() public {
+        stdstore.target(chief).sig("hat()").checked_write(address(0));
+
+        assertEq(clip.stopped(), 0, "before: clip already stopped");
+
+        vm.expectRevert();
+        spell.schedule();
+
+        assertEq(clip.stopped(), 0, "after: clip stopped unexpectedly");
+    }
+
+    event SetBreaker(address indexed clip);
+}

From 5d70199ce1afd09f881f91637865c20e47ede5c9 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Mon, 13 May 2024 19:32:37 -0300
Subject: [PATCH 16/38] feat(auto-line-wipe): add spell to wipe a single ilk

---
 .../SingleAutoLineWipeSpell.sol               | 51 +++++++++++
 .../SingleAutoLineWipeSpell.t.integration.sol | 89 +++++++++++++++++++
 2 files changed, 140 insertions(+)
 create mode 100644 src/auto-line-wipe/SingleAutoLineWipeSpell.sol
 create mode 100644 src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol

diff --git a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
new file mode 100644
index 0000000..d08ae58
--- /dev/null
+++ b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
@@ -0,0 +1,51 @@
+// 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 LineMomLike {
+    function wipe(bytes32 ilk) external returns (uint256);
+}
+
+contract SingleAutoLineWipeSpell is DssEmergencySpell {
+    LineMomLike public immutable lineMom = LineMomLike(_log.getAddress("LINE_MOM"));
+    bytes32 public immutable ilk;
+
+    event Wipe(bytes32 indexed ilk, uint256 prevLine);
+
+    constructor(bytes32 _ilk) {
+        ilk = _ilk;
+    }
+
+    function description() external view returns (string memory) {
+        return string(abi.encodePacked("Emergency Spell | Auto-Line Wipe: ", ilk));
+    }
+
+    function _emeregencyActions() internal override {
+        uint256 prevLine = lineMom.wipe(ilk);
+        emit Wipe(ilk, prevLine);
+    }
+}
+
+contract SingleAutoLineWipeFactory {
+    event Deploy(bytes32 indexed ilk, address spell);
+
+    function deploy(bytes32 ilk) external returns (address spell) {
+        spell = address(new SingleAutoLineWipeSpell(ilk));
+        emit Deploy(ilk, spell);
+    }
+}
diff --git a/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol b/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol
new file mode 100644
index 0000000..8589768
--- /dev/null
+++ b/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol
@@ -0,0 +1,89 @@
+// 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 {DssEmergencySpellLike} from "../DssEmergencySpell.sol";
+import {SingleAutoLineWipeFactory} from "./SingleAutoLineWipeSpell.sol";
+
+interface AutoLineLike {
+    function ilks(bytes32 ilk)
+        external
+        view
+        returns (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc);
+}
+
+contract SingleAutoLineWipeSpellTest is DssTest {
+    using stdStorage for StdStorage;
+
+    address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
+    DssInstance dss;
+    address chief;
+    bytes32 ilk = "ETH-A";
+    address lineMom;
+    AutoLineLike autoLine;
+    SingleAutoLineWipeFactory factory;
+    DssEmergencySpellLike spell;
+
+    function setUp() public {
+        vm.createSelectFork("mainnet");
+
+        dss = MCD.loadFromChainlog(CHAINLOG);
+        MCD.giveAdminAccess(dss);
+        chief = dss.chainlog.getAddress("MCD_ADM");
+        lineMom = dss.chainlog.getAddress("LINE_MOM");
+        autoLine = AutoLineLike(dss.chainlog.getAddress("MCD_IAM_AUTO_LINE"));
+        factory = new SingleAutoLineWipeFactory();
+        spell = DssEmergencySpellLike(factory.deploy(ilk));
+
+        stdstore.target(chief).sig("hat()").checked_write(address(spell));
+
+        vm.makePersistent(chief);
+    }
+
+    function testAutoLineWipeOnSchedule() public {
+        (uint256 pmaxLine, uint256 pgap,,,) = autoLine.ilks(ilk);
+        assertGt(pmaxLine, 0, "before: auto-line already wiped");
+        assertGt(pgap, 0, "before: auto-line already wiped");
+
+        vm.expectEmit(true, true, true, false);
+        // Ignore prevLine for now
+        emit Wipe(ilk, 0);
+        spell.schedule();
+
+        (uint256 maxLine, uint256 gap,,,) = autoLine.ilks(ilk);
+        assertEq(maxLine, 0, "after: auto-line not wiped (maxLine)");
+        assertEq(gap, 0, "after: auto-line not wiped (gap)");
+    }
+
+    function testRevertAutoLineWipeWhenItDoesNotHaveTheHat() public {
+        stdstore.target(chief).sig("hat()").checked_write(address(0));
+
+        (uint256 pmaxLine, uint256 pgap,,,) = autoLine.ilks(ilk);
+        assertGt(pmaxLine, 0, "before: auto-line already wiped");
+        assertGt(pgap, 0, "before: auto-line already wiped");
+
+        vm.expectRevert();
+        spell.schedule();
+
+        (uint256 maxLine, uint256 gap,,,) = autoLine.ilks(ilk);
+        assertGt(maxLine, 0, "after: auto-line wiped unexpectedly");
+        assertGt(gap, 0, "after: auto-line wiped unexpectedly");
+    }
+
+    event Wipe(bytes32 indexed autoLine, uint256 prevLine);
+}

From 7cd769c31d75209556064350bdb679fb25f4daed Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 16 May 2024 12:39:47 -0300
Subject: [PATCH 17/38] feat: add meaningful `done()` methods

---
 src/DssEmergencySpell.sol                     |  1 -
 .../SingleAutoLineWipeSpell.sol               | 19 ++++++++++
 .../SingleAutoLineWipeSpell.t.integration.sol |  4 +++
 src/clip-breaker/SingleClipBreakerSpell.sol   | 12 +++++++
 .../SingleClipBreakerSpell.t.integration.sol  |  4 +++
 .../UniversalClipBreakerSpell.sol             | 35 +++++++++++++++++--
 ...niversalClipBreakerSpell.t.integration.sol |  4 +++
 src/osm-stop/SingleOsmStopSpell.sol           | 12 +++++++
 .../SingleOsmStopSpell.t.integration.sol      |  8 +++--
 src/osm-stop/UniversalOsmStopSpell.sol        | 34 ++++++++++++++++++
 .../UniversalOsmStopSpell.t.integration.sol   |  2 ++
 11 files changed, 130 insertions(+), 5 deletions(-)

diff --git a/src/DssEmergencySpell.sol b/src/DssEmergencySpell.sol
index cb357bf..c4b7bf5 100644
--- a/src/DssEmergencySpell.sol
+++ b/src/DssEmergencySpell.sol
@@ -41,7 +41,6 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
     address public constant log = address(_log);
     uint256 public constant eta = 0;
     bytes public constant sig = "";
-    bool public constant done = false;
     uint256 public constant expiration = type(uint256).max;
     address public immutable action = address(this);
     bytes32 public immutable tag = keccak256(abi.encodePacked(address(this)));
diff --git a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
index d08ae58..a52490c 100644
--- a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
+++ b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
@@ -19,6 +19,14 @@ import {DssEmergencySpell} from "../DssEmergencySpell.sol";
 
 interface LineMomLike {
     function wipe(bytes32 ilk) external returns (uint256);
+    function autoLine() external view returns (address);
+}
+
+interface AutoLineLike {
+    function ilks(bytes32 ilk)
+        external
+        view
+        returns (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc);
 }
 
 contract SingleAutoLineWipeSpell is DssEmergencySpell {
@@ -39,6 +47,17 @@ contract SingleAutoLineWipeSpell is DssEmergencySpell {
         uint256 prevLine = lineMom.wipe(ilk);
         emit Wipe(ilk, prevLine);
     }
+
+    /**
+     * @notice Return whether the spell is done or not.
+     * @dev Check if the ilk has been wiped from auto-line.
+     */
+    function done() external view returns (bool) {
+        (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc) =
+            AutoLineLike(lineMom.autoLine()).ilks(ilk);
+
+        return maxLine == 0 && gap == 0 && ttl == 0 && last == 0 && lastInc == 0;
+    }
 }
 
 contract SingleAutoLineWipeFactory {
diff --git a/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol b/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol
index 8589768..f8c3b6e 100644
--- a/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol
+++ b/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol
@@ -59,6 +59,7 @@ contract SingleAutoLineWipeSpellTest is DssTest {
         (uint256 pmaxLine, uint256 pgap,,,) = autoLine.ilks(ilk);
         assertGt(pmaxLine, 0, "before: auto-line already wiped");
         assertGt(pgap, 0, "before: auto-line already wiped");
+        assertFalse(spell.done(), "before: spell already done");
 
         vm.expectEmit(true, true, true, false);
         // Ignore prevLine for now
@@ -68,6 +69,7 @@ contract SingleAutoLineWipeSpellTest is DssTest {
         (uint256 maxLine, uint256 gap,,,) = autoLine.ilks(ilk);
         assertEq(maxLine, 0, "after: auto-line not wiped (maxLine)");
         assertEq(gap, 0, "after: auto-line not wiped (gap)");
+        assertTrue(spell.done(), "after: spell not done");
     }
 
     function testRevertAutoLineWipeWhenItDoesNotHaveTheHat() public {
@@ -76,6 +78,7 @@ contract SingleAutoLineWipeSpellTest is DssTest {
         (uint256 pmaxLine, uint256 pgap,,,) = autoLine.ilks(ilk);
         assertGt(pmaxLine, 0, "before: auto-line already wiped");
         assertGt(pgap, 0, "before: auto-line already wiped");
+        assertFalse(spell.done(), "before: spell already done");
 
         vm.expectRevert();
         spell.schedule();
@@ -83,6 +86,7 @@ contract SingleAutoLineWipeSpellTest is DssTest {
         (uint256 maxLine, uint256 gap,,,) = autoLine.ilks(ilk);
         assertGt(maxLine, 0, "after: auto-line wiped unexpectedly");
         assertGt(gap, 0, "after: auto-line wiped unexpectedly");
+        assertFalse(spell.done(), "after: spell done unexpectedly");
     }
 
     event Wipe(bytes32 indexed autoLine, uint256 prevLine);
diff --git a/src/clip-breaker/SingleClipBreakerSpell.sol b/src/clip-breaker/SingleClipBreakerSpell.sol
index f51b4bf..5fb80d9 100644
--- a/src/clip-breaker/SingleClipBreakerSpell.sol
+++ b/src/clip-breaker/SingleClipBreakerSpell.sol
@@ -21,6 +21,10 @@ interface ClipperMomLike {
     function setBreaker(address clip, uint256 level, uint256 delay) external;
 }
 
+interface ClipLike {
+    function stopped() external view returns (uint256);
+}
+
 interface IlkRegistryLike {
     function xlip(bytes32 ilk) external view returns (address);
 }
@@ -50,6 +54,14 @@ contract SingleClipBreakerSpell is DssEmergencySpell {
         clipperMom.setBreaker(clip, BREAKER_LEVEL, BREAKER_DELAY);
         emit SetBreaker(clip);
     }
+
+    /**
+     * @notice Return whether the spell is done or not.
+     * @dev Check if the Clip instance has stopped = 3.
+     */
+    function done() external view returns (bool) {
+        return ClipLike(ilkReg.xlip(ilk)).stopped() == BREAKER_LEVEL;
+    }
 }
 
 contract SingleClipBreakerFactory {
diff --git a/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol b/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol
index f14c8b1..356d831 100644
--- a/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol
+++ b/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol
@@ -64,23 +64,27 @@ contract SingleClipBreakerSpellTest is DssTest {
 
     function testClipBreakerOnSchedule() public {
         assertEq(clip.stopped(), 0, "before: clip already stopped");
+        assertFalse(spell.done(), "before: spell already done");
 
         vm.expectEmit(true, true, true, true);
         emit SetBreaker(address(clip));
         spell.schedule();
 
         assertEq(clip.stopped(), 3, "after: clip not stopped");
+        assertTrue(spell.done(), "after: spell not done");
     }
 
     function testRevertClipBreakerWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 
         assertEq(clip.stopped(), 0, "before: clip already stopped");
+        assertFalse(spell.done(), "before: spell already done");
 
         vm.expectRevert();
         spell.schedule();
 
         assertEq(clip.stopped(), 0, "after: clip stopped unexpectedly");
+        assertFalse(spell.done(), "after: spell done unexpectedly");
     }
 
     event SetBreaker(address indexed clip);
diff --git a/src/clip-breaker/UniversalClipBreakerSpell.sol b/src/clip-breaker/UniversalClipBreakerSpell.sol
index 79bce24..726e5ef 100644
--- a/src/clip-breaker/UniversalClipBreakerSpell.sol
+++ b/src/clip-breaker/UniversalClipBreakerSpell.sol
@@ -28,8 +28,9 @@ interface ClipperMomLike {
     function setBreaker(address clip, uint256 level, uint256 delay) external;
 }
 
-interface WardsLike {
+interface ClipLike {
     function wards(address who) external view returns (uint256);
+    function stopped() external view returns (uint256);
 }
 
 contract UniversalClipBreakerSpell is DssEmergencySpell {
@@ -76,7 +77,7 @@ contract UniversalClipBreakerSpell is DssEmergencySpell {
 
             if (clip == address(0)) continue;
 
-            try WardsLike(clip).wards(address(clipperMom)) returns (uint256 ward) {
+            try ClipLike(clip).wards(address(clipperMom)) returns (uint256 ward) {
                 // Ignore Clip instances that have not relied on ClipperMom.
                 if (ward != 1) continue;
             } catch Error(string memory reason) {
@@ -92,4 +93,34 @@ contract UniversalClipBreakerSpell is DssEmergencySpell {
             }
         }
     }
+
+    /**
+     * @notice Return whether the spell is done or not.
+     * @dev Check if all possible Clip instances from the ilk registry have stopped = 3.
+     */
+    function done() external view returns (bool) {
+        bytes32[] memory ilks = ilkReg.list();
+        for (uint256 i = 0; i < ilks.length; i++) {
+            bytes32 ilk = ilks[i];
+            address clip = ilkReg.xlip(ilk);
+
+            if (clip == address(0)) continue;
+
+            try ClipLike(clip).wards(address(clipperMom)) returns (uint256 ward) {
+                // Ignore Clip instances that have not relied on ClipperMom.
+                if (ward != 1) continue;
+            } catch {
+                // Ignore any errors.
+                continue;
+            }
+
+            try ClipLike(clip).stopped() returns (uint256 stopped) {
+                if (stopped != BREAKER_LEVEL) return false;
+            } catch {
+                // Ignore any errors.
+                continue;
+            }
+        }
+        return true;
+    }
 }
diff --git a/src/clip-breaker/UniversalClipBreakerSpell.t.integration.sol b/src/clip-breaker/UniversalClipBreakerSpell.t.integration.sol
index e9be2dd..2bff2a0 100644
--- a/src/clip-breaker/UniversalClipBreakerSpell.t.integration.sol
+++ b/src/clip-breaker/UniversalClipBreakerSpell.t.integration.sol
@@ -107,10 +107,12 @@ contract UniversalClipBreakerSpellTest is DssTest {
 
     function testUniversalClipBreakerOnSchedule() public {
         _checkClipMaxStoppedStatus({ilks: ilkReg.list(), maxExpected: 2});
+        assertFalse(spell.done(), "before: spell already done");
 
         spell.schedule();
 
         _checkClipStoppedStatus({ilks: ilkReg.list(), expected: 3});
+        assertTrue(spell.done(), "after: spell not done");
     }
 
     function testUniversalClipBreakerInBatches_Fuzz(uint256 batchSize) public {
@@ -155,11 +157,13 @@ contract UniversalClipBreakerSpellTest is DssTest {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 
         _checkClipMaxStoppedStatus({ilks: ilkReg.list(), maxExpected: 2});
+        assertFalse(spell.done(), "before: spell already done");
 
         vm.expectRevert();
         spell.schedule();
 
         _checkClipMaxStoppedStatus({ilks: ilkReg.list(), maxExpected: 2});
+        assertFalse(spell.done(), "false: spell done unexpectedly");
     }
 
     function _checkClipMaxStoppedStatus(bytes32[] memory ilks, uint256 maxExpected) internal view {
diff --git a/src/osm-stop/SingleOsmStopSpell.sol b/src/osm-stop/SingleOsmStopSpell.sol
index 2bfd991..0afa4ea 100644
--- a/src/osm-stop/SingleOsmStopSpell.sol
+++ b/src/osm-stop/SingleOsmStopSpell.sol
@@ -22,6 +22,10 @@ interface OsmMomLike {
     function stop(bytes32 ilk) external;
 }
 
+interface OsmLike {
+    function stopped() external view returns (uint256);
+}
+
 contract SingleOsmStopSpell is DssEmergencySpell {
     OsmMomLike public immutable osmMom = OsmMomLike(_log.getAddress("OSM_MOM"));
     bytes32 public immutable ilk;
@@ -40,6 +44,14 @@ contract SingleOsmStopSpell is DssEmergencySpell {
         osmMom.stop(ilk);
         emit Stop(osmMom.osms(ilk));
     }
+
+    /**
+     * @notice Return whether the spell is done or not.
+     * @dev Check if the OSM instance is stopped.
+     */
+    function done() external view returns (bool) {
+        return OsmLike(osmMom.osms(ilk)).stopped() == 1;
+    }
 }
 
 contract SingleOsmStopFactory {
diff --git a/src/osm-stop/SingleOsmStopSpell.t.integration.sol b/src/osm-stop/SingleOsmStopSpell.t.integration.sol
index efd7bf4..54f3b01 100644
--- a/src/osm-stop/SingleOsmStopSpell.t.integration.sol
+++ b/src/osm-stop/SingleOsmStopSpell.t.integration.sol
@@ -56,25 +56,29 @@ contract SingleOsmStopSpellTest is DssTest {
         vm.makePersistent(chief);
     }
 
-    function testOracleStopOnSchedule() public {
+    function testOsmStopOnSchedule() public {
         assertEq(osm.stopped(), 0, "before: oracle already frozen");
+        assertFalse(spell.done(), "before: spell already done");
 
         vm.expectEmit(true, true, true, true);
         emit Stop(address(osm));
         spell.schedule();
 
         assertEq(osm.stopped(), 1, "after: oracle not frozen");
+        assertTrue(spell.done(), "after: spell not done");
     }
 
-    function testRevertOracleStopWhenItDoesNotHaveTheHat() public {
+    function testRevertOsmStopWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 
         assertEq(osm.stopped(), 0, "before: oracle already frozen");
+        assertFalse(spell.done(), "before: spell already done");
 
         vm.expectRevert();
         spell.schedule();
 
         assertEq(osm.stopped(), 0, "after: oracle frozen unexpectedly");
+        assertFalse(spell.done(), "after: spell done unexpectedly");
     }
 
     event Stop(address indexed osm);
diff --git a/src/osm-stop/UniversalOsmStopSpell.sol b/src/osm-stop/UniversalOsmStopSpell.sol
index 9abe664..ffedf48 100644
--- a/src/osm-stop/UniversalOsmStopSpell.sol
+++ b/src/osm-stop/UniversalOsmStopSpell.sol
@@ -28,6 +28,10 @@ interface OsmMomLike {
     function osms(bytes32 ilk) external view returns (address);
 }
 
+interface OsmLike {
+    function stopped() external view returns (uint256);
+}
+
 interface WardsLike {
     function wards(address who) external view returns (uint256);
 }
@@ -88,4 +92,34 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
             }
         }
     }
+
+    /**
+     * @notice Return whether the spell is done or not.
+     * @dev Check if all possible OSM instances from the ilk registry are stopped.
+     */
+    function done() external view returns (bool) {
+        bytes32[] memory ilks = ilkReg.list();
+        for (uint256 i = 0; i < ilks.length; i++) {
+            address osm = osmMom.osms(ilks[i]);
+
+            if (osm == address(0)) continue;
+
+            try WardsLike(osm).wards(address(osmMom)) returns (uint256 ward) {
+                // Ignore Osm instances that have not relied on OsmMom.
+                if (ward != 1) continue;
+            } catch {
+                // Ignore any errors.
+                continue;
+            }
+
+            try OsmLike(osm).stopped() returns (uint256 stopped) {
+                // If any of the OSMs that match the conditions is not stopped, the spell was not executed yet.
+                if (stopped == 0) return false;
+            } catch {
+                // Ignore any errors.
+                continue;
+            }
+        }
+        return true;
+    }
 }
diff --git a/src/osm-stop/UniversalOsmStopSpell.t.integration.sol b/src/osm-stop/UniversalOsmStopSpell.t.integration.sol
index 09803f9..366b10d 100644
--- a/src/osm-stop/UniversalOsmStopSpell.t.integration.sol
+++ b/src/osm-stop/UniversalOsmStopSpell.t.integration.sol
@@ -113,10 +113,12 @@ contract UniversalOsmStopSpellTest is DssTest {
 
     function testUniversalOracleStopOnSchedule() public {
         _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 0});
+        assertFalse(spell.done(), "before: spell already done");
 
         spell.schedule();
 
         _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 1});
+        assertTrue(spell.done(), "after: spell not done");
     }
 
     function testUniversalOracleStopInBatches_Fuzz(uint256 batchSize) public {

From 206c15c192e54b360007f9ede969333db7ae2ce8 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Fri, 17 May 2024 10:15:44 -0300
Subject: [PATCH 18/38] refactor: reorganize contracts and update natspec

---
 src/DssEmergencySpell.sol                     | 25 +++++++++++--------
 .../SingleAutoLineWipeSpell.sol               |  4 +--
 src/clip-breaker/SingleClipBreakerSpell.sol   | 12 ++++-----
 .../UniversalClipBreakerSpell.sol             | 20 +++++++--------
 src/osm-stop/SingleOsmStopSpell.sol           |  4 +--
 src/osm-stop/UniversalOsmStopSpell.sol        | 14 +++++------
 6 files changed, 41 insertions(+), 38 deletions(-)

diff --git a/src/DssEmergencySpell.sol b/src/DssEmergencySpell.sol
index c4b7bf5..50e23a0 100644
--- a/src/DssEmergencySpell.sol
+++ b/src/DssEmergencySpell.sol
@@ -42,30 +42,33 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
     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)));
     address public immutable pause = ChainlogLike(log).getAddress("MCD_PAUSE");
+    uint256 public immutable nextCastTime = block.timestamp;
 
-    // @notice Office Hours is always `false` for emergency spells.
-    bool public constant officeHours = false;
-
-    function nextCastTime() external virtual view returns (uint256) {
-        return block.timestamp;
-    }
 
     /**
-     * @notice Emergency spell are triggered when scheduled.
-     * @dev This function maintains the name for compatibility with regular spells, however nothing is actually being
+     * @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();
     }
 
-    function _emeregencyActions() internal virtual;
-
     /**
-     * @notice This function is a no-op. It exists only to keep interface compatibility with regular spells.
+     * @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;
+
 }
diff --git a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
index a52490c..c652247 100644
--- a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
+++ b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
@@ -49,8 +49,8 @@ contract SingleAutoLineWipeSpell is DssEmergencySpell {
     }
 
     /**
-     * @notice Return whether the spell is done or not.
-     * @dev Check if the ilk has been wiped from auto-line.
+     * @notice Returns whether the spell is done or not.
+     * @dev Checks if the ilk has been wiped from auto-line.
      */
     function done() external view returns (bool) {
         (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc) =
diff --git a/src/clip-breaker/SingleClipBreakerSpell.sol b/src/clip-breaker/SingleClipBreakerSpell.sol
index 5fb80d9..4f66c3a 100644
--- a/src/clip-breaker/SingleClipBreakerSpell.sol
+++ b/src/clip-breaker/SingleClipBreakerSpell.sol
@@ -30,13 +30,13 @@ interface IlkRegistryLike {
 }
 
 contract SingleClipBreakerSpell is DssEmergencySpell {
-    ClipperMomLike public immutable clipperMom = ClipperMomLike(_log.getAddress("CLIPPER_MOM"));
-    IlkRegistryLike public immutable ilkReg = IlkRegistryLike(_log.getAddress("ILK_REGISTRY"));
-
+    /// @dev During an emergency, set the breaker level to 3  to prevent botyh `kick()`, `redo()` and `take()`.
     uint256 public constant BREAKER_LEVEL = 3;
-    // For level 3 breakers, the delay is not applicable, so we set it to zero.
+    /// @dev The delay is not applicable for level 3 breakers, so we set it to zero.
     uint256 public constant BREAKER_DELAY = 0;
 
+    ClipperMomLike public immutable clipperMom = ClipperMomLike(_log.getAddress("CLIPPER_MOM"));
+    IlkRegistryLike public immutable ilkReg = IlkRegistryLike(_log.getAddress("ILK_REGISTRY"));
     bytes32 public immutable ilk;
 
     event SetBreaker(address indexed clip);
@@ -56,8 +56,8 @@ contract SingleClipBreakerSpell is DssEmergencySpell {
     }
 
     /**
-     * @notice Return whether the spell is done or not.
-     * @dev Check if the Clip instance has stopped = 3.
+     * @notice Returns whether the spell is done or not.
+     * @dev Checks if the Clip instance has stopped = 3.
      */
     function done() external view returns (bool) {
         return ClipLike(ilkReg.xlip(ilk)).stopped() == BREAKER_LEVEL;
diff --git a/src/clip-breaker/UniversalClipBreakerSpell.sol b/src/clip-breaker/UniversalClipBreakerSpell.sol
index 726e5ef..c9dc5db 100644
--- a/src/clip-breaker/UniversalClipBreakerSpell.sol
+++ b/src/clip-breaker/UniversalClipBreakerSpell.sol
@@ -34,19 +34,19 @@ interface ClipLike {
 }
 
 contract UniversalClipBreakerSpell is DssEmergencySpell {
-    IlkRegistryLike public immutable ilkReg = IlkRegistryLike(_log.getAddress("ILK_REGISTRY"));
-    ClipperMomLike public immutable clipperMom = ClipperMomLike(_log.getAddress("CLIPPER_MOM"));
-
     string public constant override description = "Emergency Spell | Universal Clip Breaker";
-
+    /// @dev During an emergency, set the breaker level to 3  to prevent botyh `kick()`, `redo()` and `take()`.
     uint256 public constant BREAKER_LEVEL = 3;
-    // For level 3 breakers, the delay is not applicable, so we set it to zero.
+    /// @dev The delay is not applicable for level 3 breakers, so we set it to zero.
     uint256 public constant BREAKER_DELAY = 0;
 
+    IlkRegistryLike public immutable ilkReg = IlkRegistryLike(_log.getAddress("ILK_REGISTRY"));
+    ClipperMomLike public immutable clipperMom = ClipperMomLike(_log.getAddress("CLIPPER_MOM"));
+
     event SetBreaker(bytes32 indexed ilk, address indexed clip);
 
     /**
-     * @notice Set breakers, when possible, for all Clip instances that can be found in the ilk registry.
+     * @notice Sets breakers, when possible, for all Clip instances that can be found in the ilk registry.
      */
     function _emeregencyActions() internal override {
         bytes32[] memory ilks = ilkReg.list();
@@ -54,7 +54,7 @@ contract UniversalClipBreakerSpell is DssEmergencySpell {
     }
 
     /**
-     * @notice Set breakers for all Clips in the batch.
+     * @notice Sets breakers for all Clips in the batch.
      * @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).
@@ -67,7 +67,7 @@ contract UniversalClipBreakerSpell is DssEmergencySpell {
     }
 
     /**
-     * @notice Set breakers, when possible, for all Clip instances that can be found from the `ilks` list.
+     * @notice Sets breakers, when possible, for all Clip instances that can be found from the `ilks` list.
      * @param ilks The list of ilks to consider.
      */
     function _doSetBreaker(bytes32[] memory ilks) internal {
@@ -95,8 +95,8 @@ contract UniversalClipBreakerSpell is DssEmergencySpell {
     }
 
     /**
-     * @notice Return whether the spell is done or not.
-     * @dev Check if all possible Clip instances from the ilk registry have stopped = 3.
+     * @notice Returns whether the spell is done or not.
+     * @dev Checks if all possible Clip instances from the ilk registry have stopped = 3.
      */
     function done() external view returns (bool) {
         bytes32[] memory ilks = ilkReg.list();
diff --git a/src/osm-stop/SingleOsmStopSpell.sol b/src/osm-stop/SingleOsmStopSpell.sol
index 0afa4ea..c8a6d5e 100644
--- a/src/osm-stop/SingleOsmStopSpell.sol
+++ b/src/osm-stop/SingleOsmStopSpell.sol
@@ -46,8 +46,8 @@ contract SingleOsmStopSpell is DssEmergencySpell {
     }
 
     /**
-     * @notice Return whether the spell is done or not.
-     * @dev Check if the OSM instance is stopped.
+     * @notice Returns whether the spell is done or not.
+     * @dev Checks if the OSM instance is stopped.
      */
     function done() external view returns (bool) {
         return OsmLike(osmMom.osms(ilk)).stopped() == 1;
diff --git a/src/osm-stop/UniversalOsmStopSpell.sol b/src/osm-stop/UniversalOsmStopSpell.sol
index ffedf48..38bd4cd 100644
--- a/src/osm-stop/UniversalOsmStopSpell.sol
+++ b/src/osm-stop/UniversalOsmStopSpell.sol
@@ -37,15 +37,15 @@ interface WardsLike {
 }
 
 contract UniversalOsmStopSpell is DssEmergencySpell {
+    string public constant override description = "Emergency Spell | Universal OSM Stop";
+
     IlkRegistryLike public immutable ilkReg = IlkRegistryLike(_log.getAddress("ILK_REGISTRY"));
     OsmMomLike public immutable osmMom = OsmMomLike(_log.getAddress("OSM_MOM"));
 
-    string public constant override description = "Emergency Spell | Universal OSM Stop";
-
     event Stop(bytes32 indexed ilk, address indexed osm);
 
     /**
-     * @notice Stop, when possible, all OSMs that can be found through the ilk registry.
+     * @notice Stops, when possible, all OSMs that can be found through the ilk registry.
      */
     function _emeregencyActions() internal override {
         bytes32[] memory ilks = ilkReg.list();
@@ -53,7 +53,7 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
     }
 
     /**
-     * @notice Stop all OSMs in the batch.
+     * @notice Stops all OSMs in the batch.
      * @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).
@@ -66,7 +66,7 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
     }
 
     /**
-     * @notice Stop, when possible, all OSMs that can be found from the `ilks` list.
+     * @notice Stops, when possible, all OSMs that can be found from the `ilks` list.
      * @param ilks The list of ilks to consider.
      */
     function _doStop(bytes32[] memory ilks) internal {
@@ -94,8 +94,8 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
     }
 
     /**
-     * @notice Return whether the spell is done or not.
-     * @dev Check if all possible OSM instances from the ilk registry are stopped.
+     * @notice Returns whether the spell is done or not.
+     * @dev Checks if all possible OSM instances from the ilk registry are stopped.
      */
     function done() external view returns (bool) {
         bytes32[] memory ilks = ilkReg.list();

From d1bee94ebdbdc7e017a8096831fd25fad00ab931 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Fri, 17 May 2024 10:26:16 -0300
Subject: [PATCH 19/38] refactor: improve universal spells structure and
 comments

---
 src/clip-breaker/UniversalClipBreakerSpell.sol | 10 +++++-----
 src/osm-stop/UniversalOsmStopSpell.sol         | 17 +++++++----------
 2 files changed, 12 insertions(+), 15 deletions(-)

diff --git a/src/clip-breaker/UniversalClipBreakerSpell.sol b/src/clip-breaker/UniversalClipBreakerSpell.sol
index c9dc5db..dc5b9d8 100644
--- a/src/clip-breaker/UniversalClipBreakerSpell.sol
+++ b/src/clip-breaker/UniversalClipBreakerSpell.sol
@@ -79,7 +79,7 @@ contract UniversalClipBreakerSpell is DssEmergencySpell {
 
             try ClipLike(clip).wards(address(clipperMom)) returns (uint256 ward) {
                 // Ignore Clip instances that have not relied on ClipperMom.
-                if (ward != 1) continue;
+                if (ward == 0) continue;
             } catch Error(string memory reason) {
                 // If the reason is empty, it means the contract is most likely not a Clip instance.
                 require(bytes(reason).length == 0, reason);
@@ -88,7 +88,7 @@ contract UniversalClipBreakerSpell is DssEmergencySpell {
             try clipperMom.setBreaker(clip, BREAKER_LEVEL, BREAKER_DELAY) {
                 emit SetBreaker(ilk, clip);
             } catch Error(string memory reason) {
-                // Ignore any failing calls to `clipeprMom.setBreaker` with no revert reason.
+                // If the reason is empty, it means the contract is most likely not a Clip instance.
                 require(bytes(reason).length == 0, reason);
             }
         }
@@ -108,16 +108,16 @@ contract UniversalClipBreakerSpell is DssEmergencySpell {
 
             try ClipLike(clip).wards(address(clipperMom)) returns (uint256 ward) {
                 // Ignore Clip instances that have not relied on ClipperMom.
-                if (ward != 1) continue;
+                if (ward == 0) continue;
             } catch {
-                // Ignore any errors.
+                // If The call failed, it means the contract is most likely not a Clip instance, so it can be ignored.
                 continue;
             }
 
             try ClipLike(clip).stopped() returns (uint256 stopped) {
                 if (stopped != BREAKER_LEVEL) return false;
             } catch {
-                // Ignore any errors.
+                // If the call failed, it means the contract is most likely not a Clip instance, so it can be ignored.
                 continue;
             }
         }
diff --git a/src/osm-stop/UniversalOsmStopSpell.sol b/src/osm-stop/UniversalOsmStopSpell.sol
index 38bd4cd..4f1a1a8 100644
--- a/src/osm-stop/UniversalOsmStopSpell.sol
+++ b/src/osm-stop/UniversalOsmStopSpell.sol
@@ -30,9 +30,6 @@ interface OsmMomLike {
 
 interface OsmLike {
     function stopped() external view returns (uint256);
-}
-
-interface WardsLike {
     function wards(address who) external view returns (uint256);
 }
 
@@ -75,9 +72,9 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
 
             if (osm == address(0)) continue;
 
-            try WardsLike(osm).wards(address(osmMom)) returns (uint256 ward) {
+            try OsmLike(osm).wards(address(osmMom)) returns (uint256 ward) {
                 // Ignore Osm instances that have not relied on OsmMom.
-                if (ward != 1) continue;
+                if (ward == 0) continue;
             } catch Error(string memory reason) {
                 // If the reason is empty, it means the contract is most likely not an OSM instance.
                 require(bytes(reason).length == 0, reason);
@@ -87,7 +84,7 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
             try OsmMomLike(osmMom).stop(ilks[i]) {
                 emit Stop(ilks[i], osm);
             } catch Error(string memory reason) {
-                // Ignore any failing calls to `osmMom.stop` with no revert reason.
+                // If the reason is empty, it means the contract is most likely not an OSM instance.
                 require(bytes(reason).length == 0, reason);
             }
         }
@@ -104,11 +101,11 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
 
             if (osm == address(0)) continue;
 
-            try WardsLike(osm).wards(address(osmMom)) returns (uint256 ward) {
+            try OsmLike(osm).wards(address(osmMom)) returns (uint256 ward) {
                 // Ignore Osm instances that have not relied on OsmMom.
-                if (ward != 1) continue;
+                if (ward == 0) continue;
             } catch {
-                // Ignore any errors.
+                // If the call failed, it means the contract is most likely not an OSM instance, so it can be ignored.
                 continue;
             }
 
@@ -116,7 +113,7 @@ contract UniversalOsmStopSpell is DssEmergencySpell {
                 // If any of the OSMs that match the conditions is not stopped, the spell was not executed yet.
                 if (stopped == 0) return false;
             } catch {
-                // Ignore any errors.
+                // If the call failed, it means the contract is most likely not an OSM instance, so it can be ignored.
                 continue;
             }
         }

From 6b421dd0634f80abc85ccfb9668618df273c6165 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Wed, 22 May 2024 15:26:40 -0300
Subject: [PATCH 20/38] refactor: rename "universal" spells to "multi"

---
 ...pBreakerSpell.sol => MultiClipBreakerSpell.sol} |  4 ++--
 ...sol => MultiClipBreakerSpell.t.integration.sol} | 14 +++++++-------
 ...ersalOsmStopSpell.sol => MultiOsmStopSpell.sol} |  4 ++--
 ...ion.sol => MultiOsmStopSpell.t.integration.sol} | 14 +++++++-------
 4 files changed, 18 insertions(+), 18 deletions(-)
 rename src/clip-breaker/{UniversalClipBreakerSpell.sol => MultiClipBreakerSpell.sol} (98%)
 rename src/clip-breaker/{UniversalClipBreakerSpell.t.integration.sol => MultiClipBreakerSpell.t.integration.sol} (94%)
 rename src/osm-stop/{UniversalOsmStopSpell.sol => MultiOsmStopSpell.sol} (98%)
 rename src/osm-stop/{UniversalOsmStopSpell.t.integration.sol => MultiOsmStopSpell.t.integration.sol} (93%)

diff --git a/src/clip-breaker/UniversalClipBreakerSpell.sol b/src/clip-breaker/MultiClipBreakerSpell.sol
similarity index 98%
rename from src/clip-breaker/UniversalClipBreakerSpell.sol
rename to src/clip-breaker/MultiClipBreakerSpell.sol
index dc5b9d8..e9261ca 100644
--- a/src/clip-breaker/UniversalClipBreakerSpell.sol
+++ b/src/clip-breaker/MultiClipBreakerSpell.sol
@@ -33,8 +33,8 @@ interface ClipLike {
     function stopped() external view returns (uint256);
 }
 
-contract UniversalClipBreakerSpell is DssEmergencySpell {
-    string public constant override description = "Emergency Spell | Universal Clip Breaker";
+contract MultiClipBreakerSpell is DssEmergencySpell {
+    string public constant override description = "Emergency Spell | Multi Clip Breaker";
     /// @dev During an emergency, set the breaker level to 3  to prevent botyh `kick()`, `redo()` and `take()`.
     uint256 public constant BREAKER_LEVEL = 3;
     /// @dev The delay is not applicable for level 3 breakers, so we set it to zero.
diff --git a/src/clip-breaker/UniversalClipBreakerSpell.t.integration.sol b/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
similarity index 94%
rename from src/clip-breaker/UniversalClipBreakerSpell.t.integration.sol
rename to src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
index 2bff2a0..e516dbf 100644
--- a/src/clip-breaker/UniversalClipBreakerSpell.t.integration.sol
+++ b/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
@@ -17,7 +17,7 @@ pragma solidity ^0.8.16;
 
 import {stdStorage, StdStorage} from "forge-std/Test.sol";
 import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol";
-import {UniversalClipBreakerSpell} from "./UniversalClipBreakerSpell.sol";
+import {MultiClipBreakerSpell} from "./MultiClipBreakerSpell.sol";
 
 interface IlkRegistryLike {
     function count() external view returns (uint256);
@@ -34,7 +34,7 @@ interface ClipLike {
     function stopped() external view returns (uint256);
 }
 
-contract UniversalClipBreakerSpellTest is DssTest {
+contract MultiClipBreakerSpellTest is DssTest {
     using stdStorage for StdStorage;
 
     address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
@@ -42,7 +42,7 @@ contract UniversalClipBreakerSpellTest is DssTest {
     address chief;
     IlkRegistryLike ilkReg;
     address clipperMom;
-    UniversalClipBreakerSpell spell;
+    MultiClipBreakerSpell spell;
 
     mapping(bytes32 => bool) ilksToIgnore;
 
@@ -54,7 +54,7 @@ contract UniversalClipBreakerSpellTest is DssTest {
         chief = dss.chainlog.getAddress("MCD_ADM");
         ilkReg = IlkRegistryLike(dss.chainlog.getAddress("ILK_REGISTRY"));
         clipperMom = dss.chainlog.getAddress("CLIPPER_MOM");
-        spell = new UniversalClipBreakerSpell();
+        spell = new MultiClipBreakerSpell();
 
         stdstore.target(chief).sig("hat()").checked_write(address(spell));
 
@@ -105,7 +105,7 @@ contract UniversalClipBreakerSpellTest is DssTest {
         }
     }
 
-    function testUniversalClipBreakerOnSchedule() public {
+    function testMultiClipBreakerOnSchedule() public {
         _checkClipMaxStoppedStatus({ilks: ilkReg.list(), maxExpected: 2});
         assertFalse(spell.done(), "before: spell already done");
 
@@ -115,7 +115,7 @@ contract UniversalClipBreakerSpellTest is DssTest {
         assertTrue(spell.done(), "after: spell not done");
     }
 
-    function testUniversalClipBreakerInBatches_Fuzz(uint256 batchSize) public {
+    function testMultiClipBreakerInBatches_Fuzz(uint256 batchSize) public {
         batchSize = bound(batchSize, 1, type(uint128).max);
         uint256 count = ilkReg.count();
         uint256 maxEnd = count - 1;
@@ -153,7 +153,7 @@ contract UniversalClipBreakerSpellTest is DssTest {
         assertEq(ClipLike(clipEthA).stopped(), 0, "ETH-A Clip was not ignored");
     }
 
-    function testRevertUniversalClipBreakerWhenItDoesNotHaveTheHat() public {
+    function testRevertMultiClipBreakerWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 
         _checkClipMaxStoppedStatus({ilks: ilkReg.list(), maxExpected: 2});
diff --git a/src/osm-stop/UniversalOsmStopSpell.sol b/src/osm-stop/MultiOsmStopSpell.sol
similarity index 98%
rename from src/osm-stop/UniversalOsmStopSpell.sol
rename to src/osm-stop/MultiOsmStopSpell.sol
index 4f1a1a8..b9b8d8f 100644
--- a/src/osm-stop/UniversalOsmStopSpell.sol
+++ b/src/osm-stop/MultiOsmStopSpell.sol
@@ -33,8 +33,8 @@ interface OsmLike {
     function wards(address who) external view returns (uint256);
 }
 
-contract UniversalOsmStopSpell is DssEmergencySpell {
-    string public constant override description = "Emergency Spell | Universal OSM Stop";
+contract MultiOsmStopSpell is DssEmergencySpell {
+    string public constant override description = "Emergency Spell | Multi OSM Stop";
 
     IlkRegistryLike public immutable ilkReg = IlkRegistryLike(_log.getAddress("ILK_REGISTRY"));
     OsmMomLike public immutable osmMom = OsmMomLike(_log.getAddress("OSM_MOM"));
diff --git a/src/osm-stop/UniversalOsmStopSpell.t.integration.sol b/src/osm-stop/MultiOsmStopSpell.t.integration.sol
similarity index 93%
rename from src/osm-stop/UniversalOsmStopSpell.t.integration.sol
rename to src/osm-stop/MultiOsmStopSpell.t.integration.sol
index 366b10d..029cb0a 100644
--- a/src/osm-stop/UniversalOsmStopSpell.t.integration.sol
+++ b/src/osm-stop/MultiOsmStopSpell.t.integration.sol
@@ -17,7 +17,7 @@ pragma solidity ^0.8.16;
 
 import {stdStorage, StdStorage} from "forge-std/Test.sol";
 import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol";
-import {UniversalOsmStopSpell} from "./UniversalOsmStopSpell.sol";
+import {MultiOsmStopSpell} from "./MultiOsmStopSpell.sol";
 
 interface OsmMomLike {
     function osms(bytes32) external view returns (address);
@@ -39,7 +39,7 @@ interface IlkRegistryLike {
     function pip(bytes32 ilk) external view returns (address);
 }
 
-contract UniversalOsmStopSpellTest is DssTest {
+contract MultiOsmStopSpellTest is DssTest {
     using stdStorage for StdStorage;
 
     address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
@@ -47,7 +47,7 @@ contract UniversalOsmStopSpellTest is DssTest {
     address chief;
     OsmMomLike osmMom;
     IlkRegistryLike ilkReg;
-    UniversalOsmStopSpell spell;
+    MultiOsmStopSpell spell;
 
     mapping(bytes32 => bool) ilksToIgnore;
 
@@ -59,7 +59,7 @@ contract UniversalOsmStopSpellTest is DssTest {
         chief = dss.chainlog.getAddress("MCD_ADM");
         osmMom = OsmMomLike(dss.chainlog.getAddress("OSM_MOM"));
         ilkReg = IlkRegistryLike(dss.chainlog.getAddress("ILK_REGISTRY"));
-        spell = new UniversalOsmStopSpell();
+        spell = new MultiOsmStopSpell();
 
         stdstore.target(chief).sig("hat()").checked_write(address(spell));
 
@@ -111,7 +111,7 @@ contract UniversalOsmStopSpellTest is DssTest {
         }
     }
 
-    function testUniversalOracleStopOnSchedule() public {
+    function testMultiOracleStopOnSchedule() public {
         _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 0});
         assertFalse(spell.done(), "before: spell already done");
 
@@ -121,7 +121,7 @@ contract UniversalOsmStopSpellTest is DssTest {
         assertTrue(spell.done(), "after: spell not done");
     }
 
-    function testUniversalOracleStopInBatches_Fuzz(uint256 batchSize) public {
+    function testMultiOracleStopInBatches_Fuzz(uint256 batchSize) public {
         batchSize = bound(batchSize, 1, type(uint128).max);
         uint256 count = ilkReg.count();
         uint256 maxEnd = count - 1;
@@ -158,7 +158,7 @@ contract UniversalOsmStopSpellTest is DssTest {
         assertEq(OsmLike(pipEth).stopped(), 0, "ETH-A pip was not ignored");
     }
 
-    function testRevertUniversalOracleStopWhenItDoesNotHaveTheHat() public {
+    function testRevertMultiOracleStopWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 
         _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 0});

From 85a7adc424fffe8be3db5d97dc8cbe1abfd035e1 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Wed, 22 May 2024 16:10:46 -0300
Subject: [PATCH 21/38] feat: guard against empty ilks list checks

---
 src/clip-breaker/MultiClipBreakerSpell.t.integration.sol | 4 ++++
 src/osm-stop/MultiOsmStopSpell.t.integration.sol         | 7 +++----
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol b/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
index e516dbf..39d1208 100644
--- a/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
+++ b/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
@@ -167,6 +167,8 @@ contract MultiClipBreakerSpellTest is DssTest {
     }
 
     function _checkClipMaxStoppedStatus(bytes32[] memory ilks, uint256 maxExpected) internal view {
+        assertTrue(ilks.length > 0, "empty ilks list");
+
         for (uint256 i = 0; i < ilks.length; i++) {
             if (ilksToIgnore[ilks[i]]) continue;
 
@@ -178,6 +180,8 @@ contract MultiClipBreakerSpellTest is DssTest {
     }
 
     function _checkClipStoppedStatus(bytes32[] memory ilks, uint256 expected) internal view {
+        assertTrue(ilks.length > 0, "empty ilks list");
+
         for (uint256 i = 0; i < ilks.length; i++) {
             if (ilksToIgnore[ilks[i]]) continue;
 
diff --git a/src/osm-stop/MultiOsmStopSpell.t.integration.sol b/src/osm-stop/MultiOsmStopSpell.t.integration.sol
index 029cb0a..07fad2c 100644
--- a/src/osm-stop/MultiOsmStopSpell.t.integration.sol
+++ b/src/osm-stop/MultiOsmStopSpell.t.integration.sol
@@ -26,9 +26,6 @@ interface OsmMomLike {
 interface OsmLike {
     function stopped() external view returns (uint256);
     function osms(bytes32 ilk) external view returns (address);
-}
-
-interface WardsLike {
     function wards(address who) external view returns (uint256);
 }
 
@@ -97,7 +94,7 @@ contract MultiOsmStopSpellTest is DssTest {
                 continue;
             }
 
-            try WardsLike(osm).wards(address(osmMom)) returns (uint256 ward) {
+            try OsmLike(osm).wards(address(osmMom)) returns (uint256 ward) {
                 if (ward == 0) {
                     ilksToIgnore[ilks[i]] = true;
                     emit log_named_string("Ignoring ilk | OsmMom not authorized", ilkStr);
@@ -170,6 +167,8 @@ contract MultiOsmStopSpellTest is DssTest {
     }
 
     function _checkOsmStoppedStatus(bytes32[] memory ilks, uint256 expected) internal view {
+        assertTrue(ilks.length > 0, "empty ilks list");
+
         for (uint256 i = 0; i < ilks.length; i++) {
             if (ilksToIgnore[ilks[i]]) continue;
 

From efec4e777b03acd5c1aa35557ee42280433fff6e Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Wed, 22 May 2024 16:11:31 -0300
Subject: [PATCH 22/38] feat(auto-line-wipe): add wipe for multiple ilks

---
 src/auto-line-wipe/MultiAutoLineWipeSpell.sol |  99 ++++++++++++
 .../MultiAutoLineWipeSpell.t.integration.sol  | 150 ++++++++++++++++++
 2 files changed, 249 insertions(+)
 create mode 100644 src/auto-line-wipe/MultiAutoLineWipeSpell.sol
 create mode 100644 src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol

diff --git a/src/auto-line-wipe/MultiAutoLineWipeSpell.sol b/src/auto-line-wipe/MultiAutoLineWipeSpell.sol
new file mode 100644
index 0000000..9e28ffe
--- /dev/null
+++ b/src/auto-line-wipe/MultiAutoLineWipeSpell.sol
@@ -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;
+    }
+}
diff --git a/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol b/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol
new file mode 100644
index 0000000..9a15628
--- /dev/null
+++ b/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol
@@ -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]))
+            );
+        }
+    }
+}

From 5b57421055a2cfbe3e89f39cb9cb005f57fe369e Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Wed, 22 May 2024 16:38:37 -0300
Subject: [PATCH 23/38] feat(ddm-disable): add single DDM disable spell

---
 src/ddm-disable/SingleDdmDisableSpell.sol     | 70 ++++++++++++++
 .../SingleDdmDisableSpell.t.integration.sol   | 91 +++++++++++++++++++
 2 files changed, 161 insertions(+)
 create mode 100644 src/ddm-disable/SingleDdmDisableSpell.sol
 create mode 100644 src/ddm-disable/SingleDdmDisableSpell.t.integration.sol

diff --git a/src/ddm-disable/SingleDdmDisableSpell.sol b/src/ddm-disable/SingleDdmDisableSpell.sol
new file mode 100644
index 0000000..d24a58c
--- /dev/null
+++ b/src/ddm-disable/SingleDdmDisableSpell.sol
@@ -0,0 +1,70 @@
+// 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 DdmMomLike {
+    function disable(address plan) external;
+}
+
+interface DdmPlanLike {
+    function active() external view returns (bool);
+    function disable() external;
+}
+
+interface DdmHubLike {
+    function plan(bytes32 ilk) external view returns (address plan);
+}
+
+contract SingleDdmDisableSpell is DssEmergencySpell {
+    DdmMomLike public immutable ddmMom = DdmMomLike(_log.getAddress("DIRECT_MOM"));
+    DdmHubLike public immutable ddmHub = DdmHubLike(_log.getAddress("DIRECT_HUB"));
+    bytes32 public immutable ilk;
+
+    event Disable(address indexed plan);
+
+    constructor(bytes32 _ilk) {
+        ilk = _ilk;
+    }
+
+    function description() external view returns (string memory) {
+        return string(abi.encodePacked("Emergency Spell | Disable DDM Plan: ", ilk));
+    }
+
+    function _emeregencyActions() internal override {
+        address plan = ddmHub.plan(ilk);
+        ddmMom.disable(plan);
+        emit Disable(plan);
+    }
+
+    /**
+     * @notice Returns whether the spell is done or not.
+     * @dev Checks if the plan instance has stopped = 3.
+     */
+    function done() external view returns (bool) {
+        return !DdmPlanLike(ddmHub.plan(ilk)).active();
+    }
+}
+
+contract SingleDdmDisableFactory {
+    event Deploy(bytes32 indexed ilk, address spell);
+
+    function deploy(bytes32 ilk) external returns (address spell) {
+        spell = address(new SingleDdmDisableSpell(ilk));
+        emit Deploy(ilk, spell);
+    }
+}
diff --git a/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol b/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol
new file mode 100644
index 0000000..e3cbfd3
--- /dev/null
+++ b/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol
@@ -0,0 +1,91 @@
+// 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 {DssEmergencySpellLike} from "../DssEmergencySpell.sol";
+import {SingleDdmDisableFactory} from "./SingleDdmDisableSpell.sol";
+
+interface DdmMomLike {
+    function disable(address plan) external;
+}
+
+interface DdmHubLike {
+    function plan(bytes32 ilk) external view returns (address);
+}
+
+interface DdmPlanLike {
+    function active() external view returns (bool);
+}
+
+contract SingleDdmDisableSpellTest is DssTest {
+    using stdStorage for StdStorage;
+
+    address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
+    DssInstance dss;
+    address chief;
+    bytes32 ilk = "DIRECT-SPARK-DAI";
+    DdmMomLike ddmMom;
+    DdmHubLike ddmHub;
+    DdmPlanLike plan;
+    SingleDdmDisableFactory factory;
+    DssEmergencySpellLike spell;
+
+    function setUp() public {
+        vm.createSelectFork("mainnet");
+
+        dss = MCD.loadFromChainlog(CHAINLOG);
+        MCD.giveAdminAccess(dss);
+        chief = dss.chainlog.getAddress("MCD_ADM");
+        ddmMom = DdmMomLike(dss.chainlog.getAddress("DIRECT_MOM"));
+        ddmHub = DdmHubLike(dss.chainlog.getAddress("DIRECT_HUB"));
+        plan = DdmPlanLike(ddmHub.plan(ilk));
+        factory = new SingleDdmDisableFactory();
+        spell = DssEmergencySpellLike(factory.deploy(ilk));
+
+        stdstore.target(chief).sig("hat()").checked_write(address(spell));
+
+        vm.makePersistent(chief);
+    }
+
+    function testDdmDisableOnSchedule() public {
+        assertTrue(plan.active(), "before: plan already disabled");
+        assertFalse(spell.done(), "before: spell already done");
+
+        vm.expectEmit(true, true, true, true);
+        emit Disable(address(plan));
+        spell.schedule();
+
+        assertFalse(plan.active(), "after: plan not disabled");
+        assertTrue(spell.done(), "after: spell not done");
+    }
+
+    function testRevertDdmDisableWhenItDoesNotHaveTheHat() public {
+        stdstore.target(chief).sig("hat()").checked_write(address(0));
+
+        assertTrue(plan.active(), "before: plan already disabled");
+        assertFalse(spell.done(), "before: spell already done");
+
+        vm.expectRevert();
+        spell.schedule();
+
+        assertTrue(plan.active(), "after: plan disabled unexpectedly");
+        assertFalse(spell.done(), "after: spell done unexpectedly");
+    }
+
+    event Disable(address indexed plan);
+}

From 1a375b5068cf89cbb02fbd42478b25bcf00e597a Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 23 May 2024 17:59:19 -0300
Subject: [PATCH 24/38] refactor(ddm-disable): change `done()` expression for
 better readability

---
 src/ddm-disable/SingleDdmDisableSpell.sol | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/ddm-disable/SingleDdmDisableSpell.sol b/src/ddm-disable/SingleDdmDisableSpell.sol
index d24a58c..f3f1cd4 100644
--- a/src/ddm-disable/SingleDdmDisableSpell.sol
+++ b/src/ddm-disable/SingleDdmDisableSpell.sol
@@ -56,7 +56,7 @@ contract SingleDdmDisableSpell is DssEmergencySpell {
      * @dev Checks if the plan instance has stopped = 3.
      */
     function done() external view returns (bool) {
-        return !DdmPlanLike(ddmHub.plan(ilk)).active();
+        return DdmPlanLike(ddmHub.plan(ilk)).active() == false;
     }
 }
 

From 2eeec5b7bbd6726a23e92855a47cf78402381ecf Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Tue, 25 Jun 2024 15:47:23 -0300
Subject: [PATCH 25/38] refactor: fix typo in function name

---
 src/DssEmergencySpell.sol                      | 4 ++--
 src/auto-line-wipe/MultiAutoLineWipeSpell.sol  | 2 +-
 src/auto-line-wipe/SingleAutoLineWipeSpell.sol | 2 +-
 src/clip-breaker/MultiClipBreakerSpell.sol     | 2 +-
 src/clip-breaker/SingleClipBreakerSpell.sol    | 2 +-
 src/ddm-disable/SingleDdmDisableSpell.sol      | 2 +-
 src/osm-stop/MultiOsmStopSpell.sol             | 2 +-
 src/osm-stop/SingleOsmStopSpell.sol            | 2 +-
 8 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/DssEmergencySpell.sol b/src/DssEmergencySpell.sol
index 50e23a0..769e61e 100644
--- a/src/DssEmergencySpell.sol
+++ b/src/DssEmergencySpell.sol
@@ -57,7 +57,7 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
      *      scheduled. Emergency spell take affect immediately, so there is no need to call `pause.plot()`.
      */
     function schedule() external {
-        _emeregencyActions();
+        _emergencyActions();
     }
 
     /**
@@ -69,6 +69,6 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
     /**
      * @notice Implements the emergency actions to be triggered by the spell.
      */
-    function _emeregencyActions() internal virtual;
+    function _emergencyActions() internal virtual;
 
 }
diff --git a/src/auto-line-wipe/MultiAutoLineWipeSpell.sol b/src/auto-line-wipe/MultiAutoLineWipeSpell.sol
index 9e28ffe..9ab5b23 100644
--- a/src/auto-line-wipe/MultiAutoLineWipeSpell.sol
+++ b/src/auto-line-wipe/MultiAutoLineWipeSpell.sol
@@ -47,7 +47,7 @@ contract MultiAutoLineWipeSpell is DssEmergencySpell {
     /**
      * @notice Wipes, when possible, all ilks from auto-line;
      */
-    function _emeregencyActions() internal override {
+    function _emergencyActions() internal override {
         bytes32[] memory ilks = ilkReg.list();
         _doWipe(ilks);
     }
diff --git a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
index c652247..bd41f06 100644
--- a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
+++ b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
@@ -43,7 +43,7 @@ contract SingleAutoLineWipeSpell is DssEmergencySpell {
         return string(abi.encodePacked("Emergency Spell | Auto-Line Wipe: ", ilk));
     }
 
-    function _emeregencyActions() internal override {
+    function _emergencyActions() internal override {
         uint256 prevLine = lineMom.wipe(ilk);
         emit Wipe(ilk, prevLine);
     }
diff --git a/src/clip-breaker/MultiClipBreakerSpell.sol b/src/clip-breaker/MultiClipBreakerSpell.sol
index e9261ca..68eecb3 100644
--- a/src/clip-breaker/MultiClipBreakerSpell.sol
+++ b/src/clip-breaker/MultiClipBreakerSpell.sol
@@ -48,7 +48,7 @@ contract MultiClipBreakerSpell is DssEmergencySpell {
     /**
      * @notice Sets breakers, when possible, for all Clip instances that can be found in the ilk registry.
      */
-    function _emeregencyActions() internal override {
+    function _emergencyActions() internal override {
         bytes32[] memory ilks = ilkReg.list();
         _doSetBreaker(ilks);
     }
diff --git a/src/clip-breaker/SingleClipBreakerSpell.sol b/src/clip-breaker/SingleClipBreakerSpell.sol
index 4f66c3a..2ef7bf6 100644
--- a/src/clip-breaker/SingleClipBreakerSpell.sol
+++ b/src/clip-breaker/SingleClipBreakerSpell.sol
@@ -49,7 +49,7 @@ contract SingleClipBreakerSpell is DssEmergencySpell {
         return string(abi.encodePacked("Emergency Spell | Set Clip Breaker: ", ilk));
     }
 
-    function _emeregencyActions() internal override {
+    function _emergencyActions() internal override {
         address clip = ilkReg.xlip(ilk);
         clipperMom.setBreaker(clip, BREAKER_LEVEL, BREAKER_DELAY);
         emit SetBreaker(clip);
diff --git a/src/ddm-disable/SingleDdmDisableSpell.sol b/src/ddm-disable/SingleDdmDisableSpell.sol
index f3f1cd4..319ea43 100644
--- a/src/ddm-disable/SingleDdmDisableSpell.sol
+++ b/src/ddm-disable/SingleDdmDisableSpell.sol
@@ -45,7 +45,7 @@ contract SingleDdmDisableSpell is DssEmergencySpell {
         return string(abi.encodePacked("Emergency Spell | Disable DDM Plan: ", ilk));
     }
 
-    function _emeregencyActions() internal override {
+    function _emergencyActions() internal override {
         address plan = ddmHub.plan(ilk);
         ddmMom.disable(plan);
         emit Disable(plan);
diff --git a/src/osm-stop/MultiOsmStopSpell.sol b/src/osm-stop/MultiOsmStopSpell.sol
index b9b8d8f..d5be8ab 100644
--- a/src/osm-stop/MultiOsmStopSpell.sol
+++ b/src/osm-stop/MultiOsmStopSpell.sol
@@ -44,7 +44,7 @@ contract MultiOsmStopSpell is DssEmergencySpell {
     /**
      * @notice Stops, when possible, all OSMs that can be found through the ilk registry.
      */
-    function _emeregencyActions() internal override {
+    function _emergencyActions() internal override {
         bytes32[] memory ilks = ilkReg.list();
         _doStop(ilks);
     }
diff --git a/src/osm-stop/SingleOsmStopSpell.sol b/src/osm-stop/SingleOsmStopSpell.sol
index c8a6d5e..d8f7fb5 100644
--- a/src/osm-stop/SingleOsmStopSpell.sol
+++ b/src/osm-stop/SingleOsmStopSpell.sol
@@ -40,7 +40,7 @@ contract SingleOsmStopSpell is DssEmergencySpell {
         return string(abi.encodePacked("Emergency Spell | OSM Stop: ", ilk));
     }
 
-    function _emeregencyActions() internal override {
+    function _emergencyActions() internal override {
         osmMom.stop(ilk);
         emit Stop(osmMom.osms(ilk));
     }

From cfafa6bd10f2d5b186ca19c679f328b69e0a28ac Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Tue, 25 Jun 2024 16:17:09 -0300
Subject: [PATCH 26/38] refactor(single-auto-line-wipe): remove `Wipe` event
 parameters

---
 src/auto-line-wipe/SingleAutoLineWipeSpell.sol              | 6 +++---
 .../SingleAutoLineWipeSpell.t.integration.sol               | 5 ++---
 2 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
index bd41f06..079fdfe 100644
--- a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
+++ b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
@@ -33,7 +33,7 @@ contract SingleAutoLineWipeSpell is DssEmergencySpell {
     LineMomLike public immutable lineMom = LineMomLike(_log.getAddress("LINE_MOM"));
     bytes32 public immutable ilk;
 
-    event Wipe(bytes32 indexed ilk, uint256 prevLine);
+    event Wipe();
 
     constructor(bytes32 _ilk) {
         ilk = _ilk;
@@ -44,8 +44,8 @@ contract SingleAutoLineWipeSpell is DssEmergencySpell {
     }
 
     function _emergencyActions() internal override {
-        uint256 prevLine = lineMom.wipe(ilk);
-        emit Wipe(ilk, prevLine);
+        lineMom.wipe(ilk);
+        emit Wipe();
     }
 
     /**
diff --git a/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol b/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol
index f8c3b6e..5ff6de5 100644
--- a/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol
+++ b/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol
@@ -62,8 +62,7 @@ contract SingleAutoLineWipeSpellTest is DssTest {
         assertFalse(spell.done(), "before: spell already done");
 
         vm.expectEmit(true, true, true, false);
-        // Ignore prevLine for now
-        emit Wipe(ilk, 0);
+        emit Wipe();
         spell.schedule();
 
         (uint256 maxLine, uint256 gap,,,) = autoLine.ilks(ilk);
@@ -89,5 +88,5 @@ contract SingleAutoLineWipeSpellTest is DssTest {
         assertFalse(spell.done(), "after: spell done unexpectedly");
     }
 
-    event Wipe(bytes32 indexed autoLine, uint256 prevLine);
+    event Wipe();
 }

From 359fc0d395ea602c2514b9366acb71b84bf13b76 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Tue, 25 Jun 2024 18:01:35 -0300
Subject: [PATCH 27/38] refactor(single-ddm-disable): remove unused function
 from interface

---
 src/ddm-disable/SingleDdmDisableSpell.sol | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/ddm-disable/SingleDdmDisableSpell.sol b/src/ddm-disable/SingleDdmDisableSpell.sol
index 319ea43..f42f14f 100644
--- a/src/ddm-disable/SingleDdmDisableSpell.sol
+++ b/src/ddm-disable/SingleDdmDisableSpell.sol
@@ -23,7 +23,6 @@ interface DdmMomLike {
 
 interface DdmPlanLike {
     function active() external view returns (bool);
-    function disable() external;
 }
 
 interface DdmHubLike {

From 50493ac34c39006c79daf005b0a4f06092161f95 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Tue, 25 Jun 2024 18:04:09 -0300
Subject: [PATCH 28/38] refactor(base-emergency-spell): change tag to
 `extcodehash(address(this))`

---
 src/DssEmergencySpell.sol               | 78 ++++++++++++++++++---
 src/DssEmergencySpell.t.integration.sol | 91 +++++++++++++++++++++++++
 2 files changed, 161 insertions(+), 8 deletions(-)
 create mode 100644 src/DssEmergencySpell.t.integration.sol

diff --git a/src/DssEmergencySpell.sol b/src/DssEmergencySpell.sol
index 769e61e..c513514 100644
--- a/src/DssEmergencySpell.sol
+++ b/src/DssEmergencySpell.sol
@@ -19,7 +19,7 @@ interface ChainlogLike {
     function getAddress(bytes32 key) external view returns (address);
 }
 
-interface DssEmergencySpellLike {
+interface DssExec {
     function action() external view returns (address);
     function cast() external;
     function description() external view returns (string memory);
@@ -35,31 +35,79 @@ interface DssEmergencySpellLike {
     function tag() external view returns (bytes32);
 }
 
+interface DssAction {
+    function actions() external;
+    function description() external view returns (string memory);
+    function execute() external;
+    function nextCastTime(uint256 eta) external view returns (uint256);
+    function officeHours() external view returns (bool);
+}
+
+interface DssEmergencySpellLike is DssExec, DssAction {
+    function description() external view override(DssExec, DssAction) returns (string memory);
+    function officeHours() external view override(DssExec, DssAction) returns (bool);
+}
+
 abstract contract DssEmergencySpell is DssEmergencySpellLike {
+    /// @dev The chainlog contract reference.
     ChainlogLike internal constant _log = ChainlogLike(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F);
 
+    /// @dev The chainlog address.
     address public constant log = address(_log);
+    /// @dev In regular spells, `eta` is used to enforce the GSM delay.
+    ///      For emergency spells, the GSM delay is not applicable.
     uint256 public constant eta = 0;
-    bytes public constant sig = "";
+    /// @dev Keeping the same value as regular spells.
+    bytes public constant sig = abi.encodeWithSelector(DssAction.execute.selector);
+    /// @dev Emergency spells should not expire.
     uint256 public constant expiration = type(uint256).max;
-    // @notice Office Hours is always `false` for emergency spells.
+    // @dev Office Hours is always `false` for emergency spells.
     bool public constant officeHours = false;
+    // @dev `action` is expected to return a valid address.
+    //         We also implement the `DssAction` interface in this contract.
     address public immutable action = address(this);
-    bytes32 public immutable tag = keccak256(abi.encodePacked(address(this)));
+    // @dev The reference to the `pause` contract.
     address public immutable pause = ChainlogLike(log).getAddress("MCD_PAUSE");
-    uint256 public immutable nextCastTime = block.timestamp;
+    // @dev An emergency spell can be cast as soon as it is deployed.
+    //      Notice that cast is always a no-op.
+    uint256 internal immutable _nextCastTime = block.timestamp;
 
+    /**
+     * @dev In regular spells, `tag` is an immutable variable with the code hash of the spell action.
+     *      It specifically uses a separate contract for spell action because `tag` is immutable and the code hash of
+     *      the contract being initialized is not accessible in the constructor.
+     *      Since we do not have a separate contract for actions in Emergency Spells, `tag` has to be turned into a
+     *      getter function instaed of an immutable variable.
+     */
+    function tag() external view returns (bytes32 _tag) {
+        assembly {
+            _tag := extcodehash(address())
+        }
+    }
 
     /**
      * @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()`.
+     *      scheduled. Emergency spells take affect immediately, so there is no need to call `pause.plot()`.
      */
     function schedule() external {
         _emergencyActions();
     }
 
+    /**
+     * @notice Implements the emergency actions to be triggered by the spell.
+     */
+    function _emergencyActions() internal virtual;
+
+    /**
+     * @notice Returns `_nextCastTime`.
+     * @dev this function exists only to keep interface compatibility with regular spells.
+     */
+    function nextCastTime() external view returns (uint256 castTime) {
+        return _nextCastTime;
+    }
+
     /**
      * @notice No-op.
      * @dev this function exists only to keep interface compatibility with regular spells.
@@ -67,8 +115,22 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
     function cast() external {}
 
     /**
-     * @notice Implements the emergency actions to be triggered by the spell.
+     * @notice No-op.
+     * @dev this function exists only to keep interface compatibility with regular spells.
      */
-    function _emergencyActions() internal virtual;
+    function execute() external {}
+
+    /**
+     * @notice No-op.
+     * @dev this function exists only to keep interface compatibility with regular spells.
+     */
+    function actions() external {}
 
+    /**
+     * @notice Returns `nextCastTime`, regardless of the input parameter.
+     * @dev this function exists only to keep interface compatibility with regular spells.
+     */
+    function nextCastTime(uint256) external view returns (uint256 castTime) {
+        return _nextCastTime;
+    }
 }
diff --git a/src/DssEmergencySpell.t.integration.sol b/src/DssEmergencySpell.t.integration.sol
new file mode 100644
index 0000000..0ab2bd1
--- /dev/null
+++ b/src/DssEmergencySpell.t.integration.sol
@@ -0,0 +1,91 @@
+// 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 {DssEmergencySpell} from "./DssEmergencySpell.sol";
+
+contract DssEmergencySpellImpl is DssEmergencySpell {
+    string public constant override description = "Emergency Spell";
+    bool public constant override done = false;
+
+    event EmergencyAction();
+
+    // No-op
+    function _emergencyActions() internal override {
+        emit EmergencyAction();
+    }
+}
+
+contract DssEmergencySpellTest is DssTest {
+    address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
+    DssInstance dss;
+    DssEmergencySpell spell;
+    address pause;
+
+    function setUp() public {
+        vm.createSelectFork("mainnet");
+
+        dss = MCD.loadFromChainlog(CHAINLOG);
+        MCD.giveAdminAccess(dss);
+
+        spell = new DssEmergencySpellImpl();
+        pause = dss.chainlog.getAddress("MCD_PAUSE");
+    }
+
+    function testEmergencySpell() public {
+        // Sanity checks
+
+        assertEq(spell.pause(), pause, "invalid pause");
+        assertEq(spell.action(), address(spell), "invalid action");
+        assertEq(spell.eta(), 0, "invalid eta");
+        assertEq(spell.nextCastTime(), block.timestamp, "invalid nextCastTime");
+        assertEq(spell.nextCastTime(1231298123), block.timestamp, "invalid nextCastTime(uint256)");
+        assertEq(spell.officeHours(), false, "invalid officeHours");
+        assertEq(spell.sig(), abi.encodeWithSignature("execute()"), "invalid sig");
+        {
+            bytes32 hash;
+            address _spell = address(spell);
+            assembly {
+                hash := extcodehash(_spell)
+            }
+            assertEq(spell.tag(), hash, "invalid tag");
+        }
+
+        // No-op checks. Allows some overhead for JUMPs and computation in the test itself.
+
+        uint256 beforeCast = gasleft();
+        spell.cast();
+        assertApproxEqAbs(gasleft(), beforeCast, 800, "cast is not a no-op");
+
+        uint256 beforeExecute = gasleft();
+        spell.execute();
+        assertApproxEqAbs(gasleft(), beforeExecute, 800, "execute is not a no-op");
+
+        uint256 beforeActions = gasleft();
+        spell.actions();
+        assertApproxEqAbs(gasleft(), beforeActions, 800, "actions is not a no-op");
+
+        // `schedule()` actually calls `_emergencyActions()`
+
+        vm.expectEmit(true, true, true, true);
+        emit EmergencyAction();
+        spell.schedule();
+    }
+
+    event EmergencyAction();
+}

From d594174e4705cac0667a8500a3a6af2d9e969abd Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Wed, 26 Jun 2024 15:04:51 -0300
Subject: [PATCH 29/38] refactor: remove inline assembly to fetch the code hash

---
 src/DssEmergencySpell.sol | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/src/DssEmergencySpell.sol b/src/DssEmergencySpell.sol
index c513514..224c1b7 100644
--- a/src/DssEmergencySpell.sol
+++ b/src/DssEmergencySpell.sol
@@ -52,6 +52,8 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
     /// @dev The chainlog contract reference.
     ChainlogLike internal constant _log = ChainlogLike(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F);
 
+    // @dev The reference to the `pause` contract.
+    address public immutable pause = _log.getAddress("MCD_PAUSE");
     /// @dev The chainlog address.
     address public constant log = address(_log);
     /// @dev In regular spells, `eta` is used to enforce the GSM delay.
@@ -64,10 +66,8 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
     // @dev Office Hours is always `false` for emergency spells.
     bool public constant officeHours = false;
     // @dev `action` is expected to return a valid address.
-    //         We also implement the `DssAction` interface in this contract.
+    //      We also implement the `DssAction` interface in this contract.
     address public immutable action = address(this);
-    // @dev The reference to the `pause` contract.
-    address public immutable pause = ChainlogLike(log).getAddress("MCD_PAUSE");
     // @dev An emergency spell can be cast as soon as it is deployed.
     //      Notice that cast is always a no-op.
     uint256 internal immutable _nextCastTime = block.timestamp;
@@ -77,12 +77,11 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
      *      It specifically uses a separate contract for spell action because `tag` is immutable and the code hash of
      *      the contract being initialized is not accessible in the constructor.
      *      Since we do not have a separate contract for actions in Emergency Spells, `tag` has to be turned into a
-     *      getter function instaed of an immutable variable.
+     *      getter function instead of an immutable variable.
+     * @return The contract codehash.
      */
-    function tag() external view returns (bytes32 _tag) {
-        assembly {
-            _tag := extcodehash(address())
-        }
+    function tag() external view returns (bytes32) {
+        return address(this).codehash;
     }
 
     /**

From 7ad2715622ac73522f9c82587d3b45f277e444e1 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Mon, 29 Jul 2024 19:25:54 -0300
Subject: [PATCH 30/38] docs: fix typo in comment

---
 src/clip-breaker/SingleClipBreakerSpell.sol | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/clip-breaker/SingleClipBreakerSpell.sol b/src/clip-breaker/SingleClipBreakerSpell.sol
index 2ef7bf6..c83686b 100644
--- a/src/clip-breaker/SingleClipBreakerSpell.sol
+++ b/src/clip-breaker/SingleClipBreakerSpell.sol
@@ -30,7 +30,7 @@ interface IlkRegistryLike {
 }
 
 contract SingleClipBreakerSpell is DssEmergencySpell {
-    /// @dev During an emergency, set the breaker level to 3  to prevent botyh `kick()`, `redo()` and `take()`.
+    /// @dev During an emergency, set the breaker level to 3  to prevent both `kick()`, `redo()` and `take()`.
     uint256 public constant BREAKER_LEVEL = 3;
     /// @dev The delay is not applicable for level 3 breakers, so we set it to zero.
     uint256 public constant BREAKER_DELAY = 0;

From ce34c281e9891b9c507fd92d2ca50ef6b1780da9 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Mon, 29 Jul 2024 19:26:06 -0300
Subject: [PATCH 31/38] docs: add README

---
 README.md | 136 ++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 92 insertions(+), 44 deletions(-)

diff --git a/README.md b/README.md
index 9265b45..a3b3f3e 100644
--- a/README.md
+++ b/README.md
@@ -1,66 +1,114 @@
-## Foundry
+# Maker Protocol Emergency Spells
 
-**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
+Pre-deployed spells to allow MakerDAO Governance to react faster in case of emergencies.
 
-Foundry consists of:
+## Motivation
 
--   **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
--   **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
--   **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
--   **Chisel**: Fast, utilitarian, and verbose solidity REPL.
+In Maker's linguo, a spell is a bespoke smart contract to execute authorized actions in Maker Protocol on behalf on
+Maker Governance.
 
-## Documentation
+Since most contracts in Maker Protocol follow a [simple, battle-tested authorization scheme][auth], with an "all or
+nothing" approach, it means that every spell has _root_ access to every single of its components.
 
-https://book.getfoundry.sh/
+[auth]: https://github.com/makerdao/pe-checklists/blob/master/core/standards.md#permissions
 
-## Usage
+In order to mitigate the risks associated with that design decision, the spell process is quite "heavy", where
+multiple trusted parties are involved, and with comprehensive [checklists][spell-checklists] that must be strictly
+followed.
 
-### Build
+[spell-checklists]: https://github.com/makerdao/pe-checklists/tree/master/spell
 
-```shell
-$ forge build
-```
+With all the complexity and coordination effort, it is not a surprise that it takes a long time for a spell to be
+successfully crafted, reviewed and handed over to Maker Governance. As per the [current process][spell-schedule], with
+the involvement of at least 3 engineers from the different EAs in the Spell Team, not to mention the Governance
+Facilitators and other key stakeholders, it takes at least 8 working days to deliver a regular spell.
 
-### Test
+[spell-schedule]: https://github.com/makerdao/pe-checklists/blob/master/spell/spell-crafter-mainnet-workflow.md#spell-coordination-schedule
 
-```shell
-$ forge test
-```
+For emergency spells, historically the agreed SLA had been 24h. That was somehow possible when there was a single
+tight-knit team working on spells, however can be specially challenging with a more decentralized workforce, which is
+scattered around the world. Even if it was still possible to meet that SLA, in some situations 24h might be too much
+time.
 
-### Format
+This repository contains a couple of different spells performing emergency actions that can be pre-deployed to allow
+MakerDAO Governance to quickly respond to incidents, without the need for dedicated engineers to chime in and craft a
+bespoke spell in record time.
 
-```shell
-$ forge fmt
-```
+## Deployments
 
-### Gas Snapshots
+TBD.
 
-```shell
-$ forge snapshot
-```
+## Implemented Actions
 
-### Anvil
+| Description        | Single ilk         | Universal          |
+| :----------        | :--------:         | :-------:          |
+| Wipe `AutoLine`    | :white_check_mark: | :white_check_mark: |
+| Set `Clip` breaker | :white_check_mark: | :white_check_mark: |
+| Disable `DDM`      | :white_check_mark: | :x:                |
+| Stop `OSM`         | :white_check_mark: | :white_check_mark: |
 
-```shell
-$ anvil
-```
+### Wipe `AutoLine`
 
-### Deploy
+No further debt can be generated from an ilk which is wiped from `AutoLine`. It also prevents the debt ceiling
+(`line`) for the affected ilk from being changed without Governance interference.
 
-```shell
-$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
-```
+### Set `Clip` breaker
 
-### Cast
+Halts collateral auctions happening in the `Clip` contract belonging to the specified ilks. Sets the breaker level to 3
+to prevent both `kick()`, `redo()` and `take()`.
 
-```shell
-$ cast <subcommand>
-```
+### Disable `DDM`
 
-### Help
+Disables a Direct Deposit Module, preventing further debt from being generated from it.
 
-```shell
-$ forge --help
-$ anvil --help
-$ cast --help
-```
+### Stop `OSM`
+
+Stops the specified Oracle Security Module instances, preventing updates in their price feeds.
+
+## Design
+
+Emergency spells are meant to be as ABI-compatible with regular spells as possible, to allow Governance to reuse any
+existing tools, which will not increase the cognitive burden in an emergency situation.
+
+Previous bespoke emergency spells ([see example][example-emergency-spell]) would perform an open-heart surgery in the
+standard [`DssExec`][dss-exec] contract and include the emergency actions calls in the `schedule` function. This allows
+any contract using the `Mom` architecture ([see example][example-mom]) to bypass the GSM delay.
+
+The same restrictions to regulars spells still apply (i.e.: storage variables are not allowed).
+
+The emergency spells in this repository build on that idea with a few modifications:
+
+1. No [`DssExecLib`][dss-exec-lib] dependency: emergency actions are quite simple by nature, which makes the use of
+   `DssExecLib` superfluous.
+1. No expiration time: contrary to regular spells, which are meant to be cast only once, emergency spells can be reused
+   if needed, so the expiration time is set so far away in the future that in practice the spell does not expire.
+1. No separate [`DssAction`][dss-action]-like contract: regular spells delegate the execution of specific actions to a
+   `DssAction` contract that is deployed by the spell in its constructor. The exact reason for that design choice is
+   unknown to the authors, however we can speculate that the way the spell `tag` is derived<sup>[\[1\]](#fn-1)</sup>
+   requires a separate contract.
+1. Casting is a no-op: while bespoke emergency spells would often conflate emergency actions with non-emergency ones,
+   pre-deployed emergency spells perform only emergency actions, turning `cast()` into a no-op, which exists only for
+   interface-compatibility purposes.
+1. No `MCD_PAUSE` interaction: as its name might suggest, the main goal of `MCD_PAUSE` is to introduce a _pause_ (GSM
+   delay) between the approval of a spell and its execution. Emergency spells by definition bypass the GSM delay, so
+   there is no strong reason to `plan` them in `MCD_PAUSE` as regular spells.
+
+[example-emergency-spell]: https://github.com/makerdao/spells-mainnet/blob/8b0e1c354a0add49f595eea01ca3a822e782ab0d/archive/2022-06-15-DssSpell/DssSpell.sol
+[dss-exec]: https://github.com/makerdao/dss-exec-lib/blob/69b658f35d8618272cd139dfc18c5713caf6b96b/src/DssExec.sol
+[dss-exec-lib]: https://github.com/makerdao/dss-exec-lib/blob/69b658f35d8618272cd139dfc18c5713caf6b96b/src/DssExecLib.sol
+[dss-action]: https://github.com/makerdao/dss-exec-lib/blob/69b658f35d8618272cd139dfc18c5713caf6b96b/src/DssAction.sol
+[example-mom]: https://etherscan.io/address/0x9c257e5Aaf73d964aEBc2140CA38078988fB0C10
+
+<sub id="fn-1"><sup>\[1\]</sup> `tag` is meant to be immutable and [extract the `codehash` of the `action`
+contract][spell-tag]. Notice that it would not be possible to get the `codehash` of the same contract in its
+constructor.</sub>
+
+[spell-tag]: https://github.com/makerdao/dss-exec-lib/blob/69b658f35d8618272cd139dfc18c5713caf6b96b/src/DssExec.sol#L75
+
+Some types of emergency spells may come in 2 flavors:
+
+1. Single ilk: applies the desired spell action for a single pre-defined ilk.
+1. Universal: applies the desired spell action for all applicable ilks.
+
+Furthermore, this repo provides on-chain factories for single ilk emergency spells to make it easier to deploy for new
+ilks.

From 0372ffb4784c9ac4a026b8da28098b00d60d6abe Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 19 Sep 2024 10:26:14 -0300
Subject: [PATCH 32/38] refactor: remove unneeded status checks after revert

---
 src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol | 2 --
 src/clip-breaker/MultiClipBreakerSpell.t.integration.sol    | 3 ---
 src/osm-stop/MultiOsmStopSpell.t.integration.sol            | 2 --
 3 files changed, 7 deletions(-)

diff --git a/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol b/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol
index 9a15628..994020d 100644
--- a/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol
+++ b/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol
@@ -129,8 +129,6 @@ contract MultiAutoLineWipeSpellTest is DssTest {
 
         vm.expectRevert();
         spell.schedule();
-
-        _checkAutoLineWipedStatus({ilks: ilkReg.list(), expected: false});
     }
 
     function _checkAutoLineWipedStatus(bytes32[] memory ilks, bool expected) internal view {
diff --git a/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol b/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
index 39d1208..be99d1c 100644
--- a/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
+++ b/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
@@ -161,9 +161,6 @@ contract MultiClipBreakerSpellTest is DssTest {
 
         vm.expectRevert();
         spell.schedule();
-
-        _checkClipMaxStoppedStatus({ilks: ilkReg.list(), maxExpected: 2});
-        assertFalse(spell.done(), "false: spell done unexpectedly");
     }
 
     function _checkClipMaxStoppedStatus(bytes32[] memory ilks, uint256 maxExpected) internal view {
diff --git a/src/osm-stop/MultiOsmStopSpell.t.integration.sol b/src/osm-stop/MultiOsmStopSpell.t.integration.sol
index 07fad2c..ed06653 100644
--- a/src/osm-stop/MultiOsmStopSpell.t.integration.sol
+++ b/src/osm-stop/MultiOsmStopSpell.t.integration.sol
@@ -162,8 +162,6 @@ contract MultiOsmStopSpellTest is DssTest {
 
         vm.expectRevert();
         spell.schedule();
-
-        _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 0});
     }
 
     function _checkOsmStoppedStatus(bytes32[] memory ilks, uint256 expected) internal view {

From 04fe1d750304873b074a59beae79dc3b7a21d258 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 19 Sep 2024 10:28:58 -0300
Subject: [PATCH 33/38] refactor: make `nextCastTime` return
 `type(uint256).max`

---
 src/DssEmergencySpell.sol               | 16 ++++++++--------
 src/DssEmergencySpell.t.integration.sol |  4 ++--
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/src/DssEmergencySpell.sol b/src/DssEmergencySpell.sol
index 224c1b7..521cc50 100644
--- a/src/DssEmergencySpell.sol
+++ b/src/DssEmergencySpell.sol
@@ -63,14 +63,14 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
     bytes public constant sig = abi.encodeWithSelector(DssAction.execute.selector);
     /// @dev Emergency spells should not expire.
     uint256 public constant expiration = type(uint256).max;
+    // @dev An emergency spell does not need to be cast, as all actions happen during the schedule phase.
+    //      Notice that cast is usually not supposed to revert, so it is implemented as a no-op.
+    uint256 internal immutable _nextCastTime = type(uint256).max;
     // @dev Office Hours is always `false` for emergency spells.
     bool public constant officeHours = false;
     // @dev `action` is expected to return a valid address.
     //      We also implement the `DssAction` interface in this contract.
     address public immutable action = address(this);
-    // @dev An emergency spell can be cast as soon as it is deployed.
-    //      Notice that cast is always a no-op.
-    uint256 internal immutable _nextCastTime = block.timestamp;
 
     /**
      * @dev In regular spells, `tag` is an immutable variable with the code hash of the spell action.
@@ -101,7 +101,7 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
 
     /**
      * @notice Returns `_nextCastTime`.
-     * @dev this function exists only to keep interface compatibility with regular spells.
+     * @dev This function exists only to keep interface compatibility with regular spells.
      */
     function nextCastTime() external view returns (uint256 castTime) {
         return _nextCastTime;
@@ -109,25 +109,25 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
 
     /**
      * @notice No-op.
-     * @dev this function exists only to keep interface compatibility with regular spells.
+     * @dev This function exists only to keep interface compatibility with regular spells.
      */
     function cast() external {}
 
     /**
      * @notice No-op.
-     * @dev this function exists only to keep interface compatibility with regular spells.
+     * @dev This function exists only to keep interface compatibility with regular spells.
      */
     function execute() external {}
 
     /**
      * @notice No-op.
-     * @dev this function exists only to keep interface compatibility with regular spells.
+     * @dev This function exists only to keep interface compatibility with regular spells.
      */
     function actions() external {}
 
     /**
      * @notice Returns `nextCastTime`, regardless of the input parameter.
-     * @dev this function exists only to keep interface compatibility with regular spells.
+     * @dev This function exists only to keep interface compatibility with regular spells.
      */
     function nextCastTime(uint256) external view returns (uint256 castTime) {
         return _nextCastTime;
diff --git a/src/DssEmergencySpell.t.integration.sol b/src/DssEmergencySpell.t.integration.sol
index 0ab2bd1..952bdd8 100644
--- a/src/DssEmergencySpell.t.integration.sol
+++ b/src/DssEmergencySpell.t.integration.sol
@@ -53,8 +53,8 @@ contract DssEmergencySpellTest is DssTest {
         assertEq(spell.pause(), pause, "invalid pause");
         assertEq(spell.action(), address(spell), "invalid action");
         assertEq(spell.eta(), 0, "invalid eta");
-        assertEq(spell.nextCastTime(), block.timestamp, "invalid nextCastTime");
-        assertEq(spell.nextCastTime(1231298123), block.timestamp, "invalid nextCastTime(uint256)");
+        assertEq(spell.nextCastTime(), type(uint256).max, "invalid nextCastTime");
+        assertEq(spell.nextCastTime(1231298123), type(uint256).max, "invalid nextCastTime(uint256)");
         assertEq(spell.officeHours(), false, "invalid officeHours");
         assertEq(spell.sig(), abi.encodeWithSignature("execute()"), "invalid sig");
         {

From bc06eebcec04800f778b0bef339f5253222209d2 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 19 Sep 2024 12:08:41 -0300
Subject: [PATCH 34/38] docs: add relevant chainlog key references to README

---
 README.md | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index a3b3f3e..e29d2ca 100644
--- a/README.md
+++ b/README.md
@@ -49,21 +49,21 @@ TBD.
 
 ### Wipe `AutoLine`
 
-No further debt can be generated from an ilk which is wiped from `AutoLine`. It also prevents the debt ceiling
+No further debt can be generated from an ilk which is wiped from `MCD_IAM_AUTO_LINE`. It also prevents the debt ceiling
 (`line`) for the affected ilk from being changed without Governance interference.
 
 ### Set `Clip` breaker
 
-Halts collateral auctions happening in the `Clip` contract belonging to the specified ilks. Sets the breaker level to 3
+Halts collateral auctions happening in the `MCD_CLIP_{ILK}` contract belonging to the specified ilks. Sets the breaker level to 3
 to prevent both `kick()`, `redo()` and `take()`.
 
 ### Disable `DDM`
 
-Disables a Direct Deposit Module, preventing further debt from being generated from it.
+Disables a Direct Deposit Module (`DIRECT_{ID}_PLAN`), preventing further debt from being generated from it.
 
 ### Stop `OSM`
 
-Stops the specified Oracle Security Module instances, preventing updates in their price feeds.
+Stops the specified Oracle Security Module (`PIP_{GEM}`) instances, preventing updates in their price feeds.
 
 ## Design
 

From c810d136c20711f30707a7e1a86e181d22710554 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Tue, 15 Oct 2024 19:41:37 -0300
Subject: [PATCH 35/38] refactor: address audit findings

---
 src/auto-line-wipe/MultiAutoLineWipeSpell.sol | 21 ++++++--
 .../MultiAutoLineWipeSpell.t.integration.sol  | 22 +++++++-
 .../SingleAutoLineWipeSpell.sol               | 25 +++++++--
 .../SingleAutoLineWipeSpell.t.integration.sol | 33 +++++++++++-
 src/clip-breaker/MultiClipBreakerSpell.sol    | 54 +++++++++++++------
 .../MultiClipBreakerSpell.t.integration.sol   |  4 ++
 src/clip-breaker/SingleClipBreakerSpell.sol   | 28 +++++++++-
 .../SingleClipBreakerSpell.t.integration.sol  |  9 ++++
 src/ddm-disable/SingleDdmDisableSpell.sol     | 12 ++++-
 .../SingleDdmDisableSpell.t.integration.sol   |  9 ++++
 src/osm-stop/MultiOsmStopSpell.sol            | 44 +++++++++++----
 .../MultiOsmStopSpell.t.integration.sol       | 35 +++++++++++-
 src/osm-stop/SingleOsmStopSpell.sol           | 29 +++++++++-
 .../SingleOsmStopSpell.t.integration.sol      | 20 ++++++-
 14 files changed, 300 insertions(+), 45 deletions(-)

diff --git a/src/auto-line-wipe/MultiAutoLineWipeSpell.sol b/src/auto-line-wipe/MultiAutoLineWipeSpell.sol
index 9ab5b23..873e66f 100644
--- a/src/auto-line-wipe/MultiAutoLineWipeSpell.sol
+++ b/src/auto-line-wipe/MultiAutoLineWipeSpell.sol
@@ -36,11 +36,19 @@ interface AutoLineLike {
         returns (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc);
 }
 
+interface VatLike {
+    function ilks(bytes32 ilk)
+        external
+        view
+        returns (uint256 Art, uint256 rate, uint256 spot, uint256 line, uint256 dust);
+}
+
 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"));
+    VatLike public immutable vat = VatLike(_log.getAddress("MCD_VAT"));
 
     event Wipe(bytes32 indexed ilk);
 
@@ -71,7 +79,9 @@ contract MultiAutoLineWipeSpell is DssEmergencySpell {
      */
     function _doWipe(bytes32[] memory ilks) internal {
         for (uint256 i = 0; i < ilks.length; i++) {
-            if (lineMom.ilks(ilks[i]) == 0) continue;
+            if (lineMom.ilks(ilks[i]) == 0) {
+                continue;
+            }
 
             LineMomLike(lineMom).wipe(ilks[i]);
             emit Wipe(ilks[i]);
@@ -86,11 +96,14 @@ contract MultiAutoLineWipeSpell is DssEmergencySpell {
         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;
+            if (lineMom.ilks(ilks[i]) == 0) {
+                continue;
+            }
 
+            (,,, uint256 line,) = vat.ilks(ilks[i]);
             (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)) {
+            // If any of the entries in auto-line or vat line has non zero values, then the spell can be cast again.
+            if (!(line == 0 && maxLine == 0 && gap == 0 && ttl == 0 && last == 0 && lastInc == 0)) {
                 return false;
             }
         }
diff --git a/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol b/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol
index 994020d..3f93529 100644
--- a/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol
+++ b/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol
@@ -35,6 +35,11 @@ interface AutoLineLike {
         external
         view
         returns (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc);
+    function remIlk(bytes32 ilk) external;
+}
+
+interface VatLike {
+    function file(bytes32 ilk, bytes32 what, uint256 data) external;
 }
 
 contract MultiAutoLineWipeSpellTest is DssTest {
@@ -43,9 +48,10 @@ contract MultiAutoLineWipeSpellTest is DssTest {
     address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
     DssInstance dss;
     address chief;
+    VatLike vat;
+    IlkRegistryLike ilkReg;
     LineMomLike lineMom;
     AutoLineLike autoLine;
-    IlkRegistryLike ilkReg;
     MultiAutoLineWipeSpell spell;
 
     mapping(bytes32 => bool) ilksToIgnore;
@@ -56,9 +62,10 @@ contract MultiAutoLineWipeSpellTest is DssTest {
         dss = MCD.loadFromChainlog(CHAINLOG);
         MCD.giveAdminAccess(dss);
         chief = dss.chainlog.getAddress("MCD_ADM");
+        vat = VatLike(dss.chainlog.getAddress("MCD_VAT"));
+        ilkReg = IlkRegistryLike(dss.chainlog.getAddress("ILK_REGISTRY"));
         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));
@@ -122,6 +129,17 @@ contract MultiAutoLineWipeSpellTest is DssTest {
         _checkAutoLineWipedStatus({ilks: ilkReg.list(), expected: true});
     }
 
+    function testDoneWhenAutoLineIsNotActiveButLineIsNonZero() public {
+        spell.schedule();
+        assertTrue(spell.done(), "before: spell not done");
+
+        address pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY");
+        vm.prank(pauseProxy);
+        vat.file("ETH-A", "line", 10 ** 45);
+
+        assertFalse(spell.done(), "after: spell still done");
+    }
+
     function testRevertMultiOracleStopWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 
diff --git a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
index 079fdfe..ec18e63 100644
--- a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
+++ b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
@@ -18,8 +18,9 @@ pragma solidity ^0.8.16;
 import {DssEmergencySpell} from "../DssEmergencySpell.sol";
 
 interface LineMomLike {
-    function wipe(bytes32 ilk) external returns (uint256);
     function autoLine() external view returns (address);
+    function ilks(bytes32 ilk) external view returns (uint256);
+    function wipe(bytes32 ilk) external returns (uint256);
 }
 
 interface AutoLineLike {
@@ -29,8 +30,17 @@ interface AutoLineLike {
         returns (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc);
 }
 
+interface VatLike {
+    function ilks(bytes32 ilk)
+        external
+        view
+        returns (uint256 Art, uint256 rate, uint256 spot, uint256 line, uint256 dust);
+    function wards(address who) external view returns (uint256);
+}
+
 contract SingleAutoLineWipeSpell is DssEmergencySpell {
     LineMomLike public immutable lineMom = LineMomLike(_log.getAddress("LINE_MOM"));
+    VatLike public immutable vat = VatLike(_log.getAddress("MCD_VAT"));
     bytes32 public immutable ilk;
 
     event Wipe();
@@ -50,13 +60,22 @@ contract SingleAutoLineWipeSpell is DssEmergencySpell {
 
     /**
      * @notice Returns whether the spell is done or not.
-     * @dev Checks if the ilk has been wiped from auto-line.
+     * @dev Checks if the ilk has been wiped from auto-line and vat line is zero.
+     *      The spell would revert if any of the following condtions holds:
+     *          1. The ilk has not been added to AutoLine
+     *          2. LineMom is not ward on Vat
+     *      In such cases, it returns `true`, meaning no further action can be taken at the moment.
      */
     function done() external view returns (bool) {
+        if (vat.wards(address(lineMom)) == 0 || lineMom.ilks(ilk) == 0) {
+            return true;
+        }
+
+        (,,, uint256 line,) = vat.ilks(ilk);
         (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc) =
             AutoLineLike(lineMom.autoLine()).ilks(ilk);
 
-        return maxLine == 0 && gap == 0 && ttl == 0 && last == 0 && lastInc == 0;
+        return line == 0 && maxLine == 0 && gap == 0 && ttl == 0 && last == 0 && lastInc == 0;
     }
 }
 
diff --git a/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol b/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol
index 5ff6de5..a8e5e81 100644
--- a/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol
+++ b/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol
@@ -27,14 +27,23 @@ interface AutoLineLike {
         returns (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc);
 }
 
+interface LineMomLike {
+    function delIlk(bytes32 ilk) external;
+}
+
+interface VatLike {
+    function file(bytes32 ilk, bytes32 what, uint256 data) external;
+}
+
 contract SingleAutoLineWipeSpellTest is DssTest {
     using stdStorage for StdStorage;
 
     address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
     DssInstance dss;
+    VatLike vat;
     address chief;
     bytes32 ilk = "ETH-A";
-    address lineMom;
+    LineMomLike lineMom;
     AutoLineLike autoLine;
     SingleAutoLineWipeFactory factory;
     DssEmergencySpellLike spell;
@@ -45,7 +54,8 @@ contract SingleAutoLineWipeSpellTest is DssTest {
         dss = MCD.loadFromChainlog(CHAINLOG);
         MCD.giveAdminAccess(dss);
         chief = dss.chainlog.getAddress("MCD_ADM");
-        lineMom = dss.chainlog.getAddress("LINE_MOM");
+        vat = VatLike(dss.chainlog.getAddress("MCD_VAT"));
+        lineMom = LineMomLike(dss.chainlog.getAddress("LINE_MOM"));
         autoLine = AutoLineLike(dss.chainlog.getAddress("MCD_IAM_AUTO_LINE"));
         factory = new SingleAutoLineWipeFactory();
         spell = DssEmergencySpellLike(factory.deploy(ilk));
@@ -71,6 +81,25 @@ contract SingleAutoLineWipeSpellTest is DssTest {
         assertTrue(spell.done(), "after: spell not done");
     }
 
+    function testDoneWhenIlkIsNotAddedToLineMom() public {
+        address pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY");
+        vm.prank(pauseProxy);
+        lineMom.delIlk(ilk);
+
+        assertTrue(spell.done(), "spell not done");
+    }
+
+    function testDoneWhenAutoLineIsNotActiveButLineIsNonZero() public {
+        spell.schedule();
+        assertTrue(spell.done(), "before: spell not done");
+
+        address pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY");
+        vm.prank(pauseProxy);
+        vat.file(ilk, "line", 10 ** 45);
+
+        assertFalse(spell.done(), "after: spell still done");
+    }
+
     function testRevertAutoLineWipeWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 
diff --git a/src/clip-breaker/MultiClipBreakerSpell.sol b/src/clip-breaker/MultiClipBreakerSpell.sol
index 68eecb3..ec6970d 100644
--- a/src/clip-breaker/MultiClipBreakerSpell.sol
+++ b/src/clip-breaker/MultiClipBreakerSpell.sol
@@ -44,6 +44,7 @@ contract MultiClipBreakerSpell is DssEmergencySpell {
     ClipperMomLike public immutable clipperMom = ClipperMomLike(_log.getAddress("CLIPPER_MOM"));
 
     event SetBreaker(bytes32 indexed ilk, address indexed clip);
+    event Fail(bytes32 indexed ilk, address indexed clip, bytes reason);
 
     /**
      * @notice Sets breakers, when possible, for all Clip instances that can be found in the ilk registry.
@@ -72,24 +73,31 @@ contract MultiClipBreakerSpell is DssEmergencySpell {
      */
     function _doSetBreaker(bytes32[] memory ilks) internal {
         for (uint256 i = 0; i < ilks.length; i++) {
-            bytes32 ilk = ilks[i];
-            address clip = ilkReg.xlip(ilk);
+            address clip = ilkReg.xlip(ilks[i]);
 
-            if (clip == address(0)) continue;
+            if (clip == address(0)) {
+                continue;
+            }
 
             try ClipLike(clip).wards(address(clipperMom)) returns (uint256 ward) {
-                // Ignore Clip instances that have not relied on ClipperMom.
-                if (ward == 0) continue;
-            } catch Error(string memory reason) {
-                // If the reason is empty, it means the contract is most likely not a Clip instance.
-                require(bytes(reason).length == 0, reason);
+                if (ward == 0) {
+                    emit Fail(ilks[i], clip, "clipperMom-not-ward");
+                    continue;
+                }
+            } catch (bytes memory reason) {
+                emit Fail(ilks[i], clip, reason);
+                continue;
             }
 
             try clipperMom.setBreaker(clip, BREAKER_LEVEL, BREAKER_DELAY) {
-                emit SetBreaker(ilk, clip);
+                emit SetBreaker(ilks[i], clip);
             } catch Error(string memory reason) {
-                // If the reason is empty, it means the contract is most likely not a Clip instance.
-                require(bytes(reason).length == 0, reason);
+                // If the spell does not have the hat, it cannot be executed, so we must halt it.
+                require(!_strEq(reason, "ClipperMom/not-authorized"), reason);
+                // Whatever other reason we just ignore and move on.
+                emit Fail(ilks[i], clip, bytes(reason));
+            } catch (bytes memory reason) {
+                emit Fail(ilks[i], clip, reason);
             }
         }
     }
@@ -101,21 +109,26 @@ contract MultiClipBreakerSpell is DssEmergencySpell {
     function done() external view returns (bool) {
         bytes32[] memory ilks = ilkReg.list();
         for (uint256 i = 0; i < ilks.length; i++) {
-            bytes32 ilk = ilks[i];
-            address clip = ilkReg.xlip(ilk);
+            address clip = ilkReg.xlip(ilks[i]);
 
-            if (clip == address(0)) continue;
+            if (clip == address(0)) {
+                continue;
+            }
 
             try ClipLike(clip).wards(address(clipperMom)) returns (uint256 ward) {
                 // Ignore Clip instances that have not relied on ClipperMom.
-                if (ward == 0) continue;
+                if (ward == 0) {
+                    continue;
+                }
             } catch {
-                // If The call failed, it means the contract is most likely not a Clip instance, so it can be ignored.
+                // If the call failed, it means the contract is most likely not a Clip instance, so it can be ignored.
                 continue;
             }
 
             try ClipLike(clip).stopped() returns (uint256 stopped) {
-                if (stopped != BREAKER_LEVEL) return false;
+                if (stopped != BREAKER_LEVEL) {
+                    return false;
+                }
             } catch {
                 // If the call failed, it means the contract is most likely not a Clip instance, so it can be ignored.
                 continue;
@@ -123,4 +136,11 @@ contract MultiClipBreakerSpell is DssEmergencySpell {
         }
         return true;
     }
+
+    /**
+     * @notice Checks if strings a and b are the same.
+     */
+    function _strEq(string memory a, string memory b) internal pure returns (bool) {
+        return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
+    }
 }
diff --git a/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol b/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
index be99d1c..a3b295a 100644
--- a/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
+++ b/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
@@ -147,6 +147,8 @@ contract MultiClipBreakerSpellTest is DssTest {
 
         _checkClipMaxStoppedStatus({ilks: ilkReg.list(), maxExpected: 2});
 
+        vm.expectEmit(true, true, true, true);
+        emit Fail("ETH-A", clipEthA, "clipperMom-not-ward");
         spell.schedule();
 
         _checkClipStoppedStatus({ilks: ilkReg.list(), expected: 3});
@@ -186,4 +188,6 @@ contract MultiClipBreakerSpellTest is DssTest {
             assertEq(ClipLike(clip).stopped(), expected, string(abi.encodePacked("invalid stopped status: ", ilks[i])));
         }
     }
+
+    event Fail(bytes32 indexed ilk, address indexed clip, bytes reason);
 }
diff --git a/src/clip-breaker/SingleClipBreakerSpell.sol b/src/clip-breaker/SingleClipBreakerSpell.sol
index c83686b..555cf53 100644
--- a/src/clip-breaker/SingleClipBreakerSpell.sol
+++ b/src/clip-breaker/SingleClipBreakerSpell.sol
@@ -23,6 +23,7 @@ interface ClipperMomLike {
 
 interface ClipLike {
     function stopped() external view returns (uint256);
+    function wards(address who) external view returns (uint256);
 }
 
 interface IlkRegistryLike {
@@ -58,9 +59,34 @@ contract SingleClipBreakerSpell is DssEmergencySpell {
     /**
      * @notice Returns whether the spell is done or not.
      * @dev Checks if the Clip instance has stopped = 3.
+     *      The spell would revert if any of the following condtions holds:
+     *          1. Clip is set to address(0)
+     *          2. ClipperMom is not a ward on Clip
+     *          3. Clip does not implement the `stopped` function
+     *      In such cases, it returns `true`, meaning no further action can be taken at the moment.
      */
     function done() external view returns (bool) {
-        return ClipLike(ilkReg.xlip(ilk)).stopped() == BREAKER_LEVEL;
+        address clip = ilkReg.xlip(ilk);
+        if (clip == address(0)) {
+            return true;
+        }
+
+        try ClipLike(clip).wards(address(clipperMom)) returns (uint256 ward) {
+            // Ignore Clip instances that have not relied on ClipperMom.
+            if (ward == 0) {
+                return true;
+            }
+        } catch {
+            // If the call failed, it means the contract is most likely not a Clip instance.
+            return true;
+        }
+
+        try ClipLike(clip).stopped() returns (uint256 stopped) {
+            return stopped == BREAKER_LEVEL;
+        } catch {
+            // If the call failed, it means the contract is most likely not a Clip instance.
+            return true;
+        }
     }
 }
 
diff --git a/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol b/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol
index 356d831..fba396d 100644
--- a/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol
+++ b/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol
@@ -22,6 +22,7 @@ import {SingleClipBreakerFactory} from "./SingleClipBreakerSpell.sol";
 
 interface IlkRegistryLike {
     function xlip(bytes32 ilk) external view returns (address);
+    function file(bytes32 ilk, bytes32 what, address data) external;
 }
 
 interface ClipperMomLike {
@@ -74,6 +75,14 @@ contract SingleClipBreakerSpellTest is DssTest {
         assertTrue(spell.done(), "after: spell not done");
     }
 
+    function testDoneWhenClipIsNotSetInIlkReg() public {
+        address pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY");
+        vm.prank(pauseProxy);
+        ilkReg.file(ilk, "xlip", address(0));
+
+        assertTrue(spell.done(), "spell not done");
+    }
+
     function testRevertClipBreakerWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 
diff --git a/src/ddm-disable/SingleDdmDisableSpell.sol b/src/ddm-disable/SingleDdmDisableSpell.sol
index f42f14f..9942545 100644
--- a/src/ddm-disable/SingleDdmDisableSpell.sol
+++ b/src/ddm-disable/SingleDdmDisableSpell.sol
@@ -23,10 +23,11 @@ interface DdmMomLike {
 
 interface DdmPlanLike {
     function active() external view returns (bool);
+    function wards(address who) external view returns (uint256);
 }
 
 interface DdmHubLike {
-    function plan(bytes32 ilk) external view returns (address plan);
+    function plan(bytes32 ilk) external view returns (address);
 }
 
 contract SingleDdmDisableSpell is DssEmergencySpell {
@@ -53,9 +54,16 @@ contract SingleDdmDisableSpell is DssEmergencySpell {
     /**
      * @notice Returns whether the spell is done or not.
      * @dev Checks if the plan instance has stopped = 3.
+     *      The spell would revert if any of the following condtions holds:
+     *          1. DDMMom is not a ward of DDMHub, the spell would revert.
+     *      In such cases, it returns `true`, meaning no further action can be taken at the moment.
      */
     function done() external view returns (bool) {
-        return DdmPlanLike(ddmHub.plan(ilk)).active() == false;
+        DdmPlanLike plan = DdmPlanLike(ddmHub.plan(ilk));
+        if (plan.wards(address(ddmMom)) == 0) {
+            return true;
+        }
+        return plan.active() == false;
     }
 }
 
diff --git a/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol b/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol
index e3cbfd3..4a3fd22 100644
--- a/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol
+++ b/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol
@@ -30,6 +30,7 @@ interface DdmHubLike {
 
 interface DdmPlanLike {
     function active() external view returns (bool);
+    function deny(address who) external;
 }
 
 contract SingleDdmDisableSpellTest is DssTest {
@@ -74,6 +75,14 @@ contract SingleDdmDisableSpellTest is DssTest {
         assertTrue(spell.done(), "after: spell not done");
     }
 
+    function testDoneWhenDdmMomIsNotWardOnDdmPlan() public {
+        address pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY");
+        vm.prank(pauseProxy);
+        plan.deny(address(ddmMom));
+
+        assertTrue(spell.done(), "spell not done");
+    }
+
     function testRevertDdmDisableWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 
diff --git a/src/osm-stop/MultiOsmStopSpell.sol b/src/osm-stop/MultiOsmStopSpell.sol
index d5be8ab..7f2ed52 100644
--- a/src/osm-stop/MultiOsmStopSpell.sol
+++ b/src/osm-stop/MultiOsmStopSpell.sol
@@ -40,6 +40,7 @@ contract MultiOsmStopSpell is DssEmergencySpell {
     OsmMomLike public immutable osmMom = OsmMomLike(_log.getAddress("OSM_MOM"));
 
     event Stop(bytes32 indexed ilk, address indexed osm);
+    event Fail(bytes32 indexed ilk, address indexed osm, bytes reason);
 
     /**
      * @notice Stops, when possible, all OSMs that can be found through the ilk registry.
@@ -70,22 +71,30 @@ contract MultiOsmStopSpell is DssEmergencySpell {
         for (uint256 i = 0; i < ilks.length; i++) {
             address osm = osmMom.osms(ilks[i]);
 
-            if (osm == address(0)) continue;
+            if (osm == address(0)) {
+                continue;
+            }
 
             try OsmLike(osm).wards(address(osmMom)) returns (uint256 ward) {
-                // Ignore Osm instances that have not relied on OsmMom.
-                if (ward == 0) continue;
-            } catch Error(string memory reason) {
-                // If the reason is empty, it means the contract is most likely not an OSM instance.
-                require(bytes(reason).length == 0, reason);
+                if (ward == 0) {
+                    emit Fail(ilks[i], osm, "osmMom-not-ward");
+                    continue;
+                }
+            } catch (bytes memory reason) {
+                emit Fail(ilks[i], osm, reason);
+                continue;
             }
 
             // There might be some duplicate calls to the same OSM, however they are idempotent.
             try OsmMomLike(osmMom).stop(ilks[i]) {
                 emit Stop(ilks[i], osm);
             } catch Error(string memory reason) {
-                // If the reason is empty, it means the contract is most likely not an OSM instance.
-                require(bytes(reason).length == 0, reason);
+                // If the spell does not have the hat, it cannot be executed, so we must halt it.
+                require(!_strEq(reason, "osm-mom/not-authorized"), reason);
+                // Whatever other reason we just ignore and move on.
+                emit Fail(ilks[i], osm, bytes(reason));
+            } catch (bytes memory reason) {
+                emit Fail(ilks[i], osm, reason);
             }
         }
     }
@@ -99,11 +108,15 @@ contract MultiOsmStopSpell is DssEmergencySpell {
         for (uint256 i = 0; i < ilks.length; i++) {
             address osm = osmMom.osms(ilks[i]);
 
-            if (osm == address(0)) continue;
+            if (osm == address(0)) {
+                continue;
+            }
 
             try OsmLike(osm).wards(address(osmMom)) returns (uint256 ward) {
                 // Ignore Osm instances that have not relied on OsmMom.
-                if (ward == 0) continue;
+                if (ward == 0) {
+                    continue;
+                }
             } catch {
                 // If the call failed, it means the contract is most likely not an OSM instance, so it can be ignored.
                 continue;
@@ -111,7 +124,9 @@ contract MultiOsmStopSpell is DssEmergencySpell {
 
             try OsmLike(osm).stopped() returns (uint256 stopped) {
                 // If any of the OSMs that match the conditions is not stopped, the spell was not executed yet.
-                if (stopped == 0) return false;
+                if (stopped == 0) {
+                    return false;
+                }
             } catch {
                 // If the call failed, it means the contract is most likely not an OSM instance, so it can be ignored.
                 continue;
@@ -119,4 +134,11 @@ contract MultiOsmStopSpell is DssEmergencySpell {
         }
         return true;
     }
+
+    /**
+     * @notice Checks if strings a and b are the same.
+     */
+    function _strEq(string memory a, string memory b) internal pure returns (bool) {
+        return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
+    }
 }
diff --git a/src/osm-stop/MultiOsmStopSpell.t.integration.sol b/src/osm-stop/MultiOsmStopSpell.t.integration.sol
index ed06653..f6b184c 100644
--- a/src/osm-stop/MultiOsmStopSpell.t.integration.sol
+++ b/src/osm-stop/MultiOsmStopSpell.t.integration.sol
@@ -24,6 +24,7 @@ interface OsmMomLike {
 }
 
 interface OsmLike {
+    function src() external view returns (address);
     function stopped() external view returns (uint256);
     function osms(bytes32 ilk) external view returns (address);
     function wards(address who) external view returns (uint256);
@@ -74,7 +75,7 @@ contract MultiOsmStopSpellTest is DssTest {
         bytes32[] memory ilks = ilkReg.list();
         for (uint256 i = 0; i < ilks.length; i++) {
             string memory ilkStr = string(abi.encodePacked(ilks[i]));
-            address osm = ilkReg.pip(ilks[i]);
+            address osm = osmMom.osms(ilks[i]);
             if (osm == address(0)) {
                 ilksToIgnore[ilks[i]] = true;
                 emit log_named_string("Ignoring ilk | No OSM", ilkStr);
@@ -149,12 +150,42 @@ contract MultiOsmStopSpellTest is DssTest {
 
         _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 0});
 
+        vm.expectEmit(true, true, true, true);
+        emit Fail("ETH-A", pipEth, "osmMom-not-ward");
         spell.schedule();
 
         _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 1});
         assertEq(OsmLike(pipEth).stopped(), 0, "ETH-A pip was not ignored");
     }
 
+    function testNonOsmShouldNotRevert() public {
+        // Not an OSM
+        address medianEth = OsmLike(ilkReg.pip("ETH-A")).src();
+        // Overwrite OSMMom so it uses the wrong contract.
+        stdstore.target(address(osmMom)).sig("osms(bytes32)").with_key("ETH-A").checked_write(
+            bytes32(uint256(uint160(medianEth)))
+        );
+        stdstore.target(address(osmMom)).sig("osms(bytes32)").with_key("ETH-B").checked_write(
+            bytes32(uint256(uint160(medianEth)))
+        );
+        stdstore.target(address(osmMom)).sig("osms(bytes32)").with_key("ETH-C").checked_write(
+            bytes32(uint256(uint160(medianEth)))
+        );
+        // De-auth OsmMom to force the error:
+        stdstore.target(medianEth).sig("wards(address)").with_key(address(osmMom)).checked_write(bytes32(uint256(1)));
+        // Updates the list of ilks to be ignored.
+        _initIlksToIgnore();
+
+        _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 0});
+
+        vm.expectEmit(true, true, true, true);
+        emit Fail("ETH-A", medianEth, "");
+        spell.schedule();
+
+        _checkOsmStoppedStatus({ilks: ilkReg.list(), expected: 1});
+        assertEq(OsmLike(ilkReg.pip("ETH-A")).stopped(), 0, "ETH-A pip was not ignored");
+    }
+
     function testRevertMultiOracleStopWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 
@@ -174,4 +205,6 @@ contract MultiOsmStopSpellTest is DssTest {
             assertEq(OsmLike(pip).stopped(), expected, string(abi.encodePacked("invalid stopped status: ", ilks[i])));
         }
     }
+
+    event Fail(bytes32 indexed ilk, address indexed osm, bytes reason);
 }
diff --git a/src/osm-stop/SingleOsmStopSpell.sol b/src/osm-stop/SingleOsmStopSpell.sol
index d8f7fb5..dffccb0 100644
--- a/src/osm-stop/SingleOsmStopSpell.sol
+++ b/src/osm-stop/SingleOsmStopSpell.sol
@@ -24,6 +24,7 @@ interface OsmMomLike {
 
 interface OsmLike {
     function stopped() external view returns (uint256);
+    function wards(address who) external view returns (uint256);
 }
 
 contract SingleOsmStopSpell is DssEmergencySpell {
@@ -48,9 +49,35 @@ contract SingleOsmStopSpell is DssEmergencySpell {
     /**
      * @notice Returns whether the spell is done or not.
      * @dev Checks if the OSM instance is stopped.
+     *      The spell would revert if any of the following condtions holds:
+     *          1. OSM has not been added to OSMMom the spell would revert.
+     *          2. OSMom is not a ward of OSM
+     *          3. OSM does not implement the `stopped()` function
+     *      In this case, it returns `true`, meaning no further action can be taken at the moment.
      */
     function done() external view returns (bool) {
-        return OsmLike(osmMom.osms(ilk)).stopped() == 1;
+        address osm = osmMom.osms(ilk);
+
+        if (osm == address(0)) {
+            return true;
+        }
+
+        try OsmLike(osm).wards(address(osmMom)) returns (uint256 ward) {
+            // Ignore Osm instances that have not relied on OsmMom.
+            if (ward == 0) {
+                return true;
+            }
+        } catch {
+            // If the call failed, it means the contract is most likely not an OSM instance.
+            return true;
+        }
+
+        try OsmLike(osm).stopped() returns (uint256 stopped) {
+            return stopped == 1;
+        } catch {
+            // If the call failed, it means the contract is most likely not an OSM instance.
+            return true;
+        }
     }
 }
 
diff --git a/src/osm-stop/SingleOsmStopSpell.t.integration.sol b/src/osm-stop/SingleOsmStopSpell.t.integration.sol
index 54f3b01..5468db1 100644
--- a/src/osm-stop/SingleOsmStopSpell.t.integration.sol
+++ b/src/osm-stop/SingleOsmStopSpell.t.integration.sol
@@ -21,11 +21,13 @@ import {DssEmergencySpellLike} from "../DssEmergencySpell.sol";
 import {SingleOsmStopFactory} from "./SingleOsmStopSpell.sol";
 
 interface OsmMomLike {
-    function osms(bytes32) external view returns (address);
+    function osms(bytes32 ilk) external view returns (address);
+    function setOsm(bytes32 ilk, address osm) external;
 }
 
 interface OsmLike {
     function stopped() external view returns (uint256);
+    function deny(address who) external;
 }
 
 contract SingleOsmStopSpellTest is DssTest {
@@ -68,6 +70,22 @@ contract SingleOsmStopSpellTest is DssTest {
         assertTrue(spell.done(), "after: spell not done");
     }
 
+    function testDoneWhenOsmIsNotAddedToOsmMom() public {
+        address pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY");
+        vm.startPrank(pauseProxy);
+        osmMom.setOsm(ilk, address(0));
+
+        assertTrue(spell.done(), "spell not done");
+    }
+
+    function testDoneWhenOsmMomIsNotWardOnOsm() public {
+        address pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY");
+        vm.prank(pauseProxy);
+        osm.deny(address(osmMom));
+
+        assertTrue(spell.done(), "spell not done");
+    }
+
     function testRevertOsmStopWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 

From a8e2beb297799e52d84274e18ba217e80eceb859 Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Tue, 15 Oct 2024 19:41:55 -0300
Subject: [PATCH 36/38] docs: update README

---
 README.md | 26 ++++++++++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index e29d2ca..7f92cb5 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ TBD.
 
 ## Implemented Actions
 
-| Description        | Single ilk         | Universal          |
+| Description        | Single ilk         | Multi ilk          |
 | :----------        | :--------:         | :-------:          |
 | Wipe `AutoLine`    | :white_check_mark: | :white_check_mark: |
 | Set `Clip` breaker | :white_check_mark: | :white_check_mark: |
@@ -67,6 +67,8 @@ Stops the specified Oracle Security Module (`PIP_{GEM}`) instances, preventing u
 
 ## Design
 
+### Overview
+
 Emergency spells are meant to be as ABI-compatible with regular spells as possible, to allow Governance to reuse any
 existing tools, which will not increase the cognitive burden in an emergency situation.
 
@@ -108,7 +110,27 @@ constructor.</sub>
 Some types of emergency spells may come in 2 flavors:
 
 1. Single ilk: applies the desired spell action for a single pre-defined ilk.
-1. Universal: applies the desired spell action for all applicable ilks.
+1. Multi ilk: applies the desired spell action for all applicable ilks.
 
 Furthermore, this repo provides on-chain factories for single ilk emergency spells to make it easier to deploy for new
 ilks.
+
+### About the `done()` function
+
+Conforming spells have a [`done`][spell-done] public storage variable which is `false` when the spell is deployed and
+set to `true` when the spell is cast. This ensures a spell cannot be cast twice.
+
+An emergency spell is not meant to be cast, but it can be scheduled multiple times. So instead of having `done` as a
+storage variable, it becomes a getter function that will return:
+- `false`: if the emergency spell can be scheduled in the current state, given it is lifted to the hat.
+- `true`: if the desired effects of the spell can be verified or if there is anything that would prevent the spell from
+  being scheduled (i.e.: bad system config)
+
+Generally speaking, `done` should almost always return `false` for any emergency spell. If it returns `true` it means it
+has just been scheduled or there is most likely something wrong with the modules touched by it. The exception is the
+case where the system naturally achieves the same final state as the spell being scheduled, in which it would be also
+returned `true`.
+
+In other words, if `done() == true`, it means that the actions performed by the spell is not applicable.
+
+[spell-done]: https://github.com/makerdao/dss-exec-lib/blob/69b658f35d8618272cd139dfc18c5713caf6b96b/src/DssExec.sol#L43

From 75c5a0662cb58c0ce5b9e8e0c8f9704ba7a9d76d Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Wed, 16 Oct 2024 10:46:51 -0300
Subject: [PATCH 37/38] refactor: additional improvements

---
 src/auto-line-wipe/MultiAutoLineWipeSpell.sol       |  7 +++++--
 src/auto-line-wipe/SingleAutoLineWipeSpell.sol      | 12 +++++++-----
 .../SingleClipBreakerSpell.t.integration.sol        | 13 +++++++++++--
 src/ddm-disable/SingleDdmDisableSpell.sol           |  4 +++-
 .../SingleDdmDisableSpell.t.integration.sol         |  9 +++++++++
 5 files changed, 35 insertions(+), 10 deletions(-)

diff --git a/src/auto-line-wipe/MultiAutoLineWipeSpell.sol b/src/auto-line-wipe/MultiAutoLineWipeSpell.sol
index 873e66f..9c09cc5 100644
--- a/src/auto-line-wipe/MultiAutoLineWipeSpell.sol
+++ b/src/auto-line-wipe/MultiAutoLineWipeSpell.sol
@@ -34,6 +34,7 @@ interface AutoLineLike {
         external
         view
         returns (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc);
+    function wards(address who) external view returns (uint256);
 }
 
 interface VatLike {
@@ -41,6 +42,7 @@ interface VatLike {
         external
         view
         returns (uint256 Art, uint256 rate, uint256 spot, uint256 line, uint256 dust);
+    function wards(address who) external view returns (uint256);
 }
 
 contract MultiAutoLineWipeSpell is DssEmergencySpell {
@@ -48,6 +50,7 @@ contract MultiAutoLineWipeSpell is DssEmergencySpell {
 
     IlkRegistryLike public immutable ilkReg = IlkRegistryLike(_log.getAddress("ILK_REGISTRY"));
     LineMomLike public immutable lineMom = LineMomLike(_log.getAddress("LINE_MOM"));
+    AutoLineLike public immutable autoLine = AutoLineLike(LineMomLike(_log.getAddress("LINE_MOM")).autoLine());
     VatLike public immutable vat = VatLike(_log.getAddress("MCD_VAT"));
 
     event Wipe(bytes32 indexed ilk);
@@ -93,10 +96,10 @@ contract MultiAutoLineWipeSpell is DssEmergencySpell {
      * @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) {
+            if (vat.wards(address(lineMom)) == 0 || autoLine.wards(address(lineMom)) == 0 || lineMom.ilks(ilks[i]) == 0)
+            {
                 continue;
             }
 
diff --git a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
index ec18e63..6fcf54e 100644
--- a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
+++ b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
@@ -28,6 +28,7 @@ interface AutoLineLike {
         external
         view
         returns (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc);
+    function wards(address who) external view returns (uint256);
 }
 
 interface VatLike {
@@ -40,6 +41,7 @@ interface VatLike {
 
 contract SingleAutoLineWipeSpell is DssEmergencySpell {
     LineMomLike public immutable lineMom = LineMomLike(_log.getAddress("LINE_MOM"));
+    AutoLineLike public immutable autoLine = AutoLineLike(LineMomLike(_log.getAddress("LINE_MOM")).autoLine());
     VatLike public immutable vat = VatLike(_log.getAddress("MCD_VAT"));
     bytes32 public immutable ilk;
 
@@ -62,18 +64,18 @@ contract SingleAutoLineWipeSpell is DssEmergencySpell {
      * @notice Returns whether the spell is done or not.
      * @dev Checks if the ilk has been wiped from auto-line and vat line is zero.
      *      The spell would revert if any of the following condtions holds:
-     *          1. The ilk has not been added to AutoLine
-     *          2. LineMom is not ward on Vat
+     *          1. LineMom is not ward on Vat
+     *          2. LineMom is not ward on AutoLine
+     *          3. The ilk has not been added to AutoLine
      *      In such cases, it returns `true`, meaning no further action can be taken at the moment.
      */
     function done() external view returns (bool) {
-        if (vat.wards(address(lineMom)) == 0 || lineMom.ilks(ilk) == 0) {
+        if (vat.wards(address(lineMom)) == 0 || autoLine.wards(address(lineMom)) == 0 || lineMom.ilks(ilk) == 0) {
             return true;
         }
 
         (,,, uint256 line,) = vat.ilks(ilk);
-        (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc) =
-            AutoLineLike(lineMom.autoLine()).ilks(ilk);
+        (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc) = autoLine.ilks(ilk);
 
         return line == 0 && maxLine == 0 && gap == 0 && ttl == 0 && last == 0 && lastInc == 0;
     }
diff --git a/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol b/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol
index fba396d..67d77b1 100644
--- a/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol
+++ b/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol
@@ -31,6 +31,7 @@ interface ClipperMomLike {
 
 interface ClipLike {
     function stopped() external view returns (uint256);
+    function deny(address who) external;
 }
 
 contract SingleClipBreakerSpellTest is DssTest {
@@ -41,7 +42,7 @@ contract SingleClipBreakerSpellTest is DssTest {
     address chief;
     IlkRegistryLike ilkReg;
     bytes32 ilk = "ETH-A";
-    ClipperMomLike clipMom;
+    ClipperMomLike clipperMom;
     ClipLike clip;
     SingleClipBreakerFactory factory;
     DssEmergencySpellLike spell;
@@ -53,7 +54,7 @@ contract SingleClipBreakerSpellTest is DssTest {
         MCD.giveAdminAccess(dss);
         chief = dss.chainlog.getAddress("MCD_ADM");
         ilkReg = IlkRegistryLike(dss.chainlog.getAddress("ILK_REGISTRY"));
-        clipMom = ClipperMomLike(dss.chainlog.getAddress("CLIPPER_MOM"));
+        clipperMom = ClipperMomLike(dss.chainlog.getAddress("CLIPPER_MOM"));
         clip = ClipLike(ilkReg.xlip(ilk));
         factory = new SingleClipBreakerFactory();
         spell = DssEmergencySpellLike(factory.deploy(ilk));
@@ -83,6 +84,14 @@ contract SingleClipBreakerSpellTest is DssTest {
         assertTrue(spell.done(), "spell not done");
     }
 
+    function testDoneWhenClipperMomIsNotWardInClip() public {
+        address pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY");
+        vm.prank(pauseProxy);
+        clip.deny(address(clipperMom));
+
+        assertTrue(spell.done(), "spell not done");
+    }
+
     function testRevertClipBreakerWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 
diff --git a/src/ddm-disable/SingleDdmDisableSpell.sol b/src/ddm-disable/SingleDdmDisableSpell.sol
index 9942545..1dddc1a 100644
--- a/src/ddm-disable/SingleDdmDisableSpell.sol
+++ b/src/ddm-disable/SingleDdmDisableSpell.sol
@@ -60,9 +60,11 @@ contract SingleDdmDisableSpell is DssEmergencySpell {
      */
     function done() external view returns (bool) {
         DdmPlanLike plan = DdmPlanLike(ddmHub.plan(ilk));
-        if (plan.wards(address(ddmMom)) == 0) {
+
+        if (address(plan) == address(0) || plan.wards(address(ddmMom)) == 0) {
             return true;
         }
+
         return plan.active() == false;
     }
 }
diff --git a/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol b/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol
index 4a3fd22..2ac6db2 100644
--- a/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol
+++ b/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol
@@ -26,6 +26,7 @@ interface DdmMomLike {
 
 interface DdmHubLike {
     function plan(bytes32 ilk) external view returns (address);
+    function file(bytes32 ilk, bytes32 what, address data) external;
 }
 
 interface DdmPlanLike {
@@ -83,6 +84,14 @@ contract SingleDdmDisableSpellTest is DssTest {
         assertTrue(spell.done(), "spell not done");
     }
 
+    function testDoneWhenDdmPlanIsAddressZero() public {
+        address pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY");
+        vm.prank(pauseProxy);
+        ddmHub.file(ilk, "plan", address(0));
+
+        assertTrue(spell.done(), "spell not done");
+    }
+
     function testRevertDdmDisableWhenItDoesNotHaveTheHat() public {
         stdstore.target(chief).sig("hat()").checked_write(address(0));
 

From d9fc4ea3d69b54a7d22b6c36e0c0baa33160df3b Mon Sep 17 00:00:00 2001
From: amusingaxl <112016538+amusingaxl@users.noreply.github.com>
Date: Thu, 17 Oct 2024 22:14:15 +0300
Subject: [PATCH 38/38] fix: apply suggestions from code review

Co-authored-by: oddaf <106770775+oddaf@users.noreply.github.com>
---
 README.md                                                    | 2 +-
 src/DssEmergencySpell.sol                                    | 2 +-
 src/auto-line-wipe/MultiAutoLineWipeSpell.sol                | 4 ++--
 src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol  | 3 +--
 src/auto-line-wipe/SingleAutoLineWipeSpell.sol               | 2 +-
 src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol | 2 +-
 src/clip-breaker/MultiClipBreakerSpell.sol                   | 2 +-
 src/clip-breaker/MultiClipBreakerSpell.t.integration.sol     | 2 +-
 src/clip-breaker/SingleClipBreakerSpell.sol                  | 4 ++--
 src/clip-breaker/SingleClipBreakerSpell.t.integration.sol    | 2 +-
 src/ddm-disable/SingleDdmDisableSpell.sol                    | 4 ++--
 src/ddm-disable/SingleDdmDisableSpell.t.integration.sol      | 2 +-
 src/osm-stop/MultiOsmStopSpell.t.integration.sol             | 3 +--
 src/osm-stop/SingleOsmStopSpell.sol                          | 4 ++--
 src/osm-stop/SingleOsmStopSpell.t.integration.sol            | 2 +-
 15 files changed, 19 insertions(+), 21 deletions(-)

diff --git a/README.md b/README.md
index 7f92cb5..dbd9a28 100644
--- a/README.md
+++ b/README.md
@@ -131,6 +131,6 @@ has just been scheduled or there is most likely something wrong with the modules
 case where the system naturally achieves the same final state as the spell being scheduled, in which it would be also
 returned `true`.
 
-In other words, if `done() == true`, it means that the actions performed by the spell is not applicable.
+In other words, if `done() == true`, it means that the actions performed by the spell are not applicable.
 
 [spell-done]: https://github.com/makerdao/dss-exec-lib/blob/69b658f35d8618272cd139dfc18c5713caf6b96b/src/DssExec.sol#L43
diff --git a/src/DssEmergencySpell.sol b/src/DssEmergencySpell.sol
index 521cc50..5a316d1 100644
--- a/src/DssEmergencySpell.sol
+++ b/src/DssEmergencySpell.sol
@@ -88,7 +88,7 @@ abstract contract DssEmergencySpell is DssEmergencySpellLike {
      * @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 spells take affect immediately, so there is no need to call `pause.plot()`.
+     *      scheduled. Emergency spells take effect immediately, so there is no need to call `pause.plot()`.
      */
     function schedule() external {
         _emergencyActions();
diff --git a/src/auto-line-wipe/MultiAutoLineWipeSpell.sol b/src/auto-line-wipe/MultiAutoLineWipeSpell.sol
index 9c09cc5..48e2226 100644
--- a/src/auto-line-wipe/MultiAutoLineWipeSpell.sol
+++ b/src/auto-line-wipe/MultiAutoLineWipeSpell.sol
@@ -77,7 +77,7 @@ contract MultiAutoLineWipeSpell is DssEmergencySpell {
     }
 
     /**
-     * @notice Stops, when possible, all OSMs that can be found from the `ilks` list.
+     * @notice Wipes, when possible, all ilks from the auto-line provided in the `ilks` list.
      * @param ilks The list of ilks to consider.
      */
     function _doWipe(bytes32[] memory ilks) internal {
@@ -86,7 +86,7 @@ contract MultiAutoLineWipeSpell is DssEmergencySpell {
                 continue;
             }
 
-            LineMomLike(lineMom).wipe(ilks[i]);
+            lineMom.wipe(ilks[i]);
             emit Wipe(ilks[i]);
         }
     }
diff --git a/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol b/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol
index 3f93529..251fcf4 100644
--- a/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol
+++ b/src/auto-line-wipe/MultiAutoLineWipeSpell.t.integration.sol
@@ -16,7 +16,7 @@
 pragma solidity ^0.8.16;
 
 import {stdStorage, StdStorage} from "forge-std/Test.sol";
-import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol";
+import {DssTest, DssInstance, MCD} from "dss-test/DssTest.sol";
 import {MultiAutoLineWipeSpell} from "./MultiAutoLineWipeSpell.sol";
 
 interface LineMomLike {
@@ -35,7 +35,6 @@ interface AutoLineLike {
         external
         view
         returns (uint256 maxLine, uint256 gap, uint48 ttl, uint48 last, uint48 lastInc);
-    function remIlk(bytes32 ilk) external;
 }
 
 interface VatLike {
diff --git a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
index 6fcf54e..5f5f39d 100644
--- a/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
+++ b/src/auto-line-wipe/SingleAutoLineWipeSpell.sol
@@ -63,7 +63,7 @@ contract SingleAutoLineWipeSpell is DssEmergencySpell {
     /**
      * @notice Returns whether the spell is done or not.
      * @dev Checks if the ilk has been wiped from auto-line and vat line is zero.
-     *      The spell would revert if any of the following condtions holds:
+     *      The spell would revert if any of the following conditions holds:
      *          1. LineMom is not ward on Vat
      *          2. LineMom is not ward on AutoLine
      *          3. The ilk has not been added to AutoLine
diff --git a/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol b/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol
index a8e5e81..0c3dc0b 100644
--- a/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol
+++ b/src/auto-line-wipe/SingleAutoLineWipeSpell.t.integration.sol
@@ -16,7 +16,7 @@
 pragma solidity ^0.8.16;
 
 import {stdStorage, StdStorage} from "forge-std/Test.sol";
-import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol";
+import {DssTest, DssInstance, MCD} from "dss-test/DssTest.sol";
 import {DssEmergencySpellLike} from "../DssEmergencySpell.sol";
 import {SingleAutoLineWipeFactory} from "./SingleAutoLineWipeSpell.sol";
 
diff --git a/src/clip-breaker/MultiClipBreakerSpell.sol b/src/clip-breaker/MultiClipBreakerSpell.sol
index ec6970d..71af2d7 100644
--- a/src/clip-breaker/MultiClipBreakerSpell.sol
+++ b/src/clip-breaker/MultiClipBreakerSpell.sol
@@ -35,7 +35,7 @@ interface ClipLike {
 
 contract MultiClipBreakerSpell is DssEmergencySpell {
     string public constant override description = "Emergency Spell | Multi Clip Breaker";
-    /// @dev During an emergency, set the breaker level to 3  to prevent botyh `kick()`, `redo()` and `take()`.
+    /// @dev During an emergency, set the breaker level to 3  to prevent both `kick()`, `redo()` and `take()`.
     uint256 public constant BREAKER_LEVEL = 3;
     /// @dev The delay is not applicable for level 3 breakers, so we set it to zero.
     uint256 public constant BREAKER_DELAY = 0;
diff --git a/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol b/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
index a3b295a..961be60 100644
--- a/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
+++ b/src/clip-breaker/MultiClipBreakerSpell.t.integration.sol
@@ -16,7 +16,7 @@
 pragma solidity ^0.8.16;
 
 import {stdStorage, StdStorage} from "forge-std/Test.sol";
-import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol";
+import {DssTest, DssInstance, MCD} from "dss-test/DssTest.sol";
 import {MultiClipBreakerSpell} from "./MultiClipBreakerSpell.sol";
 
 interface IlkRegistryLike {
diff --git a/src/clip-breaker/SingleClipBreakerSpell.sol b/src/clip-breaker/SingleClipBreakerSpell.sol
index 555cf53..4055e70 100644
--- a/src/clip-breaker/SingleClipBreakerSpell.sol
+++ b/src/clip-breaker/SingleClipBreakerSpell.sol
@@ -31,7 +31,7 @@ interface IlkRegistryLike {
 }
 
 contract SingleClipBreakerSpell is DssEmergencySpell {
-    /// @dev During an emergency, set the breaker level to 3  to prevent both `kick()`, `redo()` and `take()`.
+    /// @dev During an emergency, set the breaker level to 3 to prevent `kick()`, `redo()` and `take()`.
     uint256 public constant BREAKER_LEVEL = 3;
     /// @dev The delay is not applicable for level 3 breakers, so we set it to zero.
     uint256 public constant BREAKER_DELAY = 0;
@@ -59,7 +59,7 @@ contract SingleClipBreakerSpell is DssEmergencySpell {
     /**
      * @notice Returns whether the spell is done or not.
      * @dev Checks if the Clip instance has stopped = 3.
-     *      The spell would revert if any of the following condtions holds:
+     *      The spell would revert if any of the following conditions holds:
      *          1. Clip is set to address(0)
      *          2. ClipperMom is not a ward on Clip
      *          3. Clip does not implement the `stopped` function
diff --git a/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol b/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol
index 67d77b1..43a1f3d 100644
--- a/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol
+++ b/src/clip-breaker/SingleClipBreakerSpell.t.integration.sol
@@ -16,7 +16,7 @@
 pragma solidity ^0.8.16;
 
 import {stdStorage, StdStorage} from "forge-std/Test.sol";
-import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol";
+import {DssTest, DssInstance, MCD} from "dss-test/DssTest.sol";
 import {DssEmergencySpellLike} from "../DssEmergencySpell.sol";
 import {SingleClipBreakerFactory} from "./SingleClipBreakerSpell.sol";
 
diff --git a/src/ddm-disable/SingleDdmDisableSpell.sol b/src/ddm-disable/SingleDdmDisableSpell.sol
index 1dddc1a..0305c5c 100644
--- a/src/ddm-disable/SingleDdmDisableSpell.sol
+++ b/src/ddm-disable/SingleDdmDisableSpell.sol
@@ -53,8 +53,8 @@ contract SingleDdmDisableSpell is DssEmergencySpell {
 
     /**
      * @notice Returns whether the spell is done or not.
-     * @dev Checks if the plan instance has stopped = 3.
-     *      The spell would revert if any of the following condtions holds:
+     * @dev Checks if the plan.active() = false.
+     *      The spell would revert if any of the following conditions holds:
      *          1. DDMMom is not a ward of DDMHub, the spell would revert.
      *      In such cases, it returns `true`, meaning no further action can be taken at the moment.
      */
diff --git a/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol b/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol
index 2ac6db2..8bf321f 100644
--- a/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol
+++ b/src/ddm-disable/SingleDdmDisableSpell.t.integration.sol
@@ -16,7 +16,7 @@
 pragma solidity ^0.8.16;
 
 import {stdStorage, StdStorage} from "forge-std/Test.sol";
-import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol";
+import {DssTest, DssInstance, MCD} from "dss-test/DssTest.sol";
 import {DssEmergencySpellLike} from "../DssEmergencySpell.sol";
 import {SingleDdmDisableFactory} from "./SingleDdmDisableSpell.sol";
 
diff --git a/src/osm-stop/MultiOsmStopSpell.t.integration.sol b/src/osm-stop/MultiOsmStopSpell.t.integration.sol
index f6b184c..ebdb381 100644
--- a/src/osm-stop/MultiOsmStopSpell.t.integration.sol
+++ b/src/osm-stop/MultiOsmStopSpell.t.integration.sol
@@ -16,7 +16,7 @@
 pragma solidity ^0.8.16;
 
 import {stdStorage, StdStorage} from "forge-std/Test.sol";
-import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol";
+import {DssTest, DssInstance, MCD} from "dss-test/DssTest.sol";
 import {MultiOsmStopSpell} from "./MultiOsmStopSpell.sol";
 
 interface OsmMomLike {
@@ -26,7 +26,6 @@ interface OsmMomLike {
 interface OsmLike {
     function src() external view returns (address);
     function stopped() external view returns (uint256);
-    function osms(bytes32 ilk) external view returns (address);
     function wards(address who) external view returns (uint256);
 }
 
diff --git a/src/osm-stop/SingleOsmStopSpell.sol b/src/osm-stop/SingleOsmStopSpell.sol
index dffccb0..86945c3 100644
--- a/src/osm-stop/SingleOsmStopSpell.sol
+++ b/src/osm-stop/SingleOsmStopSpell.sol
@@ -49,9 +49,9 @@ contract SingleOsmStopSpell is DssEmergencySpell {
     /**
      * @notice Returns whether the spell is done or not.
      * @dev Checks if the OSM instance is stopped.
-     *      The spell would revert if any of the following condtions holds:
+     *      The spell would revert if any of the following conditions holds:
      *          1. OSM has not been added to OSMMom the spell would revert.
-     *          2. OSMom is not a ward of OSM
+     *          2. OSMMom is not a ward of OSM
      *          3. OSM does not implement the `stopped()` function
      *      In this case, it returns `true`, meaning no further action can be taken at the moment.
      */
diff --git a/src/osm-stop/SingleOsmStopSpell.t.integration.sol b/src/osm-stop/SingleOsmStopSpell.t.integration.sol
index 5468db1..92c048d 100644
--- a/src/osm-stop/SingleOsmStopSpell.t.integration.sol
+++ b/src/osm-stop/SingleOsmStopSpell.t.integration.sol
@@ -16,7 +16,7 @@
 pragma solidity ^0.8.16;
 
 import {stdStorage, StdStorage} from "forge-std/Test.sol";
-import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol";
+import {DssTest, DssInstance, MCD} from "dss-test/DssTest.sol";
 import {DssEmergencySpellLike} from "../DssEmergencySpell.sol";
 import {SingleOsmStopFactory} from "./SingleOsmStopSpell.sol";