Skip to content

aragon/taiko-contracts

Repository files navigation

Taiko DAO contracts

This reposity contains the codebase of the Taiko DAO, along with its 3 plugins and helper contracts.

The DAO contract is an Aragon DAO, on which an Optimistic Token Voting plugin has the permission to execute proposals. Proposals on this plugin can only be created by two distinct multisig plugins, belonging to Taiko's Security Council.

Taiko DAO Overview

The Security Council operates a standard multisig plugin and an emergency variant. The standard Multisig is designed to be the place where DAO proposals start their journey. Any signer can submit a new proposal to the Security Council. After a certain approval ratio is reached, the proposal will be forwarded to the Optimistic voting plugin, where the community will need to ratify it.

The Emergency Multisig, is meant to only be used under exceptional circumstances, i.e. when a critical vulnerability needs to be addressed immediately. Any signer can submit proposals as well, but these proposals will need to be approved by a super majority before they can be executed directly on the DAO.

Another key difference is that the Emergency Multisig is designed such in a way that the human readable description and the actions are private to the signers until the proposal is finally executed.

Learn more about Aragon OSx.

See Deploying the DAO below and check out the contract deployments.

Optimistic Token Voting plugin

This plugin is an adapted version of Aragon's Optimistic Token Voting plugin.

Only addresses that have been granted PROPOSER_PERMISSION_ID on the plugin can create proposals. These adresses belong to the two multisig's governed by the Security Council.

Proposals can only be executed when the veto threshold hasn't been reached after a given period of time.

The governance settings need to be defined when the plugin is installed but the DAO can update them at any time.

Permissions

  • Only proposers can create proposals on the plugin
  • The plugin can execute actions on the DAO
  • The DAO can update the plugin settings
  • The DAO can upgrade the plugin

Multisig (standard flow)

It allows the Security Council members to create and approve proposals. After a certain minimum of approvals is met, proposals can be relayed to the Optimistic Token Voting plugin only.

The ability to relay proposals to the Optimistic Token Voting plugin is restricted by a permission condition, which ensures that a minimum veto period is defined as part of the parameters.

Standard proposal flow

Permissions

  • Only members can create proposals
  • Only members can approve
  • The plugin can only create proposals on the Optimistic Token Voting plugin provided that the duration is equal or greater than the minimum defined
  • The DAO can update the plugin settings

Emergency Multisig

Like before, this plugin allows Security Council members to create and approve proposals. If a super majority approves, proposals can be relayed to the Optimistic Token Voting plugin with a delay period of potentially 0. This is, being executed immediately.

The address list of this plugin is taken from the standard Multisig plugin. Any changes on the former will effect both plugin instances.

There are two key differences with the standard Multisig:

  1. The proposal's metadata and the actions to execute are encrypted, only the Security Council members have the means to decrypt them
  2. When the proposal is executed, the metadata and the actions become publicly visible on the Optimistic Token Voting plugin. There is an integrity check to prevent any changes to the originally approved content.

Emergency proposal flow

Permissions

The Emergency Multisig settings are the same as for the standard Multisig.

  • Only members can create proposals
  • Only members can approve
  • The plugin can only create proposals on the Optimistic Token Voting plugin provided that the duration is equal or greater than the minimum defined
  • The DAO can update the plugin settings

Public Key Registry

This is a helper contract that allows Security Council members to register the public key of their deterministic ephemeral wallet. The available public keys will be used to encrypt the proposal metadata and actions. Refer to the UI repository for the encryption details.

NOTE: A published public key cannot be changed once published.

  • A wallet can only generate one derived key pair.
  • Public key registration is an automated process. No human error should be possible.
  • Altering an encryption key is a strange edge case of which the rest of signers should be aware of.

Delegation Wall

This is a very simple contract that serves the purpose of storing the IPFS URI's pointing to the delegation profile posted by all candidates. Profiles can be updated by the owner and read by everyone.

Installing plugins to the DAO

Installing the initial set of plugins on the DAO

This is taken care by the TaikoDAOFactory contract. It is invoked by scripts/Deploy.s.sol and it creates a holistic, immutable DAO deployment, given some settings. To create a new DAO with new settings, a new factory needs to be deployed.

Installing plugins on the existing DAO

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

There are two steps, a permissionless preparation and a privileged application.

  1. Calling pluginSetupProcessor.prepareInstallation()
    • A new plugin instance is deployed with the desired settings
    • The call stores the request of a set of permissions
  2. A proposal is passed 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.

OSx protocol overview

OSx DAO's are designed to hold all the assets and rights by themselves. On the other hand, plugins are custom opt-in pieces of logic that can implement any type of governance. They are meant to eventually make the DAO execute a certain set of actions.

The whole ecosystem is governed by the DAO's permission database, which is used to restrict actions to only the role holding the appropriate permission.

How permissions work

An Aragon DAO is a set of permissions that are used to define 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 is required to be able to create optimistic proposals on the governance plugin
  • UPDATE_MULTISIG_SETTINGS_PERMISSION_ID is used by the DAO to update the settings of a multisig plugin, if the community approves
  • UPDATE_OPTIMISTIC_GOVERNANCE_SETTINGS_PERMISSION_ID is used by the DAO to update the settings of the optimistic voting plugin, if the community approves

Other DAO specific permissions:

  • UPGRADE_DAO_PERMISSION
  • SET_METADATA_PERMISSION
  • SET_TRUSTED_FORWARDER_PERMISSION
  • SET_SIGNATURE_VALIDATOR_PERMISSION
  • REGISTER_STANDARD_CALLBACK_PERMISSION

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). Such calldata is an ABI encoded array of bytes with the function to call and the parameters it needs.

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 (slot) you add in the future

Plugin upgradeability

By default, only the DAO can upgrade plugins to newer versions. This requires passing a proposal.

Learn more about plugin upgrades

Development

To work with the repository you need to install Foundry on your operating system.

Build

$ forge build

Test

$ forge test

Formatting the code

$ forge fmt

Gas Snapshots

$ forge snapshot

Run a local test EVM

$ anvil

Cast

$ cast <subcommand>

Deployment

Deploying the DAO

  1. Edit script/multisig-members.json with the list of addresses to set as signers
  2. Run forge build && forge test
  3. Copy .env.example into .env and define the settings
  4. Run source .env to load them
  5. Set the RPC URL and run the deployment script
RPC_URL="https://eth-holesky.g.alchemy.com/v2/${ALCHEMY_API_KEY}"
forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --broadcast --verify

If you get the error Failed to get EIP-1559 fees, add --legacy to the last command:

forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --broadcast --verify --legacy

If a some contracts fail to verify on Etherscan, retry with this command:

forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --verify --legacy --private-key "$DEPLOYMENT_PRIVATE_KEY" --resume

Testing

See the test tree file for a visual representation of the implemented tests.

Tests can be described using yaml files. They will be automatically transformed into solidity test files with bulloak.

Create a file with .t.yaml extension within the test folder and describe a hierarchy of test cases:

# MyTest.t.yaml

MultisigTest:
- given: proposal exists
  comment: Comment here
  and: 
  - given: proposal is in the last stage
    and:

    - when: proposal can advance
      then:
      - it: Should return true

    - when: proposal cannot advance
      then:
      - it: Should return false

  - when: proposal is not in the last stage
    then:
    - it: should do A
      comment: This is an important remark
    - it: should do B
    - it: should do C

- when: proposal doesn't exist
  comment: Testing edge cases here
  then:
  - it: should revert

Then use make to automatically sync the described branches into solidity test files.

$ make
Available targets:
Available targets:
- make all        Builds all tree files and updates the test tree markdown
- make sync       Scaffold or sync tree files into solidity tests
- make check      Checks if solidity files are out of sync
- make markdown   Generates a markdown file with the test definitions rendered as a tree
- make init       Check the dependencies and prompt to install if needed
- make clean      Clean the intermediary tree files

$ make sync

The final output will look like a human readable tree:

# MyTest.tree

EmergencyMultisigTest
├── Given proposal exists // Comment here
│   ├── Given proposal is in the last stage
│   │   ├── When proposal can advance
│   │   │   └── It Should return true
│   │   └── When proposal cannot advance
│   │       └── It Should return false
│   └── When proposal is not in the last stage
│       ├── It should do A // Careful here
│       ├── It should do B
│       └── It should do C
└── When proposal doesn't exist // Testing edge cases here
    └── It should revert