# Streamlining Emergency Spells for `MOM` Contracts
:warning: **THIS DOCUMENT IS A WORK IN PROGRESS** :warning:
## Context
MakerDAO spell execution is managed by the [`MCD_PAUSE`](https://etherscan.io/address/0xbe286431454714f511008713973d3b053a2d38f3) contract. While the name might sound strange for those unfamiliar with the Maker Protocol, it actually stems from one of its functionalities – know as the GSM delay – which enforces a `delay` in seconds between a spell being scheduled for execution (`plot`) and the actual execution (`cast`). This allows MakerDAO governance to have some time to react if a malicious proposal somehow gets enough support to be scheduled. Currently that delay is set to [48h](https://etherscan.io/address/0xbe286431454714f511008713973d3b053a2d38f3#readContract#F2).
However, some [emergency actions](https://manual.makerdao.com/governance/verification/gsm-exceptions) cannot wait for the GSM delay to be executed, otherwise it would completely defeat their purpose. Imagine a mal-functioning Oracle reporting a bad price or a collateral auction after a liquidation going badly, among other scenarios. In these cases, the Maker Protocol have a set of special contracts known as `MOM`s, which have one or more functions designed to bypass the GSM delay.
`MOM`s are purpose-specific, so any module within the protocol that might require emergency actions to be enacted before the GSM delay have their own `MOM`. From the [chainlog](https://chainlog.makerdao.com/), the following `MOM` contracts currently exist within the protocol:
- [`FLIPPER_MOM`](https://etherscan.io/address/0xc4be7f74ee3743bded8e0fa218ee5cf06397f472#code)
- [`OSM_MOM`](https://etherscan.io/address/0x76416a4d5190d071bfed309861527431304aa14f#code)
- [`CLIPPER_MOM`](https://etherscan.io/address/0x79fbdf16b366dfb14f66ce4ac2815ca7296405a0#code)
- [`DIRECT_MOM`](https://etherscan.io/address/0x1ab3145e281c01a1597c8c62f9f060e8e3e02fab#code)
- [`STARKNET_ESCROW_MOM`](https://etherscan.io/address/0xc238e3d63dfd677fa0fa9985576f0945c581a266#code)
- [`LINE_MOM`](https://etherscan.io/address/0x9c257e5aaf73d964aebc2140ca38078988fb0c10#code)
- [`FLAPPER_MOM`](https://etherscan.io/address/0xee2058a11612587ef6f5470e7776ceb0e4736078#code)
Notice that not all `MOM` contracts will be in the scope for implementation.
For instance `FLIPPER_MOM` should probably be off-boarded soon as `FLIPPER` contracts have been replaced by `CLIPPER` contracts from the [Liquidations 2.0 module](https://forum.makerdao.com/t/liquidations-2-0-technical-summary/4632). `STARKNET_ESCROW_MOM` also needs further discussion with the upcoming changes to the Starknet L2.
## The Problem
As it currently stands, emergency spells are quite resource-intensive. There always have to be engineers with in-depth spell crafting and reviewing knowledge on-call to be able to react to some extreme market conditions. Securely shipping spells can take up to several hours, even in an "all hands on the deck" situation. Regardless if there is only one single `MOM` action to be taken, the process will not be much faster.
Furthermore, after the off-boarding of the Protocol Engineering Core Unit, the spell team became a decentralized workforce, currently composed of 3 different EAs, making the coordination for emergency response trickier.
To make matters even worse, since spells are bespoke contracts that need to be crafted an reviewed on a timely fashion, they cannot be audited. Being required to make it available as fast as possible due to extreme circumstances increases the probability of making mistakes in the long run.
Luckily there are not many different `MOM` actions. There has been [some discussion](https://forum.makerdao.com/t/emergency-response-spells-as-a-general-discussion-point/19625) in the past to pre-deploy spells carrying out all possible `MOM` actions and have it them listed publicly so GovAlpha could rally delegates to get support for such spells faster, without requiring engineering resources to be available. Unfortunately, the team was off-boarded before they could implement it.
## Understanding `MOM` Actions in Emergency Spells
Using the [out-of-schedule Executive Vote from 2023-03-11](https://github.com/makerdao/community/blob/6eb4c4f6f6978850597658d7117db2d8c0e59248/governance/votes/Executive%20vote%20-%20March%2011%2C%202023.md) as an example, the implementation of the spell can be tracked through this [pull request](https://github.com/makerdao/spells-mainnet/pull/324) in the `makerdao/spells-mainnet` repository on GitHub.
That spell disabled the D3Ms for both AaveV2 and Compound at that time as per [suggestion of the Risk Team](https://forum.makerdao.com/t/emergency-proposal-risk-and-governance-parameter-changes-11-march-2023/20125).
Standard MakerDAO spells are build on top of the [`DssExec`](https://github.com/makerdao/dss-exec-lib/blob/69b658f35d8618272cd139dfc18c5713caf6b96b/src/DssExec.sol) contract. However, to be able bypass the GSM delay, we actually need to perform an open-heart surgery on that contract.
Instead of importing the `DssExec` f from the dependency in the `makerdao/spells-mainnet` repository (`import "dss-exec-lib/DssAction.sol";`), the spell crafter needs to actually pull in the `DssExec` source code and modify it in such way that the `MOM` functions are called from the [`DssExec.schedule`](https://github.com/makerdao/spells-mainnet/pull/324/files#diff-e402012cea22cd1867a71b2574134b60a32fc9b0952a67f0d2108d39d04d34f5R90-R102) function instead of being put in the customary `DssSpellAction.actions` function.
The above solution works because the emergency actions in all `MOM` contracts have `authority` set to the [`MCD_ADM`](https://etherscan.io/address/0x0a3f6849f78076aefadf113f5bed87720274ddc0#code) contract. The `MCD_ADM` authority allows any address that obtained enough support through the on-chain vote from MKR holders to assume its control. Whenever a spell gathers enough support for itself, anyone can call the `DssSpell.schedule` function immediately. Since the `MOM` actions are executed from within that function, they are carried out before the GSM delay.
Arguably this solution is a bit hacky, but it does the job.
## Proposed Solutions
Dewiz wants to pick that up and complete this task, because we believe it plays an important role to secure the protocol under extreme market conditions or unexpected behaviors within the protocol.
One aspect that we noticed is that since `MOM` actions are simple, we can potentially build "light-weight" spells that do not depend on `DssExecLib`. This can be applicable to any of the solutions described below.
Also, after gathering feedback from @LongForWisdom, we reached out the conclusion that none of them are perfect and we would probably be required to implement both of them.
### Predeployment of Emergency Spells
As [suggested by @LongForWisdom in MakerDAO Forum](https://forum.makerdao.com/t/emergency-response-spells-as-a-general-discussion-point/19625), we could predeploy specific emergency spells. Some candidates from the thread are:
> Candidates
>
> 1. Universal Oracle Freeze - Freezes all active oracles.
> 2. ETH Oracle Freeze - Freezes just ETH oracle.
> 3. BTC Oracle Freeze - Freezes just BTC oracle.
> 4. Universal Liquidations Breaker (Level 3) - Freezes all liquidations.
> 5. Universal D3M Breaker - Withdraws Dai from all D3M implementations.
> 6. Specific D3M Breakers - Withdraws Dai from a specific D3M implementation.
> 7. StarkNet Breaker - Prevents minting of Dai on mainnet that originates on the StarkNet L2.
For 2., 3., 6. and 7. it seems easy enough. Such spells can be built and deployed with an extended expiration period and made available to MakerDAO Governance.
The `FLAPPER_MOM` did not exist when that post was written, but it is also a case where it is faily easy to predeploy a spell to stop the surplus auctions.
Regarding 5., it is still feasible, since there are only a handful of D3Ms onboraded in the Protocol (most of them being disabled as of this writing) and on-boarding/off-boarding D3Ms is not something that happens frequently.
Regarding 1. and 4., it is a little bit trickier, since they are "shotgun" responses.
`ClipperMom.setBreaker` requires the actual `Clipper` contract as a parameter, besides the `level` and a `delay`. While `level` is pretty much given and `delay` can have a sane default, we need to get all `Clipper` instances in the system to stop them all. Luckily the [`ILK_REGISTRY`](https://etherscan.io/address/0x5a464c28d19848f44199d003bef5ecc87d090f87#readContract) is iterable and contains a reference to all `Clipper`s.
`OsmMom.stop` requires an `ilk` parameter. Once again we could leverage the `ILK_REGISTRY` to get all ilks and cross-check the `PIP` values there with the ones in `OsmMom.osms` mapping.
#### Limitations
We will not expand much on the limitations and drawbacks regarding this solution because @LongForWisdom's post linked above already provides a comprehensive list of them, with which we agree.
From a technical perspective, the biggest issue we could have with "shotgun" spells is that they could potentially hit block gas limits.
Another aspect that requires careful consideration is the extended expiration period for such spells. Customarily spells are valid for 30 days, if they do not get support during this time window, they cannot be cast anymore. We would need to find a duration for such emergency spells that optimize for maintainability (i.e.: need for deploying new spells) without compromising the security of the Maker Protocol.
### Emergency Spell Factories
The idea is to build Emergency Spell Factories for each one of the `MOM` actions. **Factories will be audited** and can permissionlessly deploy emergency spells to carry out `MOM` actions.
In our point of view, factories are beneficial because:
1. Not all `MOM`s are born equal
Actions like `ClipperMom.setBreaker` take a couple of parameters:
```solidity
// Governance action without delay
function setBreaker(address clip, uint256 level, uint256 delay) external auth {
require(level <= 3, "ClipperMom/nonexistent-level");
ClipLike(clip).file("stopped", level);
// If governance changes the status of the breaker we want to lock for one hour
// the permissionless function so the osm can pull new nxt price to compare
locked[clip] = add(block.timestamp, delay);
emit SetBreaker(clip, level);
}
```
The factory would allow those parameters to be provided by the deployer, as deploying one spell contract per each parameter combination is virtually impossible. the deployer should follow advice from a domain expert EA – such as BA Labs – in order to provide the correct parameters. All deploy functions parameters and their most common/suggested values should be well documented, such the emergency response does not depend on Engineering resources.
Also we need to keep in mind that spells should not contain storage variables. The `ClipperMomSetBreakerFactory.deploy` would accept all required parameters and it would create a `ClipperMomSetBreakerSpell` passing those as `immutable` parameters to be set in the constructor. Notice how usually spells do not have constructor parameters, however in this case they are required and would not violate the "no storage" rule because they are `immutable`.
2. Spells usually have an expiration date
Actions like `FlapperMom.stop`, do not take any parameters:
```solidity
// Governance action without delay
function stop() external auth {
flapper.file("hop", type(uint256).max);
emit Stop();
}
```
In theory we could deploy a single `FlapperMomStopSpell` and list it somewhere, however they would have to have a larger expiration date in order to be executed during an emergency response.
So by having spell factories we can solve both of these issues more easily.
3. Avoid "Shotgun" Solutions
"Shotgun" solutions like "Disable all OSMs" or "Freeze all collateral auctions" might seem a good idea in principle, however they do come with some challenges.
Since spells cannot have storage, we either hard-code all possible values (i.e.: all `ilk`s in the system) or we try to use iteration to find all relevant contracts dynamically.
Hard-coded spells have the drawback of requiring to be re-deployed whenever a relevant part of the system is changed (i.e.: onboarding a new `ilk`). As for iteration, it is not always doable on-chain (it requires iterability to be explicitly added) and it might hit gas limits within a block.
Furthermore, shutting down an entire module of the Maker Protocol might have deeper consequences that require some in-depth knowledge to assess.
#### Limitations
1. Even though the spell factories would be permissionless, deploying spells through it would have to be done by trusted parties that understand the protocol. At a minimum, those trusted parties would have to verify if a contract was correctly deployed from the factories, specially in cases where parameters are requried.
2. In Solidity, [`immutable`](https://docs.soliditylang.org/en/latest/contracts.html#constant-and-immutable-state-variables) variables have some limitations. Specifically, we cannot have arrays as immutable variables, which can make it impossible to create a factory that can deploy a single spell to execute the same action in several contracts or ilks, at least without resorting to custom encoding tricks – which would make it harder for deployers to work with the factories – or through some `delegatecall` scheme – which may bring some additional security implications.
- As an example, the emeregency spell listed above disables the D3M for `DIRECT_COMPV2_DAI_PLAN` and `DIRECT_AAVEV2_DAI_PLAN` at the same time, which would be hard to do at least at first sight.
- `MCD_ADM` has a feature that allows MKR holders to support up to 5 proposals at the same time, so in theory we could solve the case above by deploying 2 spells through the factories and having Governance to support both at the same time. The drawback of this approach is that this feature is not widely used or even known by MKR holders, so they would need to be educated on how to use it.
3. Emergency spells may require other actions, not related to `MOM` contracts to be executed. The proposed solution does not work in such cases, so the existing process cannot be completely eliminated.
#### Appendix A: Spell Action Overloads
A potential solution for issue 2. above can be achieved by having multiple overloads for the spell action contract. The idea can be better demonstrated with code, so let's consider the `OsmMom` contract.
`OsmMom.stop` can stop `OSM`s identified by an `ilk`. To be able to pause multiple `ilk`s, the function needs to be called multiple times with, one per each ilk. To be able to do so, we define several contracts containing from 1 to as many `immutable` ilk parameters and make sure to call them all when `DssExec.schedule` is called.
:warning: The code below compiles, but it has not been reviewed or tested. It's included here only to illustrate the general idea. We purposedly remove the dependency from `DssExecLib`, as it is not used in `MOM` actions. `DssExec` and `DssAction` have also been modified to consider only emergency spells (i.e.: `officeHours` always set to `false`). :warning:
```solidity
pragma solidity ^0.8.21;
interface OnScheduleLike {
function onSchedule() external;
}
interface ChainlogLike {
function getAddress(bytes32 key) external view returns (address);
}
interface DsPauseLike {
function delay() external view returns (uint256);
function plot(address action, bytes32 tag, bytes memory fax, uint256 eta) external;
function exec(address action, bytes32 tag, bytes memory fax, uint256 eta) external;
}
abstract contract CustomDssAction {
// Office Hours is always false for emergency spells.
bool public constant officeHours = false;
// DssExec calls execute.
function execute() external {
actions();
}
// CustomDssAction developer must override `actions()` and place all actions to be called inside.
// The DssExec function will call this.
// By keeping this function public we allow simulations of `execute()` on the actions outside of the cast time.
function actions() public {
// No actions by default.
}
// Provides a descriptive tag for bot consumption
// This should be modified weekly to provide a summary of the actions
// Hash: seth keccak -- "$(wget https://<executive-vote-canonical-post> -q -O - 2>/dev/null)"
function description() external view virtual returns (string memory);
// Returns the next available cast time
function nextCastTime(uint256 eta) external view returns (uint256 castTime) {
require(eta != 0 && eta <= type(uint40).max, "DssAction/invalid-eta");
uint40 ts = uint40(block.timestamp);
castTime = ts > eta ? ts : eta; // Any day at XX:YY
}
}
contract HookableDssExec {
ChainlogLike public constant chainlog = ChainlogLike(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F);
bytes public constant sig = abi.encodeWithSelector(CustomDssAction.execute.selector);
DsPauseLike public immutable pause = DsPauseLike(chainlog.getAddress("MCD_PAUSE"));
bytes32 public immutable tag;
address public immutable action;
uint256 public immutable expiration;
uint256 public eta;
bool public done;
// Provides a descriptive tag for bot consumption
// This should be modified weekly to provide a summary of the actions
// Hash: seth keccak -- "$(wget https://<executive-vote-canonical-post> -q -O - 2>/dev/null)"
function description() external view returns (string memory) {
return CustomDssAction(action).description();
}
function officeHours() external view returns (bool) {
return CustomDssAction(action).officeHours();
}
function nextCastTime() external view returns (uint256 castTime) {
return CustomDssAction(action).nextCastTime(eta);
}
// @param _description A string description of the spell
// @param _expiration The timestamp this spell will expire. (Ex. block.timestamp + 30 days)
// @param _spellAction The address of the spell action
constructor(uint256 _expiration, address _spellAction) {
expiration = _expiration;
action = _spellAction;
bytes32 _tag; // Required for assembly access
address _action = _spellAction; // Required for assembly access
assembly { _tag := extcodehash(_action) }
tag = _tag;
}
function schedule() public {
require(block.timestamp <= expiration, "This contract has expired");
require(eta == 0, "This spell has already been scheduled");
eta = block.timestamp + pause.delay();
pause.plot(action, tag, sig, eta);
OnScheduleLike(action).onSchedule();
}
function cast() public {
require(!done, "spell-already-cast");
done = true;
pause.exec(action, tag, sig, eta);
}
}
contract ParametrizableDssSpell is HookableDssExec {
uint256 public constant DEFAULT_EXPIRATION = 30 days;
constructor(address action) HookableDssExec(block.timestamp + DEFAULT_EXPIRATION, action) {}
}
interface OsmMomLike {
function stop(bytes32 ilk) external;
}
abstract contract OsmMomStopActionBase is CustomDssAction, OnScheduleLike {
string public constant override description = "Emergency Spell: OsmMom.stop";
ChainlogLike public constant chainlog = ChainlogLike(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F);
OsmMomLike public immutable osmMom = OsmMomLike(chainlog.getAddress("OSM_MOM"));
}
contract OsmMomStopAction1 is OsmMomStopActionBase {
bytes32 public immutable ilk0;
constructor(bytes32[] memory ilks) {
require(ilks.length == 1, "OsmMomStopAction1/invalid-number-of-ilks");
ilk0 = ilks[0];
}
function onSchedule() public {
osmMom.stop(ilk0);
}
}
contract OsmMomStopAction2 is OsmMomStopActionBase {
bytes32 public immutable ilk0;
bytes32 public immutable ilk1;
constructor(bytes32[] memory ilks) {
require(ilks.length == 2, "OsmMomStopAction2/invalid-number-of-ilks");
ilk0 = ilks[0];
ilk1 = ilks[1];
}
function onSchedule() public {
osmMom.stop(ilk0);
osmMom.stop(ilk1);
}
}
contract OsmMomStopAction3 is OsmMomStopActionBase {
bytes32 public immutable ilk0;
bytes32 public immutable ilk1;
bytes32 public immutable ilk2;
constructor(bytes32[] memory ilks) {
require(ilks.length == 3, "OsmMomStopAction3/invalid-number-of-ilks");
ilk0 = ilks[0];
ilk1 = ilks[1];
ilk1 = ilks[2];
}
function onSchedule() public {
osmMom.stop(ilk0);
osmMom.stop(ilk1);
osmMom.stop(ilk2);
}
}
// ... as many overrides as needed
contract OsmMomStopSpellFactory {
event SpellDeployed(address indexed addr);
function deploy(bytes32[] calldata ilks) external returns (address spell) {
address action;
if (ilks.length == 1) {
action = address(new OsmMomStopAction1(ilks));
} else if (ilks.length == 2) {
action = address(new OsmMomStopAction2(ilks));
} else if (ilks.length == 3) {
action = address(new OsmMomStopAction3(ilks));
} // ...
else {
revert("OsmMomStopSpellFactory/invalid-number-of-ilks");
}
spell = address(new ParametrizableDssSpell(action));
emit SpellDeployed(spell);
}
}
```
This way the deployer of the spells only has to interact with a single function `deploy(bytes32[] calldata)`, which will figure out the correct overload to deploy and pass it as a parameter to `DssSpell`.
For `MOM` actions requiring multiple parameters, we can either define a custom struct like:
```solidity
struct BreakerParams {
address clip;
uint256 level;
uint256 delay;
}
function deploy(BreakerParams[] calldata params) external returns (address spell) {
// ...
}
```
Or require the deployer to send 3 separate arrays like:
```solidity
function deploy(address[] calldata clip, uint256[] calldata level, uint256[] calldata delay)
external returns (address spell) {
}
```