Interchain DAO with Hyperlane

Arman Aurobindo
11 min readJul 5, 2023

--

Currently, token holders over in L1 are hesitant for voting in DAOs. And the main reason behind this is gas fees. To solve this problem, I worked on the project Interchain DAO with Hyperlane. Hyperlane is the first universal and permissionless interoperability framework built for the modular blockchain stack. You can deploy Hyperlane to any chain and make it interoperable between different chains without anyone’s permission.

There are multiple types of Governor contracts that you can use to create a DAO. For this prototype, we have used OpenZeppelin Governor’s smart contract. Previously, the Scopelift team built a Flexible Voting extension with support from Uniswap Grants Program. Using this flexible voting extension, they aimed to solve a lot of issues involving Voting on L2 with bridged tokens, Voting with tokens while earning yield in DeFi, and Cheaper subsidized signature-based voting. Thus, the base of the governor contracts used in our prototype is the Fractional Governor smart contracts developed in the Flexible Voting Extension using OpenZeppelin’s Governor contract. The main motive for using the Fractional Governor smart contract is that we can import all the types of votes namely for, against, and abstain votes with just one voting function instead of making multiple voting calls. Also, it is been used because, in normal Governor contracts, we cannot accept votes fractionally. The L1VoteDelegator contract will cast votes using the voting power of the locked assets in the collateral contract.

Above is the basic workflow of our prototype. We have multiple components required for the whole system to work. Let’s understand each and every component of the workflow.

  1. L1 Token:- This is the primary token that the users can use to vote in the DAO. This is just a normal ERC20 token with an ERC20 votes extension using will be used for delegating votes in the governor.
  2. L1 Governor:- The main governor contract where all the voting and execution happens. All the proposals will be submitted to this governor. And the user who wants to vote in L1 can vote here directly.
  3. L1 Vote Delegator:- This particular smart contract has two use cases. First is that when you propose a new proposal to the L1 Governor, this smart contract is responsible to propagate the data of the proposal to the L2 Governor. The second and most important use case is that this particular smart contract is responsible to import the votes from L2 and vote in L1 governor.
  4. L1 Token Collateral:- In this smart contract, users will lock up their tokens, and in return, they will get wrapped tokens over in L2 in the same proportions. This contract will also delegate the power of voting for all the locked tokens to the L1 Vote Delegator so that it can vote in the L1 governor for the locked tokens.
  5. L2 Token:- Similar to the L1 token, this token will be used to vote over in L2 governor. The tokens in the contract can only be minted once you lock up your L1 tokens in the collateral contract.
  6. L2 Governor:- Using the wrapped tokens, users can cast their vote for the proposals over in the L2 Governor. Once the voting period ends users can bridge the votes to the L1 governor.
  7. L2 Bridge:- Similar to the L1 Vote Delegator, this particular smart contract acts as a bridge between L2 Governor and the L1 Governor. The proposals in the L2 are proposed by this smart contract and also the votes of the L2 Governor are exported by this smart contract.

Below are some important links that will be useful as we will be cloning the repositories and deploying contracts through them.

Now let’s see how we can deploy an interchain DAO using the prototype I have developed:-

The very first step is to deploy a votable token in L1 which can be used by the L1 Governor to cast votes on the proposals. I have made this process very easy. Just follow the process given below:-

git clone https://github.com/armanthepythonguy/Hyperlane-InterchainDAO.git

git submodule update --init --recursive

npm install

Also, visit Hyperlane’s Domain Identifier page and find out the domain ID for your L1 and L2 chain. Once you have successfully implemented the above-given commands it’s time to create a .env file with the below configurations:-

L1_RPC_URL= "YOUR L1 RPC URL"
L2_RPC_URL= "YOUR L2 RPC URL"
L1_DOMAIN = "L1 Domain"
L2_DOMAIN = "L2 Domain"
PRIVATE_KEY = "YOUR PRIVATE KEY"

Once you have created the .env file. It’s time to run some specific scripts which will create the L1 token for you. But first, you need to initialize the local env variables that you have stored in the .env file.

source .env

forge script script/L1TokenDeploy.sol:L1TokenDeploy --rpc-url $L1_RPC_URL --private-key $PRIVATE_KEY --broadcast

On running the above-given commands, you will get the contract address of the L1 token printed in your terminal. Keep this address handy as we will be using the address later. A screenshot is attached below:-

After creating the L1 token, now it’s time to deploy a warp route using Hyperlane through which you can bridge your L1 tokens into wrapped tokens in L2. You can read more about warp routes over here.

Before this project, you could have only deployed a normal ERC20 on the L2 which can’t be used to cast a vote. But as we needed a votable ERC20, I made some changes to the warp route deployment script and now it’s live over in Hyperlane’s git repository. Follow the commands given below to get the necessary scripts needed to deploy a warp route.

git clone https://github.com/hyperlane-xyz/hyperlane-deploy.git

yarn install

yarn build

Now you need to create the warp route config which will be used to define the warp route. It has contents like the L1 token address, L1 chain config, L2 chain config, etc. You need to make some changes in the warp_tokens.ts file over in /config/warp_tokens.ts location. The config file should look something like this:-

import { TokenType } from '@hyperlane-xyz/hyperlane-token';

import type { WarpRouteConfig } from '../src/warp/config';

export const warpRouteConfig: WarpRouteConfig = {
base: {
chainName: 'name of your L1 chain', // Over here give the name of your L1 chain
type: TokenType.collateral,
address: 'L1 token address you got after deploying the L1 contract',
votable: true, // This is a custom field that we developed to deploy votable collateralized ERC20 on the L1
},
synthetics: [
{
chainName: 'name of your L2 chain', // Over here give the name of the L2 chain,
votable: true, // This is a custom field that we developed to deploy votable ERC20 on the L2
},
],
};

Now it’s time to deploy a warp route with the given config. To do this just run this simple command:-

DEBUG=hyperlane* yarn ts-node scripts/deploy-warp-routes.ts --key $YOUR_PRIVATE_KEY

After successfully running the commands given above, your warp route is successfully deployed. You can get the token address of your L2 token over in /artifacts/warp-token-addresses.json. The L2 token address will be mentioned as the synthetic token type. Here is an example:-

Now keep the L2 token address handy as we will be using it very soon while deploying the governor contracts. After completing all the above processes it’s time to deploy the core scripts related to the DAO. Come back to the Interchain-DAO repo you cloned before. Over there in the scripts folder, you will be having L1Deploy.sol. Open that file and add the L1 token address in the required space. Also, visit Hyperlane’s Domain Identifier page and find out the domain ID for your L1 and L2 chain. An overview is given below:-

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Script.sol";
import "forge-std/console.sol";
import "../src/L1GovernorCountingFractional.sol";
import "../src/L1VoteDelegator.sol";

contract L1Deploy is Script{
function run() external{
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);

address[] memory demoAddress = new address[](2);
TimelockController timelockContract = new TimelockController(0, demoAddress, demoAddress, "Owner Address");
L1VoteDelegator delegatorContract = new L1VoteDelegator(0xCC737a94FecaeC165AbCf12dED095BB13F037685, 0xF90cB82a76492614D07B82a7658917f3aC811Ac1, "L2 Domain ID");
L1GovernorCountingFractional governorContract = new L1GovernorCountingFractional(IVotes("L1 Token Address"), timelockContract, address(delegatorContract));
console.log("L1 Governor Contract Address is :- ", address(governorContract));
console.log("L1 Vote Delegator Contract Address is :- ", address(delegatorContract));
vm.stopBroadcast();
}
}

Once you have made the required changes in the L1Deploy.sol file, now we can deploy the important scripts on the L1. Just run the following command:-

forge script script/L1Deploy.sol:L1Deploy --rpc-url $L1_RPC_URL --private-key $PRIVATE_KEY --broadcast

After running the above commands you will get the contract addresses for the Governor and Vote Delegator. Keep both addresses handy as we will be using them later.

Similar to this, now we will deploy the important governor and bridge contracts over in L2. Thus make the required changes in the L2Deploy.sol file that you will find under the scripts folder. Below is an overview:-

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Script.sol";
import "forge-std/console.sol";
import "../src/L2GovernorCountingFractional.sol";
import "../src/L2Bridge.sol";

contract L2Deploy is Script{
function run() external{
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);

address[] memory demoAddress = new address[](2);
TimelockController timelockContract = new TimelockController(0, demoAddress, demoAddress, "Owner's address");
L2GovernorCountingFractional governorContract = new L2GovernorCountingFractional(IVotes("L2 Token Address"), timelockContract);
L2Bridge bridgeContract = new L2Bridge(0xCC737a94FecaeC165AbCf12dED095BB13F037685, 0xF90cB82a76492614D07B82a7658917f3aC811Ac1, "L1 Domain ID");
console.log("L2 Governor Contract Address is :- ", address(governorContract));
console.log("L2 Bridge Contract Address is :- ", address(bridgeContract));
vm.stopBroadcast();
}
}

With all the required changes completed in the L2Deploy.sol, it’s time to deploy the contracts. Just run the following commands to do so:-

forge script script/L2Deploy.sol:L2Deploy --rpc-url $L2_RPC_URL --private-key $PRIVATE_KEY --broadcast

You will get the L2 Governor and L2Bridge contract addresses after running the script successfully. One of the snapshots has been attached below:-

All of the deployment of the contracts is completed now. Now, it’s time to initialize the contracts and have a working demo. For initializing, the first step is to delegate the voting power of the locked tokens from the Collateral contract to the L1 Vote Delegator. Once you have delegated the voting powers of the locked tokens it’s time to initialize the L1VoteDelegator and L2Bridge with cross-chain data so that using Hyperlane they can bridge proposals and votes. Just run the following commands by placing the appropriate contract address in place in the script/L1Init.sol and script/L2Init.sol files:-

forge script script/L1Init.sol:L1Init --rpc-url $L1_RPC_URL --private-key $PRIVATE_KEY --broadcast

forge script script/L2Init.sol:L2Init --rpc-url $L2_RPC_URL --private-key $PRIVATE_KEY --broadcast

Now, you have successfully deployed at interchain DAO. Now it’s time to check the workflow of the DAO. Below I have mentioned the steps on how you can propose new proposals and vote on existing proposals. Either you can add your DAO to tally.xyz so that you can get a frontend interface to easily work with the DAO. As of now, while writing this documentation Tally doesn’t support fractional voting, we had some talks with the core team and they will add it to Tally as soon as possible. Still I would recommend you to add your DAO to Tally as it will become easy for you to vote using Tally. You can do so by visiting

Over there go and fillup a form with all the required details like contract addresses, network details(which blockchain you are using), block height(block number when you deployed the contract helpful to index data related to the DAO or token).

  1. When you want to propose a new proposal to the interchain DAO there are two processes to be followed, first is to create a normal proposal in the L1 and the second step involves you to call a particular “bridgeProposal” function in the L1VoteDelegator contract. We didn’t include the automatic process where the L1 Governor itself will call the L1VoteDelegator to transport the proposal because we wanted to keep the code of the Fractional Governor intact with no changes. Because if we make custom changes, it becomes difficult for existing DAOs to audit the new contracts and then deploy. Thus, we need to call the L1VoteDelegator with the exact same data with which we created a proposal in L1. I have created two scripts using which you can propose a new proposal in the L1 Governor and bridge the proposal to the L2. Thus make changes to the script/L1Propose.sol and script/L1Delegate.sol file with appropriate data. Change the variables target, values, calldatas, and description as per your proposal. After changing the required fields you can run the following commands:-
forge script script/L1Propose.sol:L1Propose --rpc-url $L1_RPC_URL --private-key $PRIVATE_KEY --broadcast

forge script script/L1Delegate.sol:L1Delegate --rpc-url $L1_RPC_URL --private-key $PRIVATE_KEY --broadcast

Once you call this function successfully, you will see automatically see the same proposal is created over at L2 Governor. Now, for demonstration purposes, we have kept the delay period as zero, which means that as soon as the proposal is proposed, the voting starts.

2. Now coming to the voting part, we have made no changes to the process. Anyone who holds tokens for that particular project can come and vote either in L1 or if you want to save gas fees(the main motive of the project) you can bridge your tokens from L1 to L2 with the warp route that we have deployed earlier in this tutorial. And then, with the bridged token, the user can vote in the L2 Governor. Now, if you have added your DAO to Tally, you can do so by visiting the proposal page(first visit the DAO page over in your dashboard, then you an find active proposals) and voting over there. Below are some screenshots of how you can vote:-

3. Once the voting period ends in the L2Governor we can pack all the votes together and pass it to the L1 Governor. To do so, you need to call “bridgeVotes” function over in the L2 Bridge contract. Also for this, I have created a script so do change the required fields in the script/L2Bridge.sol file and run the following commands:-

forge script script/L2BridgeVote.sol:L2BridgeVote --rpc-url $L2_RPC_URL --private-key $PRIVATE_KEY --broadcast

After making this call, if everything goes fine with your demo then L1VoteDelegator will cast votes bridged from L2 in the L1 Governor.

Thanks for reading this article till this point. If you face any issues do connect with me on Discord with armanpanda#12082003 or in Telegram with username @armanaurobindo.

--

--