toucan-contracts
v0.0.1-alpha
Published
Crosschain voting and governance contracts for Aragon OSx
Downloads
5
Maintainers
Readme
ToucanVoting
Introduction
We envision a world where governance is both accessible to all, and natively multichain. Where users need not tradeoff between economic security, and meaningful participation onchain. ToucanVoting lets your voters fly free - voting for low or no-cost across multiple chains, while still maintaining complete, trustless control of the entire system.
Specifically, ToucanVoting is a cross-chain governance system for DAOs in the Ethereum Ecosystem built on Aragon OSx. It leverages the capabilities of LayerZero v2 to facilitate low-cost and trustless governance of DAOs across multiple chains.
Table of Contents
- Introduction
- Features and Benefits
- Installing on your DAO
- Building Locally
- Deep dive
- Workflows
- Restrictions and Limitations
Features and Benefits
Flexible
Any ERC20 or ERC20Votes token can be made omnichain, allowing governance across multiple chains and enabling tokens to be bridged to any chain supported by the broker. Any number of chains can be used in governance, with over 70 currently supported.
Low Cost
Voters can move to chains with extremely low fees while governing mainnet DAOs. The only requirement is users bridge their tokens to the desired chain.
Votes are aggregated before relaying back to the main chain, allowing for thousands of votes to be compressed to a single transaction posted back to the main chain.
Modular
Built on Aragon OSx, Toucan is simply another plugin for your DAO. It can be combined with other governance primitives, upgraded, extended or removed as your governance needs change.
Trustless
All Components across all chains remain fully controlled by the original DAO via the battle-tested Aragon permissions system, ensuring trustless and decentralized governance.
Installing on your DAO
A fully automated deployment is in the works! In the meantime please contact the Aragon team on our discord and we will get you setup with crosschain voting.
Building Locally
To set up the ToucanVoting project, you will need Foundry. Additionally, ensure you have lcov
and genhtml
installed for coverage reporting. If you have make
installed, you can use commands in the Makefile
Prerequisites
- Foundry: Follow the installation instructions at Foundry.
- Make: Ensure you have
make
installed. Most Unix-like systems come withmake
pre-installed. On Debian-based systems, you can install it usingapt
:sudo apt install build-essential
- lcov: Install
lcov
using your package manager. On Debian-based systems:sudo apt install lcov
- genhtml: This is typically included with
lcov
. Ensure it is available in your system.
Installation
To set up the repository, follow these steps:
Initialize the repository and run the test suite with coverage report generated in report/.
make install
You can also manually do this:
forge install # install deps
forge build # compile contracts
forge test # run the test suite
Note: Due to issues with LayerZero's test helpers, it's not possible to run
forge coverage
directly, please usemake coverage-report
instead.
Deep dive
The following is a technical deep dive into the ToucanVoting architecture. It is designed for developers seeking to have a full understanding of the system.
Key Terms
DAO: The Aragon OSx DAO that governs all other components on that particular chain. By 'governs' we mean it has full control over all permissions for that chain, and has full control.
Action: An onchain transaction that will be executed by the DAO. This could be, for example, to send tokens to a contract, to install a new governance plugin, or to enable a new voting chain.
Proposal: proposals contain actions. Token holders vote on proposals and, if they pass, the actions will be executed by the DAO.
GovernanceERC20: an ERC20 token that implements the IVotes interface. It allows for voting with delegation.
Delegation: A user allows another ethereum address to vote on their behalf. In the context of toucan voting, all tokens bridged to other voting chains delegate their voting power to the bridging contracts, so that their votes can be relayed back to the execution chain.
Execution Chain: the primary chain on which the DAO operates. Proposals are created on this chain. There is only one execution chain in the system.
Voting Chain: secondary chain(s) supported by the main DAO. A sub-DAO will be deployed on each of these chains, and proposals can execute cross-chain transactions from the execution chain to make changes on the voting chain(s). There can be many voting chains.
Proposal ID: a numerical ID for an execution chain proposal. Typically is a counter starting at 0 and incrementing by 1 for each new proposal. Used on execution chains.
Proposal Reference: Used on voting chains and in the ToucanReceiver. It is a 256 bit encoding of some basic proposal data which allows users to vote against proposals on voting chains without said proposal needing to be bridged. See the Proposal Reference section for details.
Overview
Permissions
This repo uses Aragon OSx 1.3.0 contracts and solidity 0.8.17.
You can see examples of these in the execution chain setup test and voting chain setup test
Execution Chain
| Component | Permission Granted | Target Component | Chain | | ------------------------ | --------------------- | ------------------------ | --------------- | | ToucanVoting.sol | EXECUTE_PERMISSION | DAO.sol | Execution Chain | | DAO.sol | OAPP_ADMINISTRATOR | ToucanReceiver.sol | Execution Chain | | DAO.sol | OAPP_ADMINISTRATOR | GovernanceOFTAdapter.sol | Execution Chain | | DAO.sol | OAPP_ADMINISTRATOR | ActionRelay.sol | Execution Chain | | DAO.sol | XCHAIN_ACTION_RELAYER | ActionRelay.sol | Execution Chain | | DAO.sol | MINT_PERMISSION | GovernanceERC20.sol | Execution Chain | | GovernanceOFTAdapter.sol | Delegates to | ToucanReceiver.sol | Execution Chain |
Voting Chain
| Component | Permission Granted | Target Component | Chain | | --------------- | ------------------ | ------------------------------ | ------------ | | DAO.sol | OAPP_ADMINISTRATOR | AdminXChain.sol | Voting Chain | | DAO.sol | OAPP_ADMINISTRATOR | OFTTokenBridge.sol | Voting Chain | | DAO.sol | OAPP_ADMINISTRATOR | OFTTokenBridge.sol | Voting Chain | | AdminXChain.sol | EXECUTE_PERMISSION | ToucanRelay.sol | Voting Chain | | OFTTokenBridge | MINT_PERMISSION | GovernanceERC20VotingChain.sol | Voting Chain | | OFTTokenBridge | BURN_PERMISSION | GovernanceERC20VotingChain.sol | Voting Chain |
Note that, as part of installation, an Aragon Admin plugin is configured during setup and later removed.
Layer Zero Peers
OApps require a peer to be set by the OAPP_ADMINISTRATOR
(The DAO), calling setPeer
. If the peers are not set, crosschain messages will not be accepted. The peers are as follows:
| Component | Peer Component | | ------------------------ | ------------------------ | | ToucanVoting.sol | ToucanReceiver.sol | | ToucanReceiver.sol | ToucanVoting.sol | | GovernanceOFTAdapter.sol | OFTTokenBridge.sol | | OFTTokenBridge.sol | GovernanceOFTAdapter.sol | | ActionRelay.sol | AdminXChain.sol | | AdminXChain.sol | ActionRelay.sol |
Proposal ID vs Proposal Ref
In ToucanVoting.sol, ProposalId
are auto-incrementing counters that increase with each new proposal created on the execution chain. We explicitly designed the voting plugin to be completely agnostic of cross-chain operations, so that the DAO is free to replace it with other governance plugins and not worry about cross chain.
Additionaly, we wanted to avoid having DAOs bridge propsosals to all voting chains: this takes time, incurs gas fees and opens up other difficulties involved in bridging.
The solution is the ProposalReference
: a unique identifier for proposals on chains other than the execution chain.
This ProposalReference
allows users to vote on without requiring the bridging of proposals. The ProposalRefEncoder
library provides a solution by encoding salient information about a proposal into a single 256-bit value.
Encoding Scheme
A proposal reference is composed of the following fields:
- Proposal ID (32 bits): Unique identifier for the proposal.
- Plugin Address (128 bits): First 128 bits of the plugin address on the execution chain.
- Start Timestamp (32 bits): Timestamp when the proposal voting starts.
- End Timestamp (32 bits): Timestamp when the proposal voting ends.
- Block Snapshot Timestamp (32 bits): Timestamp to check voting power at.
Usage
For more details, refer to the ProposalRefEncoder.sol.
| Component | Identifier Type | | -------------- | ------------------------------------------------------- | | ToucanRelay | Proposal References | | ToucanReceiver | Both* | | ToucanVoting | Proposal IDs | | DAO | Proposal IDs |
*The receiver listens for proposal references, validates them and then stores data against proposal IDs. You can generate proposal references from any 32bit proposal ID using the recevier's getProposalRef
function.
Workflows
A high level workflow can be seen below for a single voting chain. Click the image for an interactive viewer.
Stages in crosschain voting
Bridge Tokens:
- Users must bridge from the execution chain to the voting chain prior to proposal creation using
GovernanceOFTAdapter.sol
. This locks tokens inGovAdapter
and sends a mint transaction on the voting chain (OFTTokenBridge.sol
calls mint onGovernanceERC20VotingChain.sol
). - Users can bridge back by burning tokens on the voting chain, this will unlock the tokens on the
GovernanceOFTAdapter.sol
.
- Users must bridge from the execution chain to the voting chain prior to proposal creation using
Create Proposal:
- A holder of a sufficient quantity (set by DAO) of governance tokens creates a proposal on
ToucanVoting.sol
.
- A holder of a sufficient quantity (set by DAO) of governance tokens creates a proposal on
Voting Phase:
- When
block.timestamp
>startTimestamp
of the proposal, users who had voting power when the proposal was created can vote. - Users can vote directly on the execution chain:
- Fetch a proposal reference from
ToucanReceiver.sol
on the execution chain. - Vote on
ToucanRelay.sol
using the proposal reference. - Votes are aggregated until dispatched.
- Fetch a proposal reference from
- When
Dispatch Votes:
- Anyone can call
dispatchVotes
onToucanRelay
, which sends the votes toToucanReceiver.sol
.
- Anyone can call
Receive Votes:
ToucanReceiver.sol:lzReceive
is called, which looks up the proposal ID and validates the proposal reference. If it's valid, it updates its vote on theToucanVoting.sol
plugin with the newly received votes.
Execute Proposal:
- After the proposal finishes, execute the proposal on
ToucanVoting.sol
, which callsdao.execute
.
- After the proposal finishes, execute the proposal on
Crosschain Governance
Manage Other Chains:
- Optionally, if the DAO needs to manage another chain, encode an action into the proposal for the DAO to call the
ActionRelay.sol
contract. ActionRelay.sol
takes the action(s) and sends them toAdminXChain.sol
, which has EXECUTE permission on the voting chain DAO.
- Optionally, if the DAO needs to manage another chain, encode an action into the proposal for the DAO to call the
Admin Control:
- The voting chain DAO is the admin over all components on the voting chain, so it can execute any required actions.
This workflow ensures that the main DAO on the execution chain remains in control of the entire system, even when voting and actions are distributed across multiple chains.
Restrictions and Limitations
Limitations
Single Execution Chain Support:
- Only one execution chain is supported for creating and executing proposals.
- Note: Toucan Voting allows the execution chain DAO to bridge actions to other chains. So while execution is initiated from only one, primary chain, the DAO can control any other chain via cross-chain actions.
Bridging Tokens from Multiple Chains:
- If a user already has tokens on multiple chains, they will need to bridge them back to the execution chain and then bridge via the canonical chain. This ensures all tokens are correctly registered for governance.
- Similarly, you cannot mint tokens on multiple chains. Minting of new tokens must be done on the execution chain. In the future we plan to add airdrop functionality.
Proposal Creation Limitation:
- Currently, the only action supported for token holders on the voting chains is voting, not proposal creation. Users on voting chains must bridge to the execution chain to create proposals.
- This is something we plan to address in the future.
Token Wrapping Requirements:
- On the execution chain, if you have an ERC20 token, it must be wrapped to add governance functionality.
- If you have a voting token, it must be an ERC20Votes token, or it will be wrapped to a compatible voting token.
Delegation Not Preserved:
- Delegation on the execution chain is not preserved on the voting chain. Users will need to re-delegate after bridging.
- Preserving delegation history is a feature we are considering in future updates.
Manual Bridging Required:
- Users must manually bridge their tokens from the execution chain. Automating this process could be a feature in the future.
Dispatching Votes:
- Someone must call
dispatchVotes
for aggregate votes to be relayed back to the execution chain. This will incur a bridging fee, which is expected to be paid by the DAO. - If nobody pays for it before the vote closes, votes will not be counted.
- Automating this process could be a feature in the future.
- Someone must call
Gas Limit for Bridging Actions:
- Bridging actions cross-chain requires passing the gas limit in the proposal. The proposal creator should ensure enough gas has been passed to cover execution. Failing to do so can result in proposals being unable to execute.
- In the future, additional fallback utilities will be provided, but currently, operators should calculate the required gas. The DAO can claim refunded gas back on the voting chain using the sweep function.