Today we’ll look into MakerDAO voting process and recover the complete state of the governance contract. There are two types of voting: governance and executive. Governance voting can be seen a simple yes/no poll. Executive voting is much more interesting and it will be the subject of this article.

How voting works

Executive voting is handled like approval voting:

Anyone can create a proposal;

Users can vote for one or more proposals;

A proposal with the most votes gets the superpowers and can modify the system parameters.

Proposal contract

A proposal comes in a form of smart contract. The winning proposal is given the authorization to tap into mom contract. A common pattern for these contracts is to use ds-spell, which performs a single action once.

The proposal to reduce the stability fee from 2.5% to 0.5% can be seen below. It shows which contract it calls, the call data and whether is has already been executed.

Reads “Decrease the stability fee to 0.5%”.

We can verify the spell using a simple script that decodes the data using interface (ABI) of the target contract:

The result would be: setFee(uint256 ray 1000000000158153903837946257)

How is this 0.5%, you may ask. To figure this out, we need to understand the units which this number is expressed in. Maker uses a few different units with various degrees of precision: wad, ray and rad.

The argument title suggests the value is in ray, a fixed point number with 27 decimals. So it expresses 1.000000000158153903837946257.

Turns out that the unit is the fee multiplier per second, so if we exponentiate it to the number of seconds in a year, we should get 1.005 or 100.5%.

1.000000000158153903837946257³¹⁵³⁶⁰⁰⁰ = 1.005

The proposal checks out! Go vote for it and come back for more fun.

Voting contract

Now let’s take a look at the voting contract, ds-chief. To vote for a proposal, one must lock MKR in this contract in return for IOU tokens. Only the locked tokens count as votes.

Users vote for sets of candidates, which are stored in a slates mapping. There are two ways to vote:

vote(bytes32 slate) vote for an existing set of candidates.

vote for an existing set of candidates. vote(address[] yays) vote by providing a set of candidates, which also saves them into a slate using etch(address[] yays) .

The contract tallies the weights and awards the leading proposal with a hat . Having the hat means having superpowers. Now anyone can cast the spell and modify the system parameters.

The voters

With that knowledge, we can find all the proposals and voters. The plan:

Find all the slates by looking at Etch(bytes32 indexed slate) events. Look up the proposals in the slates mapping. Fetch all votes and remember the most recent ones. Combine them with deposits of each voter.

First, we save unique slates from the Etch events. Then we read the values from each slate array starting with index 0 and continue until we are out of bounds.

The vote calls use non-standard events fired by ds-note. It provides a note modifier, which stores the function calls as indexed logs. There is no need to define events for each function, ds-note handles that on a lower level.

To isolate the two functions (vote slates and vote yays), we need to construct a correct log filter. If you specify a topic in a filter as a list, it works like logical OR, which is exactly what we need.

The first topic is a 4 byte function signature encoded as bytes32. The log data will have what we need to recover the votes.

The voting system is using a proxy contract, so we can’t just look up the sender in the from field of the logs. Luckily for us, ds-note stores msg.sender as the second topic.

The “slates to yays” we got earlier comes in handy for looking up candidates each user has voted for. The last step here is providing everyone with a voting weight based on their deposit.

Now we can combine everything to see the complete picture.

The full script that also tries to decode the spells can be found here.

Today was indeed a productive day, hope you learned something useful. Subscribe to crypto eli5.