Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: cleanup for release #2

Merged
merged 21 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1d2a4d0
refactor: clean up code and tests
amusingaxl Apr 25, 2024
0e9fe12
docs: update `DropSpell` natspec
amusingaxl Apr 25, 2024
f5f85ec
refactor: remove custom test script
amusingaxl Apr 25, 2024
f062efe
refactor: update Github actions to run automatically
amusingaxl Apr 25, 2024
8310f46
forge install: dss-test
amusingaxl Apr 25, 2024
8eeac41
refactor: remove forge-std direct dependency
amusingaxl Apr 25, 2024
e4a0cde
refactor: replace forge-st/Test with dss-test/DssTest
amusingaxl Apr 25, 2024
b8601a6
feat: add deployment script
amusingaxl Apr 25, 2024
cd7db94
refactor: reorganize helper test contracts
amusingaxl May 14, 2024
9eeb52c
refactor: remove redundant internal function
amusingaxl May 14, 2024
547473e
refactor: extract `EmergencyDropSpell` into its own file
amusingaxl May 14, 2024
5d05b7f
test: add aftermath condition to `Protego.drop` test cases
amusingaxl May 14, 2024
a3cd851
refactor: remove methods relying on conforming spells
amusingaxl May 15, 2024
6e01020
chore: update NatSpec and remove unused interfaces
amusingaxl May 15, 2024
c095eb0
refactor: add test coverage for drop spell `done()`
amusingaxl May 15, 2024
5ecaf14
docs: fix README format [skip CI]
amusingaxl May 15, 2024
82a8eb3
docs: update README structure
amusingaxl May 15, 2024
4329683
refactor: reorg and add events to `EmergencyDropSpell`
amusingaxl May 16, 2024
d1d56af
refactor: add explicity error msg for `expectRevert`
amusingaxl May 16, 2024
f369273
refactor: remove unsed method from `test/NonConformingSpell`
amusingaxl May 16, 2024
7f454e0
docs: improve natspec structure (skip ci)
amusingaxl May 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FOUNDRY_ROOT_CHAINID='number: the ID of the chain'
FOUNDRY_EXPORTS_OVERWRITE_LATEST='bool: whether to override the latest deployment or not'
8 changes: 7 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
name: test

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

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

jobs:
check:
Expand Down
14 changes: 10 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/
# Ignores broadcast logs
broadcast/

# Ignores script config
script/input/**/*.json
!script/input/**/template-*.json
SidestreamColdMelon marked this conversation as resolved.
Show resolved Hide resolved
script/output/**/*.json

# Docs
docs/

# Dotenv file
.env
7 changes: 3 additions & 4 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
branch = v1.1.1
[submodule "lib/dss-test"]
path = lib/dss-test
url = https://github.com/makerdao/dss-test
3 changes: 0 additions & 3 deletions Makefile

This file was deleted.

68 changes: 44 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,68 @@
# protego
# Protego

Protego is a tool to permissionlessly deploy a spell which can be used to drop a particular plan in MakerDAO's Pause contract, or in extreme cases the protego contract can itself be lifted to the hat, allowing any user to permissionlessly drop any scheduled plan.
Protego is a tool to permissionlessly deploy a spell which can be used to drop a particular plan in the `MCD_PAUSE`
contract, or in extreme cases the `Protego` contract can itself be lifted to the hat, allowing any user to
permissionlessly drop any scheduled plan.

## Context

- A `plan` is a scheduled `delegatecall` that exists in [`MCD_PAUSE`](https://github.com/makerdao/ds-pause) and can be
permissionlessly executed.
- Plans in `MCD_PAUSE` are [identified by a unique 32 byte hash](https://github.com/makerdao/ds-pause/blob/master/src/pause.sol#L99).
- A conformant spell here is one where the values returned by `action()`, `tag()`, `sig()` and `eta()` are equal to the
corresponding `plan`'s `usr`, `tag`, `fax` and `eta` values respectively. Bad actors could potentially manipulate the
return values of these functions such that they are not equal, which would result in a non-conformant spell.

## Usage

### Drop an individual plan
### Create a single drop spell

Choose a reason for hiding this comment

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

As there are 4 different options to use with 4 different reasons and 2 different set of actions (voting for protego + calling drop vs calling for new spell + voting for new spell + calling drop), I find it necessary to have a very clear instructions (IF/ELSE, step-by-step) explaining which of the 4 functions to use when and in which order. As in case of emergency:

  • There will be a limited time to make a decision
  • There will be delegates/non-technical users who also need to be convinced to vote for a specific version of protego (imagine someone deliberately uses deploy in order to confuse delegates and don't vote for the protego itself)

As the next step, we might even consider writing a script that evaluates currently plotted spells, then fetches required parameters for the specific call and executes it. This way, the choice itself would also be reviewable/auditable in advance.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I find it necessary to have a very clear instructions (IF/ELSE, step-by-step) explaining which of the 4 functions to use when and in which order.

Fully agree. Part of the deliverable is to provide a long-written post and also train governance facilitators on how to use the tool. We're just not sure the README is the best way to put the content verbatim. Maybe we'll have a link here to these documents once it's finished.

As the next step, we might even consider writing a script that evaluates currently plotted spells, then fetches required parameters for the specific call and executes it.

Not sure a script would be good enough in this case. There probably needs to be a UI for that and such feature be included there, however this is out of the scope of this repo.

Choose a reason for hiding this comment

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

My main point is: the choice to use one of the four methods could be included in the scope of the repo specifically for it to be audited. This can be done the simplest way possible, for example, using a s.sol file or a js-based script

Copy link
Contributor Author

@amusingaxl amusingaxl May 14, 2024

Choose a reason for hiding this comment

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

Just to clarify: when you mention the "four methods", you are referring to:

  1. Deploy a drop spell with a spell address (for conforming spells)
  2. Deploy a drop spell from plot params (for non-conforming spells)
  3. Call Protego.drop() with a spell address (for conforming spells)
  4. Call Protego.drop() with plot params (for non-conforming spells)

Correct?

Do you mean we should include all flows into scripts for auditing?
What kind of additional value do you expect this to bring, given that those flows are already present in the test files?

Choose a reason for hiding this comment

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

Correct?

Correct

Do you mean we should include all flows into scripts for auditing? What kind of additional value do you expect this to bring, given that those flows are already present in the test files?

Current tests only check listed flows separately, isolated from each other. My proposal is to add usage instruction or (if possible) a script that would explain "how to choose between 4 methods" or directly execute a specific method (e.g. if there are multiple spells plotted and not executed). I propose this because this decision (no matter if described in words or in code) is security-critical (e.g. if a wrong method is used it might not drop the spell) and time-sensitive (highly likely there wouldn't be time to deploy another dropspell as voting also takes time) and therefore would highly benefit from external audit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm hoping that the documentation is now clear enough, since there are only 2 methods left.
Maybe there should be something written in a "decision tree" format, but not sure if it's strictly necessary.

Choose a reason for hiding this comment

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

Maybe there should be something written in a "decision tree" format, but not sure if it's strictly necessary.

Let's do it in the follow up PR, as part of the script?

Protego contains a factory to permissionlessly create a spell that is able to drop a single plan in the [pause](https://github.com/dapphub/ds-pause).
If there is sufficient ecosystem interest to cancel (`drop`) a particular scheduled set of actions (the target `plan`),
Protego contains a factory to permissionlessly create a spell ("drop spell"), which – upon being given the hat
– is able to drop the targeted plan in `MCD_PAUSE`.

* `function deploy(DSSpellLike _spell) external returns (address)`
There are two possibilities:

Used to deploy a single spell that can be elected to a privileged position. This requires that the `_spell` address parameter conforms to a MakerDAO conformant [spell](https://github.com/makerdao/spells-mainnet)
#### 1. `deploy(DssSpellLike _spell)(address)`

Choose a reason for hiding this comment

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

In which case would you prefer using deploy(DssSpellLike _spell)(address) over deploy(address _usr, bytes32 _tag, bytes memory _fax, uint256 _eta)(address)? I understand the problem with non-conformant (or rather malicious) spells, so in my opinion it's always safer to use the second option as it doesn't require an expertise to prove that the spell is conformant. It's just a bit more complicated to use as it requires parsing calldata of the original plot transaction (but that can also be automated outside of the chain)

Copy link
Contributor Author

@amusingaxl amusingaxl May 14, 2024

Choose a reason for hiding this comment

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

There are cases where a conforming spell might need to be dropped. For instance: some last minute legal issue that was found or a bug in the spell.

Choose a reason for hiding this comment

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

I understand that there are conformant spells that might need to be dropped. I rather question why not only have deploy(address _usr, bytes32 _tag, bytes memory _fax, uint256 _eta)(address) as it can be used to drop both types of spells and more importantly does not require expertise to decide if the spell is conformant. You can have a script along the repo to fetch all plotted spells and parse required parameters for calling this function no matter if the spell is conformant or not.

Choose a reason for hiding this comment

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

See the comment for a scenario when calling deploy(DssSpellLike _spell)(address) could be weaponised

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, it will make it more difficult instead of easier for users.
Code was updated.

* `function deploy(address _usr, bytes32 _tag, bytes memory _fax, uint256 _eta) external returns (address)`
Used for [conformant spells](https://github.com/makerdao/spells-mainnet), will create a drop spell targeting a plan in
`MCD_PAUSE`, using the values that the spell provides.

Used to deploy a single spell that can be elected to a privileged position. This can be used to drop a non-conformant spell by reproducing the associated components of the plan.
#### 2. `deploy(address _usr, bytes32 _tag, bytes memory _fax, uint256 _eta)(address)`

### Drop any plan
Used for non-conformat spells, will create a drop spell targeting a plan in `MCD_PAUSE` using values provided manually
– which can be found in the payload of the transaction that originally planned it.

In the case of a governance attack, many spells could be plotted quickly by an attacker. To ameliorate this risk, the `protego` contract itself can be elected to a hat role, which permits any user to permissionlessly drop any plan.
### Enable permissionless dropping of any plan

* `function drop(DSSpellLike _spell) external`
In case of a governance attack, numerous spells could be created and planned by an attacker at a rate faster than a
single hat spell can `drop` them. To mitigate this risk, the `Protego` contract itself can be given the hat, which
allows any user to permissionlessly drop any plan.

Drop a conformant spell from the pause.
Once again, there are two alternatives:

* `function drop(address _usr, bytes32 _tag, bytes memory _fax, uint256 _eta) public`
#### 1. `drop(DssSpellLike _spell)`

Drop a plan from the pause by reproducing the associated components of the plan.
Used for conformat spells, will **immediately** drop a plan in `MCD_PAUSE` using the values the spell provides.

## Additional Functions
#### 2. `drop(address _usr, bytes32 _tag, bytes memory _fax, uint256 _eta)`

### ID
Used for non-conformant spells, will **immediately** drop a plan in `MCD_PAUSE` using values provided manually –
which can be found in the payload of the transaction that originally planned it.

* `function id(DSSpellLike _spell) public view returns (bytes32)`
* `function id(address _usr, bytes32 _tag, bytes memory _fax, uint256 _eta) public pure returns (bytes32)`
### Additional functions

Return the `bytes32` id of a plan in the pause.
#### `id()`

### Planned
- `id(DssSpellLike _spell)(bytes32)`
- `id(address _usr, bytes32 _tag, bytes memory _fax, uint256 _eta)(bytes32)`

* `function planned(DSSpellLike _spell) public view returns (bool)`
* `function planned(address _usr, bytes32 _tag, bytes memory _fax, uint256 _eta) public view returns (bool)`
* `function planned(bytes32 _id) public view returns (bool)`
Returns the `bytes32` id of a given plan using the same method that `hash` does in `MCD_PAUSE`.

Returns `true` if a plan has been plotted.
#### `planned()`

- `planned(DssSpellLike _spell)(bool)`
- `planned(address _usr, bytes32 _tag, bytes memory _fax, uint256 _eta)(bool)`
- `planned(bytes32 _id)(bool)`

Returns `true` if a plan has been scheduled for execution.
18 changes: 14 additions & 4 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
[profile.default]
src = 'src'
out = 'out'
libs = ['lib']
src = "src"
out = "out"
script = 'script'
libs = ["lib"]
solc = '0.8.16'
optimizer = true

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
fs_permissions = [
{ access = "read", path = "./out/" },
{ access = "read", path = "./script/input/" },
{ access = "read-write", path = "./script/output/" }
SidestreamColdMelon marked this conversation as resolved.
Show resolved Hide resolved
]

[rpc_endpoints]
mainnet = "${ETH_RPC_URL}"
1 change: 1 addition & 0 deletions lib/dss-test
Submodule dss-test added at 6d4029
1 change: 0 additions & 1 deletion lib/forge-std
Submodule forge-std deleted from 054a25
43 changes: 43 additions & 0 deletions script/ProtegoDeploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: © 2023 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 {Script} from "forge-std/Script.sol";
import {MCD, DssInstance} from "dss-test/MCD.sol";
import {ScriptTools} from "dss-test/ScriptTools.sol";
import {ProtegoDeploy, ProtegoDeployParams} from "./dependencies/ProtegoDeploy.sol";
import {ProtegoInstance} from "./dependencies/ProtegoInstance.sol";

contract ProtegoDeployScript is Script {
using ScriptTools for string;

string constant NAME = "protego";

address constant CHAINLOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
DssInstance dss = MCD.loadFromChainlog(CHAINLOG);
address pause = dss.chainlog.getAddress("MCD_PAUSE");
ProtegoInstance inst;

function run() external {
vm.startBroadcast();

inst = ProtegoDeploy.deploy(ProtegoDeployParams({pause: pause}));

vm.stopBroadcast();

ScriptTools.exportContract(NAME, "protego", inst.protego);
}
}
30 changes: 30 additions & 0 deletions script/dependencies/ProtegoDeploy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: © 2023 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 {ScriptTools} from "dss-test/ScriptTools.sol";
import {Protego} from "src/Protego.sol";
import {ProtegoInstance} from "./ProtegoInstance.sol";

struct ProtegoDeployParams {
address pause;
}

library ProtegoDeploy {
SidestreamColdMelon marked this conversation as resolved.
Show resolved Hide resolved
function deploy(ProtegoDeployParams memory p) internal returns (ProtegoInstance memory r) {
r.protego = address(new Protego(p.pause));
}
}
21 changes: 21 additions & 0 deletions script/dependencies/ProtegoInstance.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: © 2023 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;

struct ProtegoInstance {
address protego;
}

1 change: 1 addition & 0 deletions script/input/1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Inputs for Mainnet scripts.
1 change: 1 addition & 0 deletions script/output/1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Outputs for Mainnet scripts.
13 changes: 0 additions & 13 deletions script/test.sh

This file was deleted.

Loading
Loading