Skip to content

Commit

Permalink
Merge branch 'f/mode-dao-factory' into f/dao-factory-updated
Browse files Browse the repository at this point in the history
  • Loading branch information
jordaniza committed Sep 29, 2024
2 parents 6d5e5ba + d8b08c1 commit b08a152
Show file tree
Hide file tree
Showing 11 changed files with 2,810 additions and 0 deletions.
52 changes: 52 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# NETWORK AND DEPLOYMENT WALLET
DEPLOYMENT_PRIVATE_KEY="..."
ALCHEMY_API_KEY="..."
ETHERSCAN_API_KEY="..."
NETWORK="sepolia"

# With false, the script will deploy mock tokens
DEPLOY_AS_PRODUCTION=true
MULTISIG_MEMBERS_JSON_FILE_NAME="/script/multisig-members.json"

# MULTISIG PARAMETERS
MIN_APPROVALS="5" # How many multisig approvals are required
MULTISIG_PROPOSAL_EXPIRATION_PERIOD="864000" # How long until a pending proposal expires (10 days)

# GAUGE VOTER PARAMETERS
# The token to be used for the escrow
TOKEN1_ADDRESS="0xdc518215FCbeB2b641073F4387895E64d65D51fB"
VE_TOKEN1_NAME="Voting Escrow Token 1"
VE_TOKEN1_SYMBOL="veTK1"

# Additional tokens
TOKEN2_ADDRESS="0x0000000000000000000000000000000000000000" # Ignored if 0x0
VE_TOKEN2_NAME="Voting Escrow Token 2"
VE_TOKEN2_SYMBOL="veTK2"

# 1 ether = 100%, represents the fee taken on withdrawals
FEE_PERCENT_WEI="0"

# Min seconds after depositing before voting is possible
WARMUP_PERIOD="259200" # 3 days

# Min seconds after queuing an exit before withdrawing is possible
COOLDOWN_PERIOD="259200" # 3 days

# Min seconds a user must have locked in escrow before they can queue an exit
MIN_LOCK_DURATION="4838400" # 8 weeks

# Prevent voting until manually activated by the multisig
VOTING_PAUSED=true

# PLUGIN REPO PARAMETERS (per-network)
# SEPOLIA
MULTISIG_PLUGIN_REPO_ADDRESS="0x9e7956C8758470dE159481e5DD0d08F8B59217A2"
MULTISIG_PLUGIN_RELEASE="1"
MULTISIG_PLUGIN_BUILD="2"
SIMPLE_GAUGE_VOTER_REPO_ENS_SUBDOMAIN="my-simple-gauge-voter-0"

# OSx IMPLEMENTATIONS ADDRESSES (network dependent, see active_contracts.json on lib/osx)
# SEPOLIA
DAO_FACTORY="0x7a62da7B56fB3bfCdF70E900787010Bc4c9Ca42e"
PLUGIN_SETUP_PROCESSOR="0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f"
PLUGIN_REPO_FACTORY="0x07f49c49Ce2A99CF7C28F66673d406386BDD8Ff4"
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ branch = v4.9.2
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
[submodule "lib/ens-contracts"]
path = lib/ens-contracts
url = https://github.com/ensdomains/ens-contracts
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,43 @@ The main workflow in the Mode Governance build is as follows:
## Curve design

To build a flexible approach to curve design, we reviewed implementations such as seen in Curve and Aerodrome and attempted to generalise to higher order polynomials [Details on the curve design research can be found here](https://github.com/jordaniza/ve-explainer/blob/main/README.md)

## Deployment

To deploy the DAO, ensure that [Foundry](https://getfoundry.sh/) is installed on your computer.

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 parameters

```sh
# Load the env vars
source .env
```

```sh
# Set the right RPC URL
RPC_URL="https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY}"
```

```sh
# Run the deployment script

# If using Etherscan
forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --broadcast --verify

# If using BlockScout
forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --broadcast --verify --verifier blockscout --verifier-url "https://sepolia.explorer.mode.network/api\?"
```

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

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

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

```sh
forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --verify --legacy --private-key "$DEPLOYMENT_PRIVATE_KEY" --resume
```
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ bytecode_hash = "none"
cbor_metadata = false
fuzz = { runs = 256 }
gas_reports = ["*"]
fs_permissions = [{ access = "read", path = "./script" }]
libs = ["lib"]
optimizer = true
optimizer_runs = 10_000
Expand Down
1 change: 1 addition & 0 deletions lib/ens-contracts
Submodule ens-contracts added at a6139f
202 changes: 202 additions & 0 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

import {Script, console} from "forge-std/Script.sol";
import {DAO} from "@aragon/osx/core/dao/DAO.sol";
import {GaugesDaoFactory, DeploymentParameters, Deployment, TokenParameters} from "../src/factory/GaugesDaoFactory.sol";
import {MultisigSetup as MultisigPluginSetup} from "@aragon/osx/plugins/governance/multisig/MultisigSetup.sol";
import {VotingEscrow, Clock, Lock, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol";
import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol";
import {PluginRepoFactory} from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol";
import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol";
import {MockERC20} from "@mocks/MockERC20.sol";

contract Deploy is Script {
SimpleGaugeVoterSetup simpleGaugeVoterSetup;

/// @dev Thrown when attempting to deploy a multisig with no members
error EmptyMultisig();

modifier broadcast() {
uint256 privKey = vm.envUint("DEPLOYMENT_PRIVATE_KEY");
vm.startBroadcast(privKey);
console.log("Deploying from:", vm.addr(privKey));

_;

vm.stopBroadcast();
}

/// @notice Runs the deployment flow, records the given parameters and artifacts, and it becomes read only
function run() public broadcast {
// Prepare all parameters
bool isProduction = vm.envBool("DEPLOY_AS_PRODUCTION");
DeploymentParameters memory parameters = getDeploymentParameters(isProduction);

// Create the DAO
GaugesDaoFactory factory = new GaugesDaoFactory(parameters);
factory.deployOnce();

// Done
printDeploymentSummary(factory);
}

function getDeploymentParameters(
bool isProduction
) internal returns (DeploymentParameters memory parameters) {
address[] memory multisigMembers = readMultisigMembers();
TokenParameters[] memory tokenParameters = getTokenParameters(isProduction);

// NOTE: Multisig is already deployed, using the existing Aragon's repo
// NOTE: Deploying the plugin setup from the current script to avoid code size constraints

SimpleGaugeVoterSetup gaugeVoterPluginSetup = deploySimpleGaugeVoterPluginSetup();

parameters = DeploymentParameters({
// Multisig settings
minApprovals: uint8(vm.envUint("MIN_APPROVALS")),
multisigMembers: multisigMembers,
// Gauge Voter
tokenParameters: tokenParameters,
feePercent: vm.envUint("FEE_PERCENT_WEI"),
warmupPeriod: uint64(vm.envUint("WARMUP_PERIOD")),
cooldownPeriod: uint64(vm.envUint("COOLDOWN_PERIOD")),
minLockDuration: uint64(vm.envUint("MIN_LOCK_DURATION")),
votingPaused: vm.envBool("VOTING_PAUSED"),
// Standard multisig repo
multisigPluginRepo: PluginRepo(vm.envAddress("MULTISIG_PLUGIN_REPO_ADDRESS")),
multisigPluginRelease: uint8(vm.envUint("MULTISIG_PLUGIN_RELEASE")),
multisigPluginBuild: uint16(vm.envUint("MULTISIG_PLUGIN_BUILD")),
// Voter plugin setup and ENS
voterPluginSetup: gaugeVoterPluginSetup,
voterEnsSubdomain: vm.envString("SIMPLE_GAUGE_VOTER_REPO_ENS_SUBDOMAIN"),
// OSx addresses
osxDaoFactory: vm.envAddress("DAO_FACTORY"),
pluginSetupProcessor: PluginSetupProcessor(vm.envAddress("PLUGIN_SETUP_PROCESSOR")),
pluginRepoFactory: PluginRepoFactory(vm.envAddress("PLUGIN_REPO_FACTORY"))
});
}

function readMultisigMembers() internal view returns (address[] memory result) {
// JSON list of members
string memory membersFilePath = vm.envString("MULTISIG_MEMBERS_JSON_FILE_NAME");
string memory path = string.concat(vm.projectRoot(), membersFilePath);
string memory strJson = vm.readFile(path);
result = vm.parseJsonAddressArray(strJson, "$.members");

if (result.length == 0) revert EmptyMultisig();
}

function deploySimpleGaugeVoterPluginSetup() internal returns (SimpleGaugeVoterSetup result) {
result = new SimpleGaugeVoterSetup(
address(new SimpleGaugeVoter()),
address(new QuadraticIncreasingEscrow()),
address(new ExitQueue()),
address(new VotingEscrow()),
address(new Clock()),
address(new Lock())
);
}

function getTokenParameters(
bool isProduction
) internal returns (TokenParameters[] memory tokenParameters) {
if (isProduction) {
// USE TOKEN(s)
console.log("Using production parameters");

bool hasTwoTokens = vm.envAddress("TOKEN2_ADDRESS") != address(0);
tokenParameters = new TokenParameters[](hasTwoTokens ? 2 : 1);

tokenParameters[0] = TokenParameters({
token: vm.envAddress("TOKEN1_ADDRESS"),
veTokenName: vm.envString("VE_TOKEN1_NAME"),
veTokenSymbol: vm.envString("VE_TOKEN1_SYMBOL")
});

if (hasTwoTokens) {
tokenParameters[1] = TokenParameters({
token: vm.envAddress("TOKEN2_ADDRESS"),
veTokenName: vm.envString("VE_TOKEN2_NAME"),
veTokenSymbol: vm.envString("VE_TOKEN2_SYMBOL")
});
}
} else {
// MINT TEST TOKEN
console.log("Using testing parameters (minting 2 dev tokens)");

address[] memory multisigMembers = readMultisigMembers();
tokenParameters = new TokenParameters[](2);
tokenParameters[0] = TokenParameters({
token: createTestToken(multisigMembers),
veTokenName: "VE Token 1",
veTokenSymbol: "veTK1"
});
tokenParameters[1] = TokenParameters({
token: createTestToken(multisigMembers),
veTokenName: "VE Token 2",
veTokenSymbol: "veTK2"
});
}
}

function createTestToken(address[] memory holders) internal returns (address) {
console.log("");
MockERC20 newToken = new MockERC20();

for (uint i = 0; i < holders.length; ) {
newToken.mint(holders[i], 50 ether);
console.log("Minting 50 eth for", holders[i]);

unchecked {
i++;
}
}

return address(newToken);
}

function printDeploymentSummary(GaugesDaoFactory factory) internal view {
DeploymentParameters memory deploymentParameters = factory.getDeploymentParameters();
Deployment memory deployment = factory.getDeployment();

console.log("");
console.log("Chain ID:", block.chainid);
console.log("Factory:", address(factory));
console.log("");
console.log("DAO:", address(deployment.dao));
console.log("");

console.log("Plugins");
console.log("- Multisig plugin:", address(deployment.multisigPlugin));
console.log("");

for (uint i = 0; i < deployment.gaugeVoterPluginSets.length; ) {
console.log("- Using token:", address(deploymentParameters.tokenParameters[i].token));
console.log(
" Gauge voter plugin:",
address(deployment.gaugeVoterPluginSets[i].plugin)
);
console.log(" Curve:", address(deployment.gaugeVoterPluginSets[i].curve));
console.log(" Exit Queue:", address(deployment.gaugeVoterPluginSets[i].exitQueue));
console.log(
" Voting Escrow:",
address(deployment.gaugeVoterPluginSets[i].votingEscrow)
);
console.log(" Clock:", address(deployment.gaugeVoterPluginSets[i].clock));
console.log(" NFT Lock:", address(deployment.gaugeVoterPluginSets[i].nftLock));
console.log("");

unchecked {
i++;
}
}

console.log("Plugin repositories");
console.log(
"- Multisig plugin repository (existing):",
address(deploymentParameters.multisigPluginRepo)
);
console.log("- Gauge voter plugin repository:", address(deployment.gaugeVoterPluginRepo));
}
}
4 changes: 4 additions & 0 deletions script/multisig-members.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"members": [
]
}
Loading

0 comments on commit b08a152

Please sign in to comment.