-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add priority emergency spells #1
Changes from all commits
8a9cb25
89a518a
b002456
5bc1ab3
ba2dfc9
43b2d04
fc6b1b9
87387db
fa32048
2160801
89dcc8c
9587bef
8a9382e
1b9b6a9
69d1027
5d70199
7cd769c
206c15c
d1bee94
6b421dd
85a7adc
efec4e7
5b57421
1a375b5
2eeec5b
cfafa6b
359fc0d
50493ac
d594174
7ad2715
ce34c28
0372ffb
04fe1d7
bc06eeb
c810d13
a8e2beb
75c5a06
d9fc4ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,66 +1,136 @@ | ||
## 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 | Multi ilk | | ||
| :---------- | :--------: | :-------: | | ||
| 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 `MCD_IAM_AUTO_LINE`. 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 `MCD_CLIP_{ILK}` 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 (`DIRECT_{ID}_PLAN`), preventing further debt from being generated from it. | ||
|
||
```shell | ||
$ forge --help | ||
$ anvil --help | ||
$ cast --help | ||
``` | ||
### Stop `OSM` | ||
|
||
Stops the specified Oracle Security Module (`PIP_{GEM}`) instances, preventing updates in their price feeds. | ||
|
||
## 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. | ||
|
||
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. 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 are not applicable. | ||
|
||
[spell-done]: https://github.com/makerdao/dss-exec-lib/blob/69b658f35d8618272cd139dfc18c5713caf6b96b/src/DssExec.sol#L43 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
// SPDX-FileCopyrightText: © 2024 Dai Foundation <www.daifoundation.org> | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Affero General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Affero General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU Affero General Public License | ||
// along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
pragma solidity ^0.8.16; | ||
oddaf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
interface ChainlogLike { | ||
function getAddress(bytes32 key) external view returns (address); | ||
} | ||
|
||
interface DssExec { | ||
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); | ||
} | ||
|
||
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 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. | ||
/// For emergency spells, the GSM delay is not applicable. | ||
uint256 public constant eta = 0; | ||
/// @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; | ||
// @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 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 instead of an immutable variable. | ||
* @return The contract codehash. | ||
*/ | ||
function tag() external view returns (bytes32) { | ||
return address(this).codehash; | ||
} | ||
|
||
/** | ||
* @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 effect immediately, so there is no need to call `pause.plot()`. | ||
*/ | ||
function schedule() external { | ||
_emergencyActions(); | ||
} | ||
Comment on lines
+93
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at the chief-keeper block-scheme, I understand that it will try to call To solve this loop, we can either:
All in all, I would recommend to make some changes to the base contract to prevent keeper loop. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at that line we have: Notice that all emergency spells have meaningful There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The idea of having pre-deployed spells is to not require any non-engineer to ever provide inputs manually. Even in the cases where there are factories, they would be used by engineers to deploy the contracts and hand over only the addresses to other stakeholders. |
||
|
||
/** | ||
* @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; | ||
} | ||
Comment on lines
+106
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: if we don't need nor expect There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't take a deep dive into the chief keeper, just looked at the most important aspects of it. |
||
|
||
/** | ||
* @notice No-op. | ||
* @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. | ||
*/ | ||
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; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated to this code review, I wonder how current "whitelisting"-based monitoring implemented by techops works and if it detects votes on a random non-whitelisted contract before it's been too late / spell is already
schedule
d or if they even depend on the event emitted byplan
to detect that something is offThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me raise that question to them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From Techops: