COMP Governance Explained

How Compound's Decentralized Governance is working under the hood

You might have heard about the COMP token launch. With a current market cap of over 350 million USD, the token has accumulated massive value. But what is the actual utility of COMP?

It's a governance token. Compound being a fully decentralized system (or at least on the way towards it), has a decentralized governance mechanism in place. The same system that was copied and used also in the SushiSwap governance.

But how does it work in detail?

What is the Compound Governance?

On a high-level, the governance is a simple set of smart contracts. They allow calling any other smart contract with any data from within the governance core contract in a decentralized way. In Compound the core contract is set as the admin of the other Compound protocol contracts. This means the core contract will be able to call all kinds of management functions like adding new markets, changing parameters and even upgrading the contracts itself.

COMP token holders are allowed to vote. Further COMP is distributed to active participants in the system, ensuring the people who use Compound are the people who get to decide on its future.

The upgrade functionality is the most powerful. Compound is using a typical proxy upgrade pattern, so by allowing governance to do upgrades, literally everything can be changed including the governance mechanism itself.

The mechanism of governance follows a strict timeline of creating proposals, voting for proposals and a two day timelock before execution:

Governance Diagram

Live Governance in Action

Since the governance system is live and ongoing for Compound, you can see the system live in action in various different places:

A look at the Governance Smart Contracts

Now let's examine the governance contracts in detail. They consist of

  • the core (GovernorAlpha.sol)
  • a timelock (Timelock.sol)
  • and the COMP token contract itself

GovernorAlpha.sol

The core contract is called GovernorAlpha. It contains all the logic for creating and executing proposals.

propose()

At the heart of the governance is the propose function.  It takes in a list of actions, each action consists of:

  • target: A target address for a call to be made.
  • value: The msg.value to be passed to the call.
  • signature: The function signature for the call, e.g., 'transfer(address, amount)'
  • calldata: The data (parameters) to be passed to the function call.
function propose(
    address[] memory targets,
    uint[] memory values,
    string[] memory signatures,
    bytes[] memory calldatas,
    string memory description
) returns (uint)

Each COMP token holder with at least 100,000 tokens, i.e., ≥ 1% of supply, is allowed to add a new proposal, but they can only have one active proposal at any given time. Lastly a description string must be passed that contains a human-readable explanation the proposal.

Referendum

Referendum: After a proposal has been added, a referendum begins. Addresses that held voting weight, at the start of the proposal, invoked through the compToken.getPriorVotes function, can submit their votes, either 'Yes' or 'No', during a 3 day voting period.

castVote()

Cast a vote on a proposal. The account's voting weight is determined by the number of votes the account had delegated to it at the time the proposal state became active.

The same method exists also using EIP-712 signatures. The idea is the same to the ERC-20 Permit that we previously discussed. If you haven't seen it yet, you can read it here.

function castVote(
    uint proposalId,
    bool support
)
function castVoteBySig(
    uint proposalId,
    bool support,
    uint8 v,
    bytes32 r,
    bytes32 s
)
Governance Meme

queue()

A referendum is successful if

  • a majority has voted for the proposal
  • and at least 400,000 votes are cast, i.e., ≥ 4% of the supply.


After a proposal has succeeded, any address can call the queue method to move the proposal into the Timelock queue.

function queue(
    uint proposalId
)

execute()

Once the Timelock delay has passed, anyone may call the execute method. This will now run each action part of the accepted proposal sequentially. For any action (target, value, signature, calldata), we execute the transaction as:

bytes memory callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
(bool success,) = target.call.value(value)(callData);

Remember that the calldata begins with the function selector which is calculated as the first four bytes of the hash of the signature string. So here we simply prepend the result to our existing calldata and then call the target address with our given data.

function execute(
    uint proposalId
)

cancel()

In rare cases an accepted proposal can still be cancelled. Currently there still exists a guardian address which has the power to cancel any proposal. It is currently held by the Compound Labs, Inc. itself. In the future this guardian address may be removed.

A proposal can also be cancelled if the original proposer looses the required amount of COMP tokens to create proposals after it was added. This is useful to prevent someone from doing a malicious proposal and immediately selling off all his COMP before the value of COMP might drop due to the malicious proposal being accepted.

function cancel(
    uint proposalId
)

Timelock.sol

The Timelock contract is a wrapper around the execution of proposals. It consists of

  • delay: How many days one has to wait between a proposal being accepted until it can be executed. Can be changed by the governance to anywhere from two to 30 days. Currently set as two days.
  • grace period: The time after which an accepted proposal cannot be executed anymore, constantly set to 14 days.

CompToken.sol

Besides being a regular ERC-20 token, the COMP contract includes relevant governance methods.

delegate()

Users can delegate their COMP to one other address. That address will be able to cast votes with the added voting power of their own COMP balances + all COMP balances from addresses that set that address as delegatee.

Just as before there also exists a delegateBySig to be used with EIP-712 signatures.

function delegate(
    address delegatee
)

getPriorVotes()

To receive the voting power at a given block number, we can call getPriorVotes to retrieve the value. Obviously this works only for past block numbers.

function getPriorVotes(
    address account,
    uint blockNumber
) returns (uint96)

Compound Autonomous Proposals (CAPs)

Since the requirement of 100,000 COMP tokens to create a proposal is a very high barrier, after all that's over 10 million USD at the current COMP prices, a new mechanism called Compound Autonomous Proposals (CAPs) was introduced. This will allow anyone with 100 COMP or more to propose a proposal. Any CAP that gets 100,000 votes can be added as regular proposal.

You can see and vote for CAPs under https://app.compound.finance/vote.

JS SDK and API

You can also find useful tools around governance from Compound directly via JS SDK and API:

Example Governance Request

Lets take a look at the recent 'Add UNI support' proposal as example. You can find the creation transaction here.

address[] targets

0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B
0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B
0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B
0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B
0x35A18000230DA775CAc24873d00Ff85BccdeD550
0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B

string[] signatures

_setPriceOracle(address)
_supportMarket(address)
_addCompMarkets(address[])
_setCollateralFactor(address,uint256)
_setReserveFactor(uint256)
_setMarketBorrowCaps(address[],uint256[])

bytes[] calldatas

0x000000000000000000000000922018674c12a7f0d394ebeef9b58f186cde13c1
0x00000000000000000000000035a18000230da775cac24873d00ff85bccded550
0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000035a18000230da775cac24873d00ff85bccded550
0x00000000000000000000000035a18000230da775cac24873d00ff85bccded5500000000000000000000000000000000000000000000000000853a0d2313c0000
0x00000000000000000000000000000000000000000000000002c68af0bb140000
0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000035a18000230da775cac24873d00ff85bccded550000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000001a784379d99db42000000

string description

Add UNI Support This proposal adds [Uniswap](https://etherscan.io/token/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984) as a supported asset
and implements an updated [Open Price Feed](https://compound.finance/docs/prices) view contract that supports cUNI.

[...]

Further, the values (msg.value) were all set to 0. Once the proposal was accepted and later executed, all six actions were executed. You can use a tool like Ethtx.info to see the transaction in detail here.

Execute Uniswap Proposal Transaction

The Future of Decentralized Governance

That was the Compound Governance in a nutshell. It is a simple design, but still pretty powerful. Many projects have adopted the same mechanism or were at least inspired by it. We will see where this develops in the future, but it's an important step towards more decentralization.


Markus Waas

Solidity Developer

More great blog posts from Markus Waas

© 2024 Solidity Dev Studio. All rights reserved.

This website is powered by Scrivito, the next generation React CMS.