Auditing Executive Spells¶
This is intended as a guide to help MKR Holders verify executive spells in the Maker Protocol before they vote for them. This document contains three main sections.
First, finding the Contract Code deals with locating the code for the executive in question.
Second, anatomy of a Spell Contract shows examples of the main sections you should expect to see in an executive spell.
Finally, A Non-Exhaustive checklist includes a list of the major things to double check and how to do so.
The key thing to remember is that if any part of an executive spell seems suspicious or confusing in any way it is always better to double check before voting. Even if you are mistaken in your concerns, this allows us to add more detail to this document to prevent someone becoming confused in the same way in the future.
Finding the Contract Code¶
The link to the spell on etherscan can be found on the voting portal in the 'details' pane. Look for the 'source' property.
Once you open the spell in etherscan, click on the Contract tab.
You should see the contract code now. If you are only able to see bytecode (contract is unverified) then the executive should be viewed as potentially malicious. In this case, cry foul in chat and encourage people NOT to vote for it until the contract code is made available.
For this document, we will be looking at this executive as our source for examples: https://etherscan.io/address/0xD24FbbB4497AD32308BDa735683B55499Ddc2CaD
Anatomy of an Executive¶
The meat of each spell is located near the bottom. Most of the stuff at the top are flattened libraries included when the contract was verified.
There are two contracts of note in each executive. These are the DssSpell
contract and the SpellAction
contract.
In our example, the DssSpell
contract is named DssSpell20200221
in this example and is located at the bottom of the file. Once this spell 0xD24FbbB4497AD32308BDa735683B55499Ddc2CaD
gets enough votes to be lifted
to the hat
, the schedule()
function you see here will be called.
The spell action contract on the other hand includes the execute()
function. This function contains the MCD variable changes that are included in the executive.
DssSpell¶
Contract Variables¶
Each contract will contain a number of variables to be used in the other functions. In our example, this section looks like this:
DSPauseAbstract public pause = DSPauseAbstract(0xbE286431454714F511008713973d3B053A2d38f3); address constant public SAIMOM = 0xF2C5369cFFb8Ea6284452b0326e326DbFdCb867C; uint256 constant public NEW_FEE = 1000000002877801985002875644; // 9.5% address public action; bytes32 public tag; uint256 public eta; bytes public sig; bool public done;
Constructor¶
The constructor should always look the same:
constructor() public { sig = abi.encodeWithSignature("execute()"); action = address(new SpellAction()); bytes32 _tag; address _action = action; assembly { _tag := extcodehash(_action) } tag = _tag }
Schedule and Cast¶
The schedule
and cast
functions will usually be the same, but for now any SCD changes will be tucked into schedule
. If they deviate from the following, it will be commented:
function schedule() public { require(eta == 0, "spell-already-scheduled"); eta = add(now, DSPauseAbstract(pause).delay()); pause.plot(action, tag, sig, eta); } function cast() public { require(!done, "spell-already-cast"); done = true; pause.exec(action, tag, sig, eta); }
After schedule()
has been called and after the delay is up, anyone can call cast()
which will execute the code in the SpellAction's execute()
function (see below).
SpellAction¶
Contract Variables¶
uint256 constant RAD = 10 ** 45; address constant public PAUSE = 0xbE286431454714F511008713973d3B053A2d38f3; address constant public CHIEF = 0x9eF05f7F6deB616fd37aC3c959a2dDD25A54E4F5; address constant public OSM_MOM = 0x76416A4d5190d071bfed309861527431304aA14f; address constant public ETH_OSM = 0x81FE72B5A8d1A857d176C3E7d5Bd2679A9B85763; address constant public BAT_OSM = 0xB4eb54AF9Cc7882DF0121d26c5b97E802915ABe6; address constant public VAT = 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B; address constant public JUG = 0x19c0976f590D67707E62397C87829d896Dc0f1F1; address constant public POT = 0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7; address constant public FLAP = 0xdfE0fb1bE2a52CDBf8FB962D5701d7fd0902db9f; uint256 constant NEW_BEG = 1.02E18; // 2%
NOTE: Review "All SpellAction Contract Variables must be declared 'constant'" in the checklist section below.
Execute¶
The execute()
function will contain each MCD change that is being made in this executive spell.
function execute() external { // drip PotAbstract(POT).drip(); JugAbstract(JUG).drip("ETH-A"); JugAbstract(JUG).drip("BAT-A"); // set the global debt ceiling to 183,000,000 VatAbstract(VAT).file("Line", mul(183000000, RAD)); // set the ETH-A debt ceiling to 150,000,000 // https://vote.makerdao.com/polling-proposal/qmsm1q1hohyctsgxpbm44fomjoukf1d5g9lmpqraikmeoc VatAbstract(VAT).file("ETH-A", "line", mul(150000000, RAD)); // No Sai debt ceiling change this week. // set dsr to 8.0% // Previously ETH SF was set to 8.0%, no change this week. // DSR rate was voted to a 0% spread, so we're bringing DSR up to match. // https://vote.makerdao.com/polling-proposal/qmss9hnszwr6egq3xn6gpx4u8bz8cajja56rgtanjev1v8 PotAbstract(POT).file("dsr", 1000000002440418608258400030); // MCD Stability fee is currently at 8% and remains the same this week. // https://vote.makerdao.com/polling-proposal/qmzgvzjm4xpm4b1tk2hxhdc6p8f4zqyju38pwqieatmhel // Lower the minimum flap auction bid increase to 2% // https://vote.makerdao.com/polling-proposal/qmtsxrqavtczfsseytpypgqrz6z8zb613ikxwhqjv9ytzz FlapAbstract(FLAP).file("beg", NEW_BEG); // Increase the Pause to 24 Hours OsmAbstract(ETH_OSM).rely(OSM_MOM); OsmAbstract(BAT_OSM).rely(OSM_MOM); OsmMomAbstract(OSM_MOM).setAuthority(CHIEF); OsmMomAbstract(OSM_MOM).setOsm("ETH-A", ETH_OSM); OsmMomAbstract(OSM_MOM).setOsm("BAT-A", BAT_OSM); DSPauseAbstract(PAUSE).setDelay(60 * 60 * 24); } ``` The `execute()` function will be too large to go into detail, but each change should be well documented and one should make sure the code does what the comment says. ## A Non-exhaustive Checklist ### Verify that the contract code is visible. Using the instructions above find the contract code on Etherscan and ensure that it is visible and verified. ### Verify the Constructor, the Schedule, and the Cast functions mach templates. Each of these functions should match the templates shown above unless comments are included with the changes explaining why they do not conform to the expected templates. ### Verify that the contents of the execute function are as expected Make sure that you are seeing what you expected to see from the voting dashboard. Ensure that everything present is correct and that nothing is missing from this function. ### Verify all addresses against the changelog Any addresses you see in either of the above contracts should be verified against the most recent mainnet changelog. Currently MCD is at version `1.0.4`, and the address list can be found here: https://changelog.makerdao.com/releases/mainnet/1.0.4/contracts.json To find the latest release you can look at https://changelog.makerdao.com/ In order to verify these, you should ensure that each address in the contract matches one of the addresses in the changelog. Note that in the example spell, there were SCD changes. Those are well documented and done with SaiMomAbstract(SAIMOM).setFee(NEW_FEE);. This calls a contract that has privileged access to make a few configuration changes in SCD. Verify the address of `SAIMOM` from the contract definition above. This is trickier, but you can put the `SAIMOM` address into etherscan: https://etherscan.io/address/0xF2C5369cFFb8Ea6284452b0326e326DbFdCb867C#code ### Verify Rate Changes Rates are defined as per-second accumulation values. These values can be validated against the commented rate by using the `bc` command in a `bash` shell. Using the NEW_FEE variable in the example contract we have:
This produces 1.000000002877801985002875644
, just drop the decimal place and you can see this matches the definition of NEW_FEE
.
Validating all rate adjustments can be done the same way.
For more information on the rates module, @vamsi wrote up a great post here: https://github.com/makerdao/developerguides/blob/master/mcd/intro-rate-mechanism/intro-rate-mechanism.md
Ensure Drip is called before Rates are changed.¶
Before we change any of the rates, we must call drip()
on those respective contracts (e.g. if this is a DSR rate change, we call drip()
on the pot
or if we are changing the SF of a collateral type, we have to call drip("ILK")
on the jug
).
All SpellAction Contract Variables must be declared 'constant'¶
Because of how the SpellAction
is called, it must never have anything in contract memory. That is, all the variables that look like they are contract variables in the example contract are actually declared as constant
. This is because, at execution time, the contract's variables will be that of the DSPauseProxy
. If there are variables in this section that are anything other than constant
then it is a MAJOR bug. If this is the case and the spell might look like it’s doing one thing but is actually doing another. and needs to be called out in maker chat. DO NOT vote for a spell with non-constant variables here.