From f3ac621b19b60ce0bf3b943f862728081839db17 Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Mon, 30 Sep 2024 23:23:56 -0300 Subject: [PATCH 01/18] feat: Lite PSM halt emergency Spell --- src/lite-psm-halt/SingleLitePsmHaltSpell.sol | 89 ++++++++++++++ .../SingleLitePsmHaltSpell.t.sol | 111 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 src/lite-psm-halt/SingleLitePsmHaltSpell.sol create mode 100644 src/lite-psm-halt/SingleLitePsmHaltSpell.t.sol diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol new file mode 100644 index 0000000..eddc0b7 --- /dev/null +++ b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: © 2024 Dai Foundation +// 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 . +pragma solidity ^0.8.16; + +import {DssEmergencySpell} from "../DssEmergencySpell.sol"; + +enum Flow { + SELL, // Halt only selling gems + BUY, // Halt only buying gems + BOTH // Halt Both +} + +interface LitePsmMomLike { + function halt(address psm, Flow what) external; +} + +interface LitePsmLike { + function tin() external view returns (uint256); + function tout() external view returns (uint256); + function HALTED() external view returns (uint256); +} + +contract SingleLitePsmHaltSpell is DssEmergencySpell { + LitePsmMomLike public immutable litePsmMom = LitePsmMomLike(_log.getAddress("LITE_PSM_MOM")); + Flow public immutable flow; + address public immutable psm = _log.getAddress("MCD_LITE_PSM_USDC_A"); + + event Halt(Flow what); + + constructor(Flow _flow) { + flow = _flow; + } + + function flowToString(Flow _flow) internal pure returns (string memory) { + if (_flow == Flow.SELL) return "SELL"; + if (_flow == Flow.BUY) return "BUY"; + if (_flow == Flow.BOTH) return "BOTH"; + return ""; + } + + function description() external view returns (string memory) { + return string(abi.encodePacked("Emergency Spell | MCD_LITE_PSM_USDC_A halt: ", flowToString(flow))); + } + + function _emergencyActions() internal override { + litePsmMom.halt(psm, flow); + emit Halt(flow); + } + + /** + * @notice Returns whether the spell is done or not. + * @dev Checks if the swaps have been halted on the psm. + */ + function done() external view returns (bool) { + uint256 halted = LitePsmLike(psm).HALTED(); + + if (flow == Flow.SELL || flow == Flow.BOTH) { + if (LitePsmLike(psm).tin() != halted) return false; + } + + if (flow == Flow.BUY || flow == Flow.BOTH) { + if (LitePsmLike(psm).tout() != halted) return false; + } + + return true; + } +} + +contract SingleLitePsmHaltSpellFactory { + event Deploy(Flow indexed flow, address spell); + + function deploy(Flow flow) external returns (address spell) { + spell = address(new SingleLitePsmHaltSpell(flow)); + emit Deploy(flow, spell); + } +} diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.t.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.t.sol new file mode 100644 index 0000000..e9d95b3 --- /dev/null +++ b/src/lite-psm-halt/SingleLitePsmHaltSpell.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +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 {SingleLitePsmHaltSpellFactory, LitePsmLike, Flow} from "./SingleLitePsmHaltSpell.sol"; + +contract SingleLitePsmHaltSpellTest is DssTest { + using stdStorage for StdStorage; + + address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; + DssInstance dss; + address chief; + address litePsmMom; + LitePsmLike psm; + SingleLitePsmHaltSpellFactory factory; + + + function setUp() public { + vm.createSelectFork("mainnet"); + + dss = MCD.loadFromChainlog(CHAINLOG); + MCD.giveAdminAccess(dss); + chief = dss.chainlog.getAddress("MCD_ADM"); + litePsmMom = dss.chainlog.getAddress("LITE_PSM_MOM"); + psm = LitePsmLike(dss.chainlog.getAddress("MCD_LITE_PSM_USDC_A")); + factory = new SingleLitePsmHaltSpellFactory(); + } + + function testPsmHaltOnScheduleBuy() public { + testPsmHaltOnSchedule(Flow.BUY); + } + + function testPsmHaltOnScheduleSell() public { + testPsmHaltOnSchedule(Flow.SELL); + } + + function testPsmHaltOnScheduleBoth() public { + testPsmHaltOnSchedule(Flow.BOTH); + } + + function testPsmHaltOnSchedule(Flow flow) internal { + DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(flow)); + stdstore.target(chief).sig("hat()").checked_write(address(spell)); + vm.makePersistent(chief); + + uint256 preTin = psm.tin(); + uint256 preTout = psm.tout(); + uint256 halted = psm.HALTED(); + + if (flow == Flow.SELL || flow == Flow.BOTH) { + assertNotEq(preTin, halted, "before: PSM SELL already halted"); + } + if (flow == Flow.BUY || flow == Flow.BOTH) { + assertNotEq(preTout, halted, "before: PSM BUY already halted"); + } + assertFalse(spell.done(), "before: spell already done"); + + vm.expectEmit(true, true, true, false, address(spell)); + emit Halt(flow); + + spell.schedule(); + + uint256 postTin = psm.tin(); + uint256 postTout = psm.tout(); + + if (flow == Flow.SELL || flow == Flow.BOTH) { + assertEq(postTin, halted, "after: PSM SELL not halted (tin)"); + } + if (flow == Flow.BUY || flow == Flow.BOTH) { + assertEq(postTout, halted, "after: PSM BUY not halted (tout)"); + } + + assertTrue(spell.done(), "after: spell not done"); + } + + function testRevertPsmHaltWhenItDoesNotHaveTheHat() public { + Flow flow = Flow.BOTH; + DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(flow)); + + uint256 preTin = psm.tin(); + uint256 preTout = psm.tout(); + uint256 halted = psm.HALTED(); + + if (flow == Flow.SELL || flow == Flow.BOTH) { + assertNotEq(preTin, halted, "before: PSM SELL already halted"); + } + if (flow == Flow.BUY || flow == Flow.BOTH) { + assertNotEq(preTout, halted, "before: PSM BUY already halted"); + } + assertFalse(spell.done(), "before: spell already done"); + + vm.expectRevert(); + spell.schedule(); + + uint256 postTin = psm.tin(); + uint256 postTout = psm.tout(); + + if (flow == Flow.SELL || flow == Flow.BOTH) { + assertEq(postTin, preTin, "after: PSM SELL halted unexpectedly (tin)"); + } + if (flow == Flow.BUY || flow == Flow.BOTH) { + assertEq(postTout, preTout, "after: PSM BUY halted unexpectedly (tout)"); + } + + assertFalse(spell.done(), "after: spell done unexpectedly"); + } + + event Halt(Flow what); +} From 16f0e4664211d299c23ed1d2f4f90a4e3969c671 Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Tue, 1 Oct 2024 00:33:40 -0300 Subject: [PATCH 02/18] chore: fix test file name --- .../SingleLitePsmHaltSpell.t.integration.sol | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol new file mode 100644 index 0000000..e9d95b3 --- /dev/null +++ b/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +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 {SingleLitePsmHaltSpellFactory, LitePsmLike, Flow} from "./SingleLitePsmHaltSpell.sol"; + +contract SingleLitePsmHaltSpellTest is DssTest { + using stdStorage for StdStorage; + + address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; + DssInstance dss; + address chief; + address litePsmMom; + LitePsmLike psm; + SingleLitePsmHaltSpellFactory factory; + + + function setUp() public { + vm.createSelectFork("mainnet"); + + dss = MCD.loadFromChainlog(CHAINLOG); + MCD.giveAdminAccess(dss); + chief = dss.chainlog.getAddress("MCD_ADM"); + litePsmMom = dss.chainlog.getAddress("LITE_PSM_MOM"); + psm = LitePsmLike(dss.chainlog.getAddress("MCD_LITE_PSM_USDC_A")); + factory = new SingleLitePsmHaltSpellFactory(); + } + + function testPsmHaltOnScheduleBuy() public { + testPsmHaltOnSchedule(Flow.BUY); + } + + function testPsmHaltOnScheduleSell() public { + testPsmHaltOnSchedule(Flow.SELL); + } + + function testPsmHaltOnScheduleBoth() public { + testPsmHaltOnSchedule(Flow.BOTH); + } + + function testPsmHaltOnSchedule(Flow flow) internal { + DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(flow)); + stdstore.target(chief).sig("hat()").checked_write(address(spell)); + vm.makePersistent(chief); + + uint256 preTin = psm.tin(); + uint256 preTout = psm.tout(); + uint256 halted = psm.HALTED(); + + if (flow == Flow.SELL || flow == Flow.BOTH) { + assertNotEq(preTin, halted, "before: PSM SELL already halted"); + } + if (flow == Flow.BUY || flow == Flow.BOTH) { + assertNotEq(preTout, halted, "before: PSM BUY already halted"); + } + assertFalse(spell.done(), "before: spell already done"); + + vm.expectEmit(true, true, true, false, address(spell)); + emit Halt(flow); + + spell.schedule(); + + uint256 postTin = psm.tin(); + uint256 postTout = psm.tout(); + + if (flow == Flow.SELL || flow == Flow.BOTH) { + assertEq(postTin, halted, "after: PSM SELL not halted (tin)"); + } + if (flow == Flow.BUY || flow == Flow.BOTH) { + assertEq(postTout, halted, "after: PSM BUY not halted (tout)"); + } + + assertTrue(spell.done(), "after: spell not done"); + } + + function testRevertPsmHaltWhenItDoesNotHaveTheHat() public { + Flow flow = Flow.BOTH; + DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(flow)); + + uint256 preTin = psm.tin(); + uint256 preTout = psm.tout(); + uint256 halted = psm.HALTED(); + + if (flow == Flow.SELL || flow == Flow.BOTH) { + assertNotEq(preTin, halted, "before: PSM SELL already halted"); + } + if (flow == Flow.BUY || flow == Flow.BOTH) { + assertNotEq(preTout, halted, "before: PSM BUY already halted"); + } + assertFalse(spell.done(), "before: spell already done"); + + vm.expectRevert(); + spell.schedule(); + + uint256 postTin = psm.tin(); + uint256 postTout = psm.tout(); + + if (flow == Flow.SELL || flow == Flow.BOTH) { + assertEq(postTin, preTin, "after: PSM SELL halted unexpectedly (tin)"); + } + if (flow == Flow.BUY || flow == Flow.BOTH) { + assertEq(postTout, preTout, "after: PSM BUY halted unexpectedly (tout)"); + } + + assertFalse(spell.done(), "after: spell done unexpectedly"); + } + + event Halt(Flow what); +} From cd7768083839997e8b330c72f69cd1a0c1e607a7 Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Tue, 1 Oct 2024 00:34:09 -0300 Subject: [PATCH 03/18] chore: fix test file name --- .../SingleLitePsmHaltSpell.t.sol | 111 ------------------ 1 file changed, 111 deletions(-) delete mode 100644 src/lite-psm-halt/SingleLitePsmHaltSpell.t.sol diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.t.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.t.sol deleted file mode 100644 index e9d95b3..0000000 --- a/src/lite-psm-halt/SingleLitePsmHaltSpell.t.sol +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -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 {SingleLitePsmHaltSpellFactory, LitePsmLike, Flow} from "./SingleLitePsmHaltSpell.sol"; - -contract SingleLitePsmHaltSpellTest is DssTest { - using stdStorage for StdStorage; - - address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; - DssInstance dss; - address chief; - address litePsmMom; - LitePsmLike psm; - SingleLitePsmHaltSpellFactory factory; - - - function setUp() public { - vm.createSelectFork("mainnet"); - - dss = MCD.loadFromChainlog(CHAINLOG); - MCD.giveAdminAccess(dss); - chief = dss.chainlog.getAddress("MCD_ADM"); - litePsmMom = dss.chainlog.getAddress("LITE_PSM_MOM"); - psm = LitePsmLike(dss.chainlog.getAddress("MCD_LITE_PSM_USDC_A")); - factory = new SingleLitePsmHaltSpellFactory(); - } - - function testPsmHaltOnScheduleBuy() public { - testPsmHaltOnSchedule(Flow.BUY); - } - - function testPsmHaltOnScheduleSell() public { - testPsmHaltOnSchedule(Flow.SELL); - } - - function testPsmHaltOnScheduleBoth() public { - testPsmHaltOnSchedule(Flow.BOTH); - } - - function testPsmHaltOnSchedule(Flow flow) internal { - DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(flow)); - stdstore.target(chief).sig("hat()").checked_write(address(spell)); - vm.makePersistent(chief); - - uint256 preTin = psm.tin(); - uint256 preTout = psm.tout(); - uint256 halted = psm.HALTED(); - - if (flow == Flow.SELL || flow == Flow.BOTH) { - assertNotEq(preTin, halted, "before: PSM SELL already halted"); - } - if (flow == Flow.BUY || flow == Flow.BOTH) { - assertNotEq(preTout, halted, "before: PSM BUY already halted"); - } - assertFalse(spell.done(), "before: spell already done"); - - vm.expectEmit(true, true, true, false, address(spell)); - emit Halt(flow); - - spell.schedule(); - - uint256 postTin = psm.tin(); - uint256 postTout = psm.tout(); - - if (flow == Flow.SELL || flow == Flow.BOTH) { - assertEq(postTin, halted, "after: PSM SELL not halted (tin)"); - } - if (flow == Flow.BUY || flow == Flow.BOTH) { - assertEq(postTout, halted, "after: PSM BUY not halted (tout)"); - } - - assertTrue(spell.done(), "after: spell not done"); - } - - function testRevertPsmHaltWhenItDoesNotHaveTheHat() public { - Flow flow = Flow.BOTH; - DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(flow)); - - uint256 preTin = psm.tin(); - uint256 preTout = psm.tout(); - uint256 halted = psm.HALTED(); - - if (flow == Flow.SELL || flow == Flow.BOTH) { - assertNotEq(preTin, halted, "before: PSM SELL already halted"); - } - if (flow == Flow.BUY || flow == Flow.BOTH) { - assertNotEq(preTout, halted, "before: PSM BUY already halted"); - } - assertFalse(spell.done(), "before: spell already done"); - - vm.expectRevert(); - spell.schedule(); - - uint256 postTin = psm.tin(); - uint256 postTout = psm.tout(); - - if (flow == Flow.SELL || flow == Flow.BOTH) { - assertEq(postTin, preTin, "after: PSM SELL halted unexpectedly (tin)"); - } - if (flow == Flow.BUY || flow == Flow.BOTH) { - assertEq(postTout, preTout, "after: PSM BUY halted unexpectedly (tout)"); - } - - assertFalse(spell.done(), "after: spell done unexpectedly"); - } - - event Halt(Flow what); -} From 549b45739681e52322897be062544c39fd244fae Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Tue, 1 Oct 2024 01:29:47 -0300 Subject: [PATCH 04/18] feat: Splitter disable emergency Spell --- src/splitter-stop/SplitterStopSpell.sol | 51 +++++++++++++++ .../SplitterStopSpell.t.integration.sol | 62 +++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 src/splitter-stop/SplitterStopSpell.sol create mode 100644 src/splitter-stop/SplitterStopSpell.t.integration.sol diff --git a/src/splitter-stop/SplitterStopSpell.sol b/src/splitter-stop/SplitterStopSpell.sol new file mode 100644 index 0000000..ea1478f --- /dev/null +++ b/src/splitter-stop/SplitterStopSpell.sol @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: © 2024 Dai Foundation +// 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 . +pragma solidity ^0.8.16; + +import {DssEmergencySpell} from "../DssEmergencySpell.sol"; + +interface SplitterMomLike { + function stop() external; +} + +interface SplitterLike { + function hop() external view returns (uint256); +} + +contract SplitterStopSpell is DssEmergencySpell { + string public constant override description = "Emergency Spell | Disable Splitter"; + + SplitterMomLike public immutable splitterMom = SplitterMomLike(_log.getAddress("SPLITTER_MOM")); + SplitterLike public immutable splitter = SplitterLike(_log.getAddress("MCD_SPLIT")); + + event SplitterDisabled(); + + /** + * @notice Disables Splitter + */ + function _emergencyActions() internal override { + splitterMom.stop(); + emit SplitterDisabled(); + } + + /** + * @notice Returns whether the spell is done or not. + * @dev Checks if `splitter.hop() == type(uint).max` (disabled). + */ + function done() external view returns (bool) { + return splitter.hop() == type(uint256).max; + } +} diff --git a/src/splitter-stop/SplitterStopSpell.t.integration.sol b/src/splitter-stop/SplitterStopSpell.t.integration.sol new file mode 100644 index 0000000..05d5914 --- /dev/null +++ b/src/splitter-stop/SplitterStopSpell.t.integration.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.16; + +import {stdStorage, StdStorage} from "forge-std/Test.sol"; +import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol"; +import {SplitterStopSpell, SplitterLike} from "./SplitterStopSpell.sol"; + +contract SplitterStopSpellTest is DssTest { + using stdStorage for StdStorage; + + address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; + DssInstance dss; + address chief; + SplitterLike splitter; + SplitterStopSpell spell; + + function setUp() public { + vm.createSelectFork("mainnet"); + + dss = MCD.loadFromChainlog(CHAINLOG); + MCD.giveAdminAccess(dss); + chief = dss.chainlog.getAddress("MCD_ADM"); + splitter = SplitterLike(dss.chainlog.getAddress("MCD_SPLIT")); + spell = new SplitterStopSpell(); + + stdstore.target(chief).sig("hat()").checked_write(address(spell)); + + vm.makePersistent(chief); + } + + function testSplitterStopOnSchedule() public { + uint256 preHop = splitter.hop(); + assertTrue(preHop != type(uint256).max, "before: Splitter already stopped"); + assertFalse(spell.done(), "before: spell already done"); + + vm.expectEmit(true, false, false, false, address(spell)); + emit SplitterDisabled(); + + spell.schedule(); + + uint256 postHop = splitter.hop(); + assertEq(postHop, type(uint256).max, "after: Splitter not stopped"); + assertTrue(spell.done(), "after: spell not done"); + } + + function testRevertSplitterStopWhenItDoesNotHaveTheHat() public { + stdstore.target(chief).sig("hat()").checked_write(address(0)); + + uint256 preHop = splitter.hop(); + assertTrue(preHop != type(uint256).max, "before: Splitter already stopped"); + assertFalse(spell.done(), "before: spell already done"); + + vm.expectRevert(); + spell.schedule(); + + uint256 postHop = splitter.hop(); + assertEq(postHop, preHop, "after: Splitter stopped unexpectedly"); + assertFalse(spell.done(), "after: spell done unexpectedly"); + } + + event SplitterDisabled(); +} From 5ac99a546c91f069bced69a435f17c326bd3ed14 Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Tue, 1 Oct 2024 01:37:19 -0300 Subject: [PATCH 05/18] chore: Update Readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index e29d2ca..a9643bf 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ TBD. | Set `Clip` breaker | :white_check_mark: | :white_check_mark: | | Disable `DDM` | :white_check_mark: | :x: | | Stop `OSM` | :white_check_mark: | :white_check_mark: | +| Halt `PSM` | :white_check_mark: | :x: | +| Stop `Splitter` | n/a | :white_check_mark: | ### Wipe `AutoLine` @@ -65,6 +67,14 @@ Disables a Direct Deposit Module (`DIRECT_{ID}_PLAN`), preventing further debt f Stops the specified Oracle Security Module (`PIP_{GEM}`) instances, preventing updates in their price feeds. +### Halt `PSM` + +Halts swaps on the PSM, with optional direction (only GEM buys, only GEM sells, both). + +### Stop `Splitter` + +Disables the smart burn engine. + ## Design Emergency spells are meant to be as ABI-compatible with regular spells as possible, to allow Governance to reuse any From 17efbe81f122191fac969057514ae09d70ba6fa4 Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Tue, 1 Oct 2024 01:38:26 -0300 Subject: [PATCH 06/18] chore: readme formatting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a9643bf..7ed71b3 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Stops the specified Oracle Security Module (`PIP_{GEM}`) instances, preventing u ### Halt `PSM` -Halts swaps on the PSM, with optional direction (only GEM buys, only GEM sells, both). +Halts swaps on the `PSM`, with optional direction (only `GEM` buys, only `GEM` sells, both). ### Stop `Splitter` From 1a1b4caa828745c4dce016fd0d69fa0238d9475b Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:27:27 -0300 Subject: [PATCH 07/18] chore: Add missing natspec --- src/lite-psm-halt/SingleLitePsmHaltSpell.sol | 13 +++++++++++-- src/splitter-stop/SplitterStopSpell.sol | 6 ++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol index eddc0b7..01556bf 100644 --- a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol +++ b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol @@ -19,8 +19,8 @@ import {DssEmergencySpell} from "../DssEmergencySpell.sol"; enum Flow { SELL, // Halt only selling gems - BUY, // Halt only buying gems - BOTH // Halt Both + BUY, // Halt only buying gems + BOTH // Halt both } interface LitePsmMomLike { @@ -33,6 +33,12 @@ interface LitePsmLike { function HALTED() external view returns (uint256); } +/// @title Lite PSM Halt Emergency Spell +/// @notice Will halt trading on MCD_LITE_PSM_USDC_A, can halt only gem buys, sells, or both. +/// @custom:authors [Oddaf] +/// @custom:reviewers [] +/// @custom:auditors [] +/// @custom:bounties [] contract SingleLitePsmHaltSpell is DssEmergencySpell { LitePsmMomLike public immutable litePsmMom = LitePsmMomLike(_log.getAddress("LITE_PSM_MOM")); Flow public immutable flow; @@ -55,6 +61,9 @@ contract SingleLitePsmHaltSpell is DssEmergencySpell { return string(abi.encodePacked("Emergency Spell | MCD_LITE_PSM_USDC_A halt: ", flowToString(flow))); } + /** + * @notice Halts trading on LitePSM + */ function _emergencyActions() internal override { litePsmMom.halt(psm, flow); emit Halt(flow); diff --git a/src/splitter-stop/SplitterStopSpell.sol b/src/splitter-stop/SplitterStopSpell.sol index ea1478f..513bf9f 100644 --- a/src/splitter-stop/SplitterStopSpell.sol +++ b/src/splitter-stop/SplitterStopSpell.sol @@ -25,6 +25,12 @@ interface SplitterLike { function hop() external view returns (uint256); } +/// @title Splitter Stop Emergency Spell +/// @notice Will disable the Splitter (Smart Burn Engine, former Flap auctions) +/// @custom:authors [Oddaf] +/// @custom:reviewers [] +/// @custom:auditors [] +/// @custom:bounties [] contract SplitterStopSpell is DssEmergencySpell { string public constant override description = "Emergency Spell | Disable Splitter"; From 61bbd45d8488b7fc9222cf813f68fa24a7d0884c Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:48:55 -0300 Subject: [PATCH 08/18] Update README.md Co-authored-by: amusingaxl <112016538+amusingaxl@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ed71b3..c9369d6 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ TBD. | Disable `DDM` | :white_check_mark: | :x: | | Stop `OSM` | :white_check_mark: | :white_check_mark: | | Halt `PSM` | :white_check_mark: | :x: | -| Stop `Splitter` | n/a | :white_check_mark: | +| Stop `Splitter` | :x: | :white_check_mark: | ### Wipe `AutoLine` From 517f21300260f0c05bb812729fe4aacb0e59b381 Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:07:50 -0300 Subject: [PATCH 09/18] feat: Make PSM address dynamic --- src/lite-psm-halt/SingleLitePsmHaltSpell.sol | 13 +++++++------ .../SingleLitePsmHaltSpell.t.integration.sol | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol index 01556bf..03bf0dd 100644 --- a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol +++ b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol @@ -42,11 +42,12 @@ interface LitePsmLike { contract SingleLitePsmHaltSpell is DssEmergencySpell { LitePsmMomLike public immutable litePsmMom = LitePsmMomLike(_log.getAddress("LITE_PSM_MOM")); Flow public immutable flow; - address public immutable psm = _log.getAddress("MCD_LITE_PSM_USDC_A"); + address public immutable psm; event Halt(Flow what); - constructor(Flow _flow) { + constructor(address _psm, Flow _flow) { + psm = _psm; flow = _flow; } @@ -89,10 +90,10 @@ contract SingleLitePsmHaltSpell is DssEmergencySpell { } contract SingleLitePsmHaltSpellFactory { - event Deploy(Flow indexed flow, address spell); + event Deploy(address psm, Flow indexed flow, address spell); - function deploy(Flow flow) external returns (address spell) { - spell = address(new SingleLitePsmHaltSpell(flow)); - emit Deploy(flow, spell); + function deploy(address psm, Flow flow) external returns (address spell) { + spell = address(new SingleLitePsmHaltSpell(psm, flow)); + emit Deploy(psm, flow, spell); } } diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol index e9d95b3..7c56886 100644 --- a/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol +++ b/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol @@ -41,7 +41,7 @@ contract SingleLitePsmHaltSpellTest is DssTest { } function testPsmHaltOnSchedule(Flow flow) internal { - DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(flow)); + DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(address(psm), flow)); stdstore.target(chief).sig("hat()").checked_write(address(spell)); vm.makePersistent(chief); @@ -77,7 +77,7 @@ contract SingleLitePsmHaltSpellTest is DssTest { function testRevertPsmHaltWhenItDoesNotHaveTheHat() public { Flow flow = Flow.BOTH; - DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(flow)); + DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(address(psm), flow)); uint256 preTin = psm.tin(); uint256 preTout = psm.tout(); From cacd6072190a1d6790873323077c86166f9a1b9d Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:14:54 -0300 Subject: [PATCH 10/18] refactor: add _ prefix to internal _flowToString --- src/lite-psm-halt/SingleLitePsmHaltSpell.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol index 03bf0dd..2c0579e 100644 --- a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol +++ b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol @@ -51,7 +51,7 @@ contract SingleLitePsmHaltSpell is DssEmergencySpell { flow = _flow; } - function flowToString(Flow _flow) internal pure returns (string memory) { + function _flowToString(Flow _flow) internal pure returns (string memory) { if (_flow == Flow.SELL) return "SELL"; if (_flow == Flow.BUY) return "BUY"; if (_flow == Flow.BOTH) return "BOTH"; @@ -59,7 +59,7 @@ contract SingleLitePsmHaltSpell is DssEmergencySpell { } function description() external view returns (string memory) { - return string(abi.encodePacked("Emergency Spell | MCD_LITE_PSM_USDC_A halt: ", flowToString(flow))); + return string(abi.encodePacked("Emergency Spell | MCD_LITE_PSM_USDC_A halt: ", _flowToString(flow))); } /** From a47c858a547fd53a3e004d92c9f6d8d3eda3f59d Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:17:43 -0300 Subject: [PATCH 11/18] refactor: rename internal testPsmHaltOnSchedule to _checkPsmHaltOnSchedule --- .../SingleLitePsmHaltSpell.t.integration.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol index 7c56886..b951ef7 100644 --- a/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol +++ b/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol @@ -29,18 +29,18 @@ contract SingleLitePsmHaltSpellTest is DssTest { } function testPsmHaltOnScheduleBuy() public { - testPsmHaltOnSchedule(Flow.BUY); + _checkPsmHaltOnSchedule(Flow.BUY); } function testPsmHaltOnScheduleSell() public { - testPsmHaltOnSchedule(Flow.SELL); + _checkPsmHaltOnSchedule(Flow.SELL); } function testPsmHaltOnScheduleBoth() public { - testPsmHaltOnSchedule(Flow.BOTH); + _checkPsmHaltOnSchedule(Flow.BOTH); } - function testPsmHaltOnSchedule(Flow flow) internal { + function _checkPsmHaltOnSchedule(Flow flow) internal { DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(address(psm), flow)); stdstore.target(chief).sig("hat()").checked_write(address(spell)); vm.makePersistent(chief); From 563c47163caccc474b417f8a3c55ce766b209ed0 Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:24:36 -0300 Subject: [PATCH 12/18] refactor: rename event SplitterDisabled to Stop --- src/splitter-stop/SplitterStopSpell.sol | 4 ++-- src/splitter-stop/SplitterStopSpell.t.integration.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/splitter-stop/SplitterStopSpell.sol b/src/splitter-stop/SplitterStopSpell.sol index 513bf9f..43484da 100644 --- a/src/splitter-stop/SplitterStopSpell.sol +++ b/src/splitter-stop/SplitterStopSpell.sol @@ -37,14 +37,14 @@ contract SplitterStopSpell is DssEmergencySpell { SplitterMomLike public immutable splitterMom = SplitterMomLike(_log.getAddress("SPLITTER_MOM")); SplitterLike public immutable splitter = SplitterLike(_log.getAddress("MCD_SPLIT")); - event SplitterDisabled(); + event Stop(); /** * @notice Disables Splitter */ function _emergencyActions() internal override { splitterMom.stop(); - emit SplitterDisabled(); + emit Stop(); } /** diff --git a/src/splitter-stop/SplitterStopSpell.t.integration.sol b/src/splitter-stop/SplitterStopSpell.t.integration.sol index 05d5914..d173613 100644 --- a/src/splitter-stop/SplitterStopSpell.t.integration.sol +++ b/src/splitter-stop/SplitterStopSpell.t.integration.sol @@ -34,7 +34,7 @@ contract SplitterStopSpellTest is DssTest { assertFalse(spell.done(), "before: spell already done"); vm.expectEmit(true, false, false, false, address(spell)); - emit SplitterDisabled(); + emit Stop(); spell.schedule(); @@ -58,5 +58,5 @@ contract SplitterStopSpellTest is DssTest { assertFalse(spell.done(), "after: spell done unexpectedly"); } - event SplitterDisabled(); + event Stop(); } From 5279310bf5ef874950766065ee0fc845b926afb3 Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:37:57 -0300 Subject: [PATCH 13/18] chore: forge fmt --- src/lite-psm-halt/SingleLitePsmHaltSpell.sol | 5 +++-- src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol index 2c0579e..f49f0ae 100644 --- a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol +++ b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol @@ -19,8 +19,9 @@ import {DssEmergencySpell} from "../DssEmergencySpell.sol"; enum Flow { SELL, // Halt only selling gems - BUY, // Halt only buying gems - BOTH // Halt both + BUY, // Halt only buying gems + BOTH // Halt both + } interface LitePsmMomLike { diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol index b951ef7..305a118 100644 --- a/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol +++ b/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol @@ -16,7 +16,6 @@ contract SingleLitePsmHaltSpellTest is DssTest { LitePsmLike psm; SingleLitePsmHaltSpellFactory factory; - function setUp() public { vm.createSelectFork("mainnet"); From 0af25558207800836052ff913a365ba2a37e7f66 Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:01:11 -0300 Subject: [PATCH 14/18] chore: fix formatting --- src/lite-psm-halt/SingleLitePsmHaltSpell.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol index f49f0ae..154a335 100644 --- a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol +++ b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol @@ -21,7 +21,6 @@ enum Flow { SELL, // Halt only selling gems BUY, // Halt only buying gems BOTH // Halt both - } interface LitePsmMomLike { @@ -42,8 +41,8 @@ interface LitePsmLike { /// @custom:bounties [] contract SingleLitePsmHaltSpell is DssEmergencySpell { LitePsmMomLike public immutable litePsmMom = LitePsmMomLike(_log.getAddress("LITE_PSM_MOM")); - Flow public immutable flow; address public immutable psm; + Flow public immutable flow; event Halt(Flow what); From 94803c28cf7b5cfabcefe7dd277d70bd06e3d9d7 Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:13:26 -0300 Subject: [PATCH 15/18] refactor: Align litePsm emergency spell done implementation with audit recommendations --- src/lite-psm-halt/SingleLitePsmHaltSpell.sol | 34 ++++++++++---- .../SingleLitePsmHaltSpell.t.integration.sol | 45 ++++++++++++++++++- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol index 154a335..34c8c87 100644 --- a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol +++ b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol @@ -28,6 +28,7 @@ interface LitePsmMomLike { } interface LitePsmLike { + function wards(address) external view returns (uint256); function tin() external view returns (uint256); function tout() external view returns (uint256); function HALTED() external view returns (uint256); @@ -73,19 +74,36 @@ contract SingleLitePsmHaltSpell is DssEmergencySpell { /** * @notice Returns whether the spell is done or not. * @dev Checks if the swaps have been halted on the psm. + * The spell would revert if any of the following conditions holds: + * 1. LitePsmMom is not a ward of LitePsm + * 2. Call to LitePsm `HALTED()` reverts (likely not a LitePsm) + * In both cases, it returns `true`, meaning no further action can be taken at the moment. */ function done() external view returns (bool) { - uint256 halted = LitePsmLike(psm).HALTED(); - - if (flow == Flow.SELL || flow == Flow.BOTH) { - if (LitePsmLike(psm).tin() != halted) return false; + try LitePsmLike(psm).wards(address(litePsmMom)) returns (uint256 ward) { + // Ignore LitePsm instances that have not relied on LitePsmMom. + if (ward == 0) { + return true; + } + } catch { + // If the call failed, it means the contract is most likely not a LitePsm instance. + return true; } - if (flow == Flow.BUY || flow == Flow.BOTH) { - if (LitePsmLike(psm).tout() != halted) return false; - } + try LitePsmLike(psm).HALTED() returns (uint256 halted) { + if (flow == Flow.SELL || flow == Flow.BOTH) { + if (LitePsmLike(psm).tin() != halted) return false; + } - return true; + if (flow == Flow.BUY || flow == Flow.BOTH) { + if (LitePsmLike(psm).tout() != halted) return false; + } + + return true; + } catch { + // If the call failed, it means the contract is most likely not a LitePsm instance. + return true; + } } } diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol index 305a118..47371d0 100644 --- a/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol +++ b/src/lite-psm-halt/SingleLitePsmHaltSpell.t.integration.sol @@ -4,7 +4,26 @@ 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 {SingleLitePsmHaltSpellFactory, LitePsmLike, Flow} from "./SingleLitePsmHaltSpell.sol"; +import {SingleLitePsmHaltSpellFactory, Flow} from "./SingleLitePsmHaltSpell.sol"; + +interface LitePsmLike { + function deny(address) external; + function tin() external view returns (uint256); + function tout() external view returns (uint256); + function HALTED() external view returns (uint256); +} + +contract MockAuth { + function wards(address) external pure returns (uint256) { + return 1; + } +} + +contract MockPsmHaltedReverts is MockAuth { + function HALTED() external pure { + revert(); + } +} contract SingleLitePsmHaltSpellTest is DssTest { using stdStorage for StdStorage; @@ -74,6 +93,30 @@ contract SingleLitePsmHaltSpellTest is DssTest { assertTrue(spell.done(), "after: spell not done"); } + function testDoneWhenLitePsmMomIsNotWardInPsm() public { + DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(address(psm), Flow.BUY)); + + address pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY"); + vm.prank(pauseProxy); + psm.deny(address(litePsmMom)); + + assertTrue(spell.done(), "spell not done"); + } + + function testDoneWhenLitePsmDoesNotImplementHalted() public { + psm = LitePsmLike(address(new MockAuth())); + DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(address(psm), Flow.BUY)); + + assertTrue(spell.done(), "spell not done"); + } + + function testDoneWhenLitePsmHaltedReverts() public { + psm = LitePsmLike(address(new MockPsmHaltedReverts())); + DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(address(psm), Flow.BUY)); + + assertTrue(spell.done(), "spell not done"); + } + function testRevertPsmHaltWhenItDoesNotHaveTheHat() public { Flow flow = Flow.BOTH; DssEmergencySpellLike spell = DssEmergencySpellLike(factory.deploy(address(psm), flow)); From 1c06b2ffe68ae6895072dd75aa37967325e0962a Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:18:24 -0300 Subject: [PATCH 16/18] refactor: Align splitter emergency spell done implementation with audit recommendations --- src/splitter-stop/SplitterStopSpell.sol | 22 +++++++++- .../SplitterStopSpell.t.integration.sol | 42 ++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/splitter-stop/SplitterStopSpell.sol b/src/splitter-stop/SplitterStopSpell.sol index 43484da..15ff27a 100644 --- a/src/splitter-stop/SplitterStopSpell.sol +++ b/src/splitter-stop/SplitterStopSpell.sol @@ -22,6 +22,7 @@ interface SplitterMomLike { } interface SplitterLike { + function wards(address) external view returns (uint256); function hop() external view returns (uint256); } @@ -50,8 +51,27 @@ contract SplitterStopSpell is DssEmergencySpell { /** * @notice Returns whether the spell is done or not. * @dev Checks if `splitter.hop() == type(uint).max` (disabled). + * The spell would revert if any of the following conditions holds: + * 1. SplitterMom is not a ward of Splitter + * 2. Call to Splitter `hop()` reverts (likely not a Splitter) + * In both cases, it returns `true`, meaning no further action can be taken at the moment. */ function done() external view returns (bool) { - return splitter.hop() == type(uint256).max; + try splitter.wards(address(splitterMom)) returns (uint256 ward) { + // Ignore Splitter instances that have not relied on SplitterMom. + if (ward == 0) { + return true; + } + } catch { + // If the call failed, it means the contract is most likely not a Splitter instance. + return true; + } + + try splitter.hop() returns (uint256 hop) { + return hop == type(uint256).max; + } catch { + // If the call failed, it means the contract is most likely not a Splitter instance. + return true; + } } } diff --git a/src/splitter-stop/SplitterStopSpell.t.integration.sol b/src/splitter-stop/SplitterStopSpell.t.integration.sol index d173613..2a9283b 100644 --- a/src/splitter-stop/SplitterStopSpell.t.integration.sol +++ b/src/splitter-stop/SplitterStopSpell.t.integration.sol @@ -3,7 +3,25 @@ pragma solidity ^0.8.16; import {stdStorage, StdStorage} from "forge-std/Test.sol"; import {DssTest, DssInstance, MCD, GodMode} from "dss-test/DssTest.sol"; -import {SplitterStopSpell, SplitterLike} from "./SplitterStopSpell.sol"; +import {SplitterStopSpell} from "./SplitterStopSpell.sol"; + +interface SplitterLike { + function rely(address) external; + function deny(address) external; + function hop() external view returns (uint256); +} + +contract MockAuth { + function wards(address) external pure returns (uint256) { + return 1; + } +} + +contract MockSplitterHopReverts is MockAuth { + function hop() external pure { + revert(); + } +} contract SplitterStopSpellTest is DssTest { using stdStorage for StdStorage; @@ -11,6 +29,7 @@ contract SplitterStopSpellTest is DssTest { address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; DssInstance dss; address chief; + address splitterMom; SplitterLike splitter; SplitterStopSpell spell; @@ -20,6 +39,7 @@ contract SplitterStopSpellTest is DssTest { dss = MCD.loadFromChainlog(CHAINLOG); MCD.giveAdminAccess(dss); chief = dss.chainlog.getAddress("MCD_ADM"); + splitterMom = dss.chainlog.getAddress("SPLITTER_MOM"); splitter = SplitterLike(dss.chainlog.getAddress("MCD_SPLIT")); spell = new SplitterStopSpell(); @@ -43,6 +63,26 @@ contract SplitterStopSpellTest is DssTest { assertTrue(spell.done(), "after: spell not done"); } + function testDoneWhenSplitterMomIsNotWardInSplitter() public { + address pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY"); + vm.prank(pauseProxy); + splitter.deny(splitterMom); + + assertTrue(spell.done(), "spell not done"); + } + + function testDoneWhenSplitterDoesNotImplementHop() public { + vm.etch(address(splitter), address(new MockAuth()).code); + + assertTrue(spell.done(), "spell not done"); + } + + function testDoneWhenLiteSplitterHopReverts() public { + vm.etch(address(splitter), address(new MockSplitterHopReverts()).code); + + assertTrue(spell.done(), "spell not done"); + } + function testRevertSplitterStopWhenItDoesNotHaveTheHat() public { stdstore.target(chief).sig("hat()").checked_write(address(0)); From 86cc187950b9a29a08cb4bc33b93a88568c63be5 Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Mon, 28 Oct 2024 19:49:52 -0300 Subject: [PATCH 17/18] refactor: review suggestions on psmLite emergency Spell --- src/lite-psm-halt/SingleLitePsmHaltSpell.sol | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol index 34c8c87..405494d 100644 --- a/src/lite-psm-halt/SingleLitePsmHaltSpell.sol +++ b/src/lite-psm-halt/SingleLitePsmHaltSpell.sol @@ -32,6 +32,7 @@ interface LitePsmLike { function tin() external view returns (uint256); function tout() external view returns (uint256); function HALTED() external view returns (uint256); + function ilk() external view returns (bytes32); } /// @title Lite PSM Halt Emergency Spell @@ -42,13 +43,13 @@ interface LitePsmLike { /// @custom:bounties [] contract SingleLitePsmHaltSpell is DssEmergencySpell { LitePsmMomLike public immutable litePsmMom = LitePsmMomLike(_log.getAddress("LITE_PSM_MOM")); - address public immutable psm; + LitePsmLike public immutable psm; Flow public immutable flow; event Halt(Flow what); constructor(address _psm, Flow _flow) { - psm = _psm; + psm = LitePsmLike(_psm); flow = _flow; } @@ -60,14 +61,14 @@ contract SingleLitePsmHaltSpell is DssEmergencySpell { } function description() external view returns (string memory) { - return string(abi.encodePacked("Emergency Spell | MCD_LITE_PSM_USDC_A halt: ", _flowToString(flow))); + return string(abi.encodePacked("Emergency Spell | ", psm.ilk(), " | halt: ", _flowToString(flow))); } /** * @notice Halts trading on LitePSM */ function _emergencyActions() internal override { - litePsmMom.halt(psm, flow); + litePsmMom.halt(address(psm), flow); emit Halt(flow); } @@ -80,7 +81,7 @@ contract SingleLitePsmHaltSpell is DssEmergencySpell { * In both cases, it returns `true`, meaning no further action can be taken at the moment. */ function done() external view returns (bool) { - try LitePsmLike(psm).wards(address(litePsmMom)) returns (uint256 ward) { + try psm.wards(address(litePsmMom)) returns (uint256 ward) { // Ignore LitePsm instances that have not relied on LitePsmMom. if (ward == 0) { return true; @@ -90,16 +91,15 @@ contract SingleLitePsmHaltSpell is DssEmergencySpell { return true; } - try LitePsmLike(psm).HALTED() returns (uint256 halted) { - if (flow == Flow.SELL || flow == Flow.BOTH) { - if (LitePsmLike(psm).tin() != halted) return false; + try psm.HALTED() returns (uint256 halted) { + if (flow == Flow.SELL) { + return psm.tin() == halted; } - - if (flow == Flow.BUY || flow == Flow.BOTH) { - if (LitePsmLike(psm).tout() != halted) return false; + if (flow == Flow.BUY) { + return psm.tout() == halted; } - return true; + return psm.tin() == halted && psm.tout() == halted; } catch { // If the call failed, it means the contract is most likely not a LitePsm instance. return true; From fab504624ca2262c3839be0664ea2ae2e13ad2bc Mon Sep 17 00:00:00 2001 From: oddaf <106770775+oddaf@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:29:48 -0300 Subject: [PATCH 18/18] Update src/splitter-stop/SplitterStopSpell.sol Co-authored-by: amusingaxl <112016538+amusingaxl@users.noreply.github.com> --- src/splitter-stop/SplitterStopSpell.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/splitter-stop/SplitterStopSpell.sol b/src/splitter-stop/SplitterStopSpell.sol index 15ff27a..99530fb 100644 --- a/src/splitter-stop/SplitterStopSpell.sol +++ b/src/splitter-stop/SplitterStopSpell.sol @@ -33,7 +33,7 @@ interface SplitterLike { /// @custom:auditors [] /// @custom:bounties [] contract SplitterStopSpell is DssEmergencySpell { - string public constant override description = "Emergency Spell | Disable Splitter"; + string public constant override description = "Emergency Spell | Stop Splitter"; SplitterMomLike public immutable splitterMom = SplitterMomLike(_log.getAddress("SPLITTER_MOM")); SplitterLike public immutable splitter = SplitterLike(_log.getAddress("MCD_SPLIT"));