Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DAO factory (custom) #19

Merged
merged 23 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading