aragon / optimistic-token-voting-plugin-hardhat

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Optimistic token voting - Aragon OSx

Getting started

cp .env.template .env

Add your Infura API key and then, run:

yarn
cd packages/contracts
yarn build
yarn test

Overview

The DAO contract holds all the assets and rights, while plugins are custom, opt-in pieces of logic that can perform certain actions governed by the DAO's permission database.

The DAO contract can be deployed by using Aragon's DAOFactory contract. This will deploy a new DAO with the desired plugins and settings.

How permissions work

An Aragon DAO is a set of permissions that are used to restrict who can do what and where.

A permission looks like:

  • An address who holds MY_PERMISSION_ID on a target contract where

Brand new DAO's are deployed with a ROOT_PERMISSION assigned to its creator, but the DAO will typically deployed by the DAO factory, which will install all the requested plugins and drop the ROOT permission after the set up is done.

Managing permissions is made via two functions that are called on the DAO:

function grant(address _where, address _who, bytes32 _permissionId);

function revoke(address _where, address _who, bytes32 _permissionId);

Permission Conditions

For the cases where an unrestricted permission is not derisable, a Permission Condition can be used.

Conditional permissions look like this:

  • An address who holds MY_PERMISSION_ID on a target contract where, only when the condition contract approves it

Conditional permissions are granted like this:

function grantWithCondition(
  address _where,
  address _who,
  bytes32 _permissionId,
  IPermissionCondition _condition
);

See the condition contract boilerplate. It provides the plumbing to easily restrict what the different multisig plugins can propose on the OptimisticVotingPlugin.

Learn more about OSx permissions

Permissions being used

Below are all the permissions that a PluginSetup contract may want to request:

  • EXECUTE_PERMISSION is required to make the DAO execute a set of actions
    • Only governance plugins should have this permission
  • ROOT_PERMISSION is required to make the DAO grant or revoke permissions
    • The DAO needs to be ROOT on itself (it is by default)
    • Nobody else should be ROOT on the DAO
  • UPGRADE_PLUGIN_PERMISSION is required for an address to be able to upgrade a plugin to a newer version published by the developer
    • Typically called by the DAO via proposal
    • Optionally granted to an additional address for convenience
  • PROPOSER_PERMISSION_ID is required to be able to create optimistic proposals on the governance plugin

Other DAO specific permissions:

  • UPGRADE_DAO_PERMISSION
  • SET_METADATA_PERMISSION
  • SET_TRUSTED_FORWARDER_PERMISSION
  • SET_SIGNATURE_VALIDATOR_PERMISSION
  • REGISTER_STANDARD_CALLBACK_PERMISSION

Interacting with the contracts from JS

Run yarn build && yarn typechain on the packages/contracts folder.

See packages/contracts/typechain for all the generated JS/TS wrappers to interact with the contracts.

Learn more

Encoding and decoding actions

Making calls to the DAO is straightforward, however making execute arbitrary actions requires them to be encoded, stored on chain and be approved before they can be executed.

To this end, the DAO has a struct called Action { to, value, data }, which will make the DAO call the to address, with value ether and call the given calldata (if any). To encode these functions, you can make use of the provided JS client template.

It uses the generated typechain artifacts, which contain the interfaces for the available contract methods and allow to easily encode function calls into hex strings.

See packages/js-client/src/internal/modules/encoding.ts and decoding.ts for a JS boilerplate.

The DAO's plugins

Optimistic Token Voting plugin

It's the main governance plugin for standard spaces, where all proposals can be vetoed by token holders. It is a adapted version of Aragon's TokenVoting plugin. Only addresses holding the PROPOSER_PERMISSION_ID can create proposals and they can only be executed after a majority against hasn't emerged after a given period of time.

The governance settings need to be defined when the plugin is deployed but the DAO can change them at any time. Proposal creators can cancel their own proposals before they end.

Methods

  • function initialize(IDAO _dao, VotingSettings calldata _votingSettings, address[] calldata _initialEditors)
  • function addAddresses(address[])
  • function removeAddresses(address[])
  • function createProposal(bytes calldata metadata, IDAO.Action[] calldata actions, uint256 allowFailureMap, uint64, uint64, VoteOption voteOption, bool tryEarlyExecution)
  • function cancelProposal(uint256 _proposalId)

Inherited:

  • function vote(uint256 _proposalId, VoteOption _voteOption, bool _tryEarlyExecution)
  • function execute(uint256 _proposalId)
  • function updateVotingSettings(VotingSettings calldata _votingSettings)
  • function upgradeTo(address newImplementation)
  • function upgradeToAndCall(address newImplementation, bytes data)

Getters

  • function isMember(address _account) returns (bool)
  • function isEditor(address _account) returns (bool)
  • function supportsInterface(bytes4 _interfaceId) returns (bool)

Inherited:

  • function canVote(uint256 _proposalId, address _voter, VoteOption _voteOption)
  • function getProposal(uint256 _proposalId) returns (bool open, bool executed, ProposalParameters parameters, Tally tally, IDAO.Action[] actions, uint256 allowFailureMap)
  • function getVoteOption(uint256 _proposalId, address _voter)
  • function isSupportThresholdReached(uint256 _proposalId) returns (bool)
  • function isSupportThresholdReachedEarly(uint256 _proposalId)
  • function isMinParticipationReached(uint256 _proposalId) returns (bool)
  • function canExecute(uint256 _proposalId) returns (bool)
  • function supportThreshold() returns (uint32)
  • function minParticipation() returns (uint32)
  • function minDuration() returns (uint64)
  • function minProposerVotingPower() returns (uint256)
  • function votingMode() returns (VotingMode)
  • function totalVotingPower(uint256 _blockNumber) returns (uint256)
  • function implementation() returns (address)

Events

  • event ProposalCanceled(uint256 proposalId)

Inherited:

  • event ProposalCreated(uint256 indexed proposalId, address indexed creator, uint64 startDate, uint64 endDate, bytes metadata, IDAO.Action[] actions, uint256 allowFailureMap)
  • event VoteCast(uint256 indexed proposalId, address indexed voter, VoteOption voteOption, uint256 votingPower)
  • event ProposalExecuted(uint256 indexed proposalId)
  • event VotingSettingsUpdated(VotingMode votingMode, uint32 supportThreshold, uint32 minParticipation, uint64 minDuration, uint256 minProposerVotingPower)

Permissions

  • Proposers create proposals
  • The plugin can execute on the DAO
  • The DAO can update the plugin settings
  • The DAO can upgrade the plugin

Plugin Setup contracts

So far, we have been talking about the plugin contracts. However, they need to be prepared and installed on a DAO and for this, a DAO needs to approve for it. To this end, PluginSetup contracts act as an install script in charge of preparing installations, updates and uninstallations. They always have two steps:

  1. An unprivileged step to prepare the plugin and request any privileged changes
  2. An approval step after which, editors execute an action that applies the requested installation, upgrade or uninstallation

Learn more

Installing plugins when deploying the DAO

This is taken care by the DAOFactory. The DAO creator calls daoFactory.createDao():

  • The call contains:
    • The DAO settings
    • An array with the details and the settings of the desired plugins
  • The method will deploy a new DAO and set itself as ROOT
  • It will then call prepareInstallation() on all plugins and applyInstallation() right away
  • It will finally drop ROOT_PERMISSION on itself

See a JS example of installing plugins during a DAO's deployment

Installing plugins afterwards

Plugin changes need a proposal to be passed when the DAO already exists.

  1. Calling pluginSetup.prepareInstallation()
    • A new plugin instance is deployed with the desired settings
    • The call requests a set of permissions to be applied by the DAO
  2. Editors pass a proposal to make the DAO call applyInstallation() on the PluginSetupProcessor
    • This applies the requested permissions and the plugin becomes installed

See OptimistictokenVotingPluginSetup.

Learn more about plugin setup's and preparing installations.

Deploying a DAO

The recommended way to create a DAO is by using @aragon/sdk-client. It uses the DAOFactory under the hood and it reduces the amount of low level interactions with the protocol.

See an example.

In the example, the code is making use of the existing JS client for Aragon's Token Voting plugin. They encapsulate all the Typechain and Subgraph calls and provide a high level library.

It is recommended to use the provided boilerplate on packages/js-client and adapt the existing Aragon's TokenVoting plugin to make use of the MainVotingPlugin__factory class.

Plugin deployment

  • The HardHat deployment scripts are located on the packages/contracts/deploy folder.
  • The settings about the naming, ID's and versions can be found on packages/contracts/plugin-setup-params.ts.
  • The deployments made will populate data to the packages/contracts/plugin-repo-info.json and packages/contracts/plugin-repo-info-dev.json.
  • You need to copy .env.template into .env and provide your Infura API key

Plugin metadata

Plugins need some basic metadata in order for JS clients to be able to handle installations and updates. Beyond a simple title and description, every contract's build metadata contains the ABI of the parameters that need to be encoded in order to prepare the plugin installation.

Every plugin has an initialize() methos, which acts as the constructor for UUPS upgradeable contracts. This method will be passed its DAO's address, as well as a bytes memory data parameter, with all the settings encoded.

The format of these settings is defined in the packages/contracts/src/*-build.metadata.json file. See the pluginSetup > prepareInstallation section.

DO's and DONT's

  • Never grant ROOT_PERMISSION unless you are just trying things out
  • Never uninstall all plugins, as this would brick your DAO
  • Ensure that there is at least always one plugin with EXECUTE_PERMISSION on the DAO
  • Ensure that the DAO is ROOT on itself
  • Use the _gap[] variable for upgradeable plugins, as a way to reserve storage slots for future plugin implementations
    • Decrement the _gap number for every new variable you add in the future

Plugin upgradeability

By default, only the DAO can upgrade plugins to newer versions. This requires passing a proposal. For the 3 upgradeable plugins, their plugin setup allows to pass an optional parameter to define a plugin upgrader address.

When a zero address is passed, only the DAO can call upgradeTo() and upgradeToAndCall(). When a non-zero address is passed, the desired address will be able to upgrade to whatever newer version the developer has published.

Every new version needs to be published to the plugin's repository.

Learn more about plugin upgrades.

About

License:GNU Affero General Public License v3.0


Languages

Language:TypeScript 81.5%Language:Solidity 17.2%Language:Shell 0.9%Language:JavaScript 0.3%