What is a DAO?

A DAO - Decentralized Autonomous Organization is an organization model in which decentralization and autonomy are emphasized as key factors of the system. This concept was first mentioned in the blockchain community by Vitalik Buterin as Decentralized Autonomous Corporation in 2013[1], and later DAO. The idea of DAO is to limit and gradually remove the influences of central authorities in governing a decentralized organization, which usually refers to projects on the public blockchain like funds, financial solutions, social groups, and in the end, the future of the organization lies in the hands of its community.

This model's first and widely-known implementation is The DAO, an open-sourced crowdfunding project. Its idea of transparent and provably fair public funds brought extreme excitement to the community. Not long after that, it became an infamous example of smart contract exploits that cost investors $60 million dollars at that time[2]. However, the time has proved that one bad incident can not spoil a good idea. Nowadays, DAO has become a fundamental concept in Web3 and DeFi platforms. Almost every DeFi projects find similarity in its vision and mission with the DAO concept - a decentralized autonomous organization that belongs to its community. And DAO as a DeFi governance system has gradually become a gold standard.

Top DeFi projects adopted the DAO model. Source: CoinMarketCap
Top DeFi projects adopted the DAO model. Source: CoinMarketCap

This article will focus on the foundation of smart contract system behind governance modules of DeFi projects.

Governance modules in DeFi

A governance module in a DeFi project that follows the DAO concept at its heart is a smart contract-based system implementing a voting mechanism for the organization's decision-making process and automating the execution that applies changes to the protocol.

A common design of the governance process is the "Improvement Proposal." First, ideas are discussed among the community before shaping into specific changes that aim to improve the protocol. Then, the final version of the idea is proposed as an Improvement Proposal for the community to vote on. The voting power of a user usually depends on their possession of the project's governance token. After a period, if the proposal successfully passes the voting period, its "body," which are transactions to execute on the blockchain, will be enforced by the smart contract system.

Example of an Improvement Proposal (Compound). Source: Compound.finance
Example of an Improvement Proposal (Compound). Source: Compound.finance

There are two types of governance processes: off-chain and on-chain with the main difference is that off-chain processes do not use smart contracts, so it is easier to implement but harder to achieve transparency, decentralization, and execution can not be enforced by smart contracts. Snapshot is a popular platform that provides off-chain voting solutions for many projects' governance modules. For more information, please refer to the official document[6]. n

Despite all the advantages of an on-chain governance process, it requires specialized developers to build a trusted platform for a project, and there are still many risks to manage. While designing an on-chain governance module, there are multiple questions to answer, various paths to take, and security risks and best practices that have to be kept in mind.

Therefore, to explore how a governance module work, let's take a look into the architectures of the forefront and battle-tested solution in the market.

The forefront and battle-tested

Compound

Compound Protocol is a DeFi lending protocol that allows users to earn interest on their cryptocurrencies by depositing them into one of several pools supported by the platform.

This governance module is widely adopted by other protocols in the DeFi market, like Uniswap.

Compound's governance module smart contract includes three main components: Governance Bravo, Timelock, and Governed contracts.

Compound on-chain governance module's architecture. Source: Compound.finance
Compound on-chain governance module's architecture. Source: Compound.finance

This architecture can be explained as follows:

  • COMP Holders: All COMP holders can cast their vote, and holders with a significant balance can create proposals.
  • GovernorBravo: Core implementation of proposal and voting processes & mechanism, follows the proxy pattern for upgradability with Delegator and Delegate.
  • Delegate contract: Implement the logic.
  • Delegator contract: Storage and delegate call to the Delegate contract.
  • Timelock: Queue transactions after approval by the voting process in delay for execution.
  • Governed contracts: Smart contracts of Compound Protocol, owned by the governance module.
  • Administration: The loop dependency between Governor Bravo and Timelock contracts is what makes the system fully autonomous, with minimal influence from

For a deeper understanding of the process, let's dive into Compound's source code[5] to see how a proposal is represented on the blockchain.

struct Proposal {
   uint id;                                /// Unique id for looking up a proposal
   address proposer;                       /// Creator of the proposal
   uint eta;                               /// The "available for execution timestamp" after the proposal succeeded
   address[] targets;                      /// Ordered list of target addresses for calls to be made
   uint[] values;                          /// Ordered list of ether values to be passed to the calls
   string[] signatures;                    /// Ordered list of function signatures to be called
   bytes[] calldatas;                      /// Ordered list of calldata to be passed to the calls
   uint startBlock;                        /// Block number at which voting begins and voting power is snapshot
   uint endBlock;                          /// Block number at which voting ends and result is final
   uint forVotes;                          /// Current number of favor votes for this proposal
   uint againstVotes;                      /// Current number of oppose votes for this proposal
   uint abstainVotes;                      /// Current number of abstain votes for this proposal
   bool canceled;                          /// Whether the proposal has been canceled
   bool executed;                          /// Whether the proposal has been executed
   mapping (address => Receipt) receipts;  /// Receipts of votes cast by users
}
 
struct Receipt {
   bool hasVoted;                          /// Whether or not a vote has been cast
   uint8 support;                          /// Voted option - For/Against/Abstain
   uint96 votes;                           /// Voting power of the cast vote
}

Each proposal consists of a sequence of actions to perform as transactions, with essential data:

  • Target: Address of the contract to interact (call a function).
  • Value: Amount of ether to send for the transaction if needed.
  • Signature: Function signature to be called.
  • Calldatas: Encoded data prepared for the function call.

These transactions will change the state of the Governed contracts and need to be in correct order to achieve the expected result.

Also, each proposal has a description emitted in the creation of dApp's UI.

/// @notice An event emitted when a new proposal is created
   event ProposalCreated(uint id, address proposer, address[] targets, uint[] values, string[] signatures, bytes[] calldatas, uint startBlock, uint endBlock, string description);
 
   /**
    * @notice An event emitted when a vote has been cast on a proposal
    * @param voter The address which casted a vote
    * @param proposalId The proposal id which was voted on
    * @param support Support value for the vote. 0=against, 1=for, 2=abstain
    * @param votes Number of votes which were cast by the voter
    * @param reason The reason given for the vote by the voter
    */
   event VoteCast(address indexed voter, uint proposalId, uint8 support, uint votes, string reason);
 
   /// @notice An event emitted when a proposal has been canceled
   event ProposalCanceled(uint id);
 
   /// @notice An event emitted when a proposal has been queued in the Timelock
   event ProposalQueued(uint id, uint eta);
 
   /// @notice An event emitted when a proposal has been executed in the Timelock
   event ProposalExecuted(uint id);
 
   /**
     * @notice Gets the state of a proposal
     * @param proposalId The id of the proposal
     * @return Proposal state
     */
   function state(uint proposalId) external view returns (ProposalState);
 
   /**
     * @notice Function used to propose a new proposal. Sender must have delegates above the proposal threshold
     * @param targets Target addresses for proposal calls
     * @param values Eth values for proposal calls
     * @param signatures Function signatures for proposal calls
     * @param calldatas Calldatas for proposal calls
     * @param description String description of the proposal
     * @return Proposal id of new proposal
     */
   function propose(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description) external returns (uint);
 
   /**
     * @notice Queues a proposal of state succeeded
     * @param proposalId The id of the proposal to queue
     */
   function queue(uint proposalId) external;
 
   /**
     * @notice Executes a queued proposal if eta has passed
     * @param proposalId The id of the proposal to execute
     */
   function execute(uint proposalId) external payable;
 
   /**
     * @notice Cancels a proposal only if sender is the proposer, or proposer delegates dropped below proposal threshold
     * @param proposalId The id of the proposal to cancel
     */
   function cancel(uint proposalId) external;
 
   /**
     * @notice Cast a vote for a proposal
     * @param proposalId The id of the proposal to vote on
     * @param support The support value for the vote. 0=against, 1=for, 2=abstain
     */
   function castVote(uint proposalId, uint8 support) external;
 
   /**
     * @notice Cast a vote for a proposal with a reason
     * @param proposalId The id of the proposal to vote on
     * @param support The support value for the vote. 0=against, 1=for, 2=abstain
     * @param reason The reason given for the vote by the voter
     */
   function castVoteWithReason(uint proposalId, uint8 support, string calldata reason) external;
 
   /**
     * @notice Cast a vote for a proposal by signature
     * @dev External function that accepts EIP-712 signatures for voting on proposals.
     */
   function castVoteBySig(uint proposalId, uint8 support, uint8 v, bytes32 r, bytes32 s) external;

There are six main ways to interact with a proposal, and the contract will emit an event for any of these actions.

  • state(): get the state of an existed proposal.
  • propose(): create a proposal
  • castVote(): vote for, against or abstain from a proposal
  • queue(): queue a proposal if succeeded
  • execute(): execute a proposal after the delay period
  • cancel(): cancel a proposal in emergencies, only for guardian

The data about the state of a proposal is fully transparent on the blockchain. Anyone can examine all changes that occurred to the protocol's contracts. For more information, please refer to Compound official documentation[4].

OpenZeppelin

From the collaboration with Compound, OpenZeppelin, an open-sourced project for smart contract development, introduced a Governor module with full compatibility with Compound's governance module and Abstraction for other governance options.

OpenZepplin on-chain governance module's architecture. Source: openzeppelin.com
OpenZepplin on-chain governance module's architecture. Source: openzeppelin.com

OpenZeppelin's implementation of the governance inherits the idea for the proposal and voting processes from Compound, with some significant differences:

  • Modularization & Abstraction: bring flexibility for projects who want to adapt this module instead of forking one big architecture, with modules with separate functions like Quorum, ProposalThreshold, Votes,.etc.
  • ERC20 & ERC721 compatible: implement more general governance tokens options.
  • Security & Gas optimization: design with minimal use of storage for more gas efficiency and follow best practices for security, combined with OpenZeppelin other modules and libraries.
  • Upgradability: replace [email protected] proxy pattern with OpenZeppelin standard upgradable contract patterns.

For more information, please refer to the official documents and the source code repository.

Conclusion

DAO and governance module in DeFi protocols has proved to be interesting ideas for the future of a fully decentralized financial platform. Furthermore, multiple projects are working to improve and implement secured and well-designed mechanisms and processes, so the future of on-chain governance is exciting to look forward to.

Although on-chain governance is necessary, it also contains dangerous risks to the protocols and their users, which can come from smart contract exploits or malicious actions taking advantage of the governance processes. We will analyze some examples in the next part of the series.

References

[1] Bootstrapping a decentralized autonomous corporation series, bitcoinmagazine.com, accessed July 2nd, 2022.

[2] Lines of code worth 60 million dollars in The DAO, techfi.tech, accessed 3rd, 2022.

[3] Top DAO Tokens by Market Capitalization, coinmarketcap.com, accessed 3rd, 2022.

[4] Compound official documentation, Compound.finance, accessed 4th, 2022.

[5] Compound smart contracts, github.com/compound-finance, accessed 4th, 2022.

[6] Snapshot.org official documentation, snapshot.org, accessed 3rd, 2022.

[7] OpenZepplin Governance official documentation, openzepplin.com, accessed 4th, 2022.

[8] OpenZepplin Governance smart contracts, github.com/OpenZeppelin, accessed 4th, 2022.