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

feat: final gov deployment allocations and merkle tree #168

Merged
merged 12 commits into from
Apr 23, 2024
28 changes: 23 additions & 5 deletions bin/merkle-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,18 @@

import { parseArgs } from "node:util";
import { MerkleTree } from "merkletreejs";
import { keccak256, AbiCoder } from "ethers";
import { keccak256, AbiCoder, parseEther } from "ethers";
import * as fs from "fs";
import { parse } from "csv-parse/sync";
import assert from "assert";

interface DistributionRecord {
Address: string;
"MENTO based on locked CELO": string;
"MENTO based on cStables balances": string;
"MENTO based on cStables volumes": string;
total_distributed: string;
}

const {
values: { network },
Expand All @@ -27,14 +36,23 @@ export const generateTree = async (): Promise<MerkleTree> => {
const abicoder = new AbiCoder();
const fileContent = fs.readFileSync(`data/airgrab.${network}.csv`, "utf8");
const records = parse(fileContent, {
columns: false,
columns: true,
skipEmptyLines: true,
});

console.log("Records: ", records.length);
const leaves = records.map((row: DistributionRecord) => {
const address = row.Address;
const totalDistributed = row.total_distributed;

const fromLockedCelo = parseFloat(row["MENTO based on locked CELO"] || "0");
const fromStablesBalances = parseFloat(row["MENTO based on cStables balances"] || "0");
const fromStablesVolumes = parseFloat(row["MENTO based on cStables volumes"] || "0");
assert(
fromLockedCelo + fromStablesBalances + fromStablesVolumes === parseFloat(totalDistributed),
"Invalid distribution",
);

const leaves = records.map((row: any) => {
const encoded = abicoder.encode(["address", "uint256"], [row[0], row[1]]);
const encoded = abicoder.encode(["address", "uint256"], [address, parseEther(totalDistributed)]);
const leafHash = keccak256(encoded);
const leaf = keccak256(Buffer.concat([Buffer.from(leafHash.slice(2), "hex")])); // Remove '0x' and convert to Buffer
return leaf;
Expand Down
44,435 changes: 44,421 additions & 14 deletions data/airgrab.alfajores.csv

Large diffs are not rendered by default.

133,349 changes: 133,299 additions & 50 deletions data/airgrab.alfajores.tree.json

Large diffs are not rendered by default.

61 changes: 35 additions & 26 deletions script/upgrades/MUGOV/MUGOV.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,30 +44,8 @@ contract MUGOV is IMentoUpgrade, GovernanceScript {

function buildProposal() public returns (ICeloGovernance.Transaction[] memory) {
require(transactions.length == 0, "buildProposal() should only be called once");
IGovernanceFactory.MentoTokenAllocationParams memory allocationParams;
allocationParams.airgrabAllocation = 100;
allocationParams.mentoTreasuryAllocation = 100;
if (Chain.isCelo()) {
// TODO: Add final allocation amounts
allocationParams.additionalAllocationRecipients = Arrays.addresses(contracts.dependency("MentoLabsMultisig"));
allocationParams.additionalAllocationAmounts = Arrays.uints(100);
} else {
allocationParams.additionalAllocationRecipients = Arrays.addresses(
contracts.dependency("MentoLabsMultisig"),
/// @dev This is a tesetnet only allocation.
/// Whenever we deploy to testnets we seed these addresses with $MENTO.
/// The arguments to vm.addr are the private keys, and the addresses are in the comment.
/// You can import one of these private keys into metamask to use for testing.
vm.addr(0xb228ca748093e781f324701f53cfa26b26bc55919e0fe361d704b9c2a3d9817c), // 0x99995570bc88340d726D15D172e668271FBC9e20
vm.addr(0x6deca5973d3a26e5ee93e60ade2e7568072471711909a87e26afcec346dbf9da), // 0x9999f469Fa49bB921eA385F1de49dcBccfbC9A82
vm.addr(0x648f06647a69623eb01ce413890fc7c907bf2d36e3a4e7dbc9fd3adc8162f542), // 0x9999700347b57a3152E8B63123649949A9aBE20d
vm.addr(0x2a5e23ad202f6dcc13847c68a85b26ee7b26a5e89a89cc9a19f15d46932fc5da), // 0x9999db67bF5151668AAff29eD4BAca3926747ED7
vm.addr(0x50a5d71e8994f5f4bb44e8431f695ed1520cf1999b6d01c4a6fd199f14a6c747), // 0x9999C6De88eBdf0aff022D127C36541D53F8789A
vm.addr(0x6ccf3ccc08dabc44678a688815e2d2e8603416c886eb550c2be2319423be518c), // 0x99994874b3B90E690287C85df1ba26E886FF87f0
vm.addr(0xef7301ce9a7e88105c539f96c53e5cf70cff0c21ffae34c088febe6cf00696b1) // 0x99990eA09DD56949DbaFe97fc34DBC69BDA81027
);
allocationParams.additionalAllocationAmounts = Arrays.uints(100, 1, 1, 1, 1, 1, 1, 1);
}

IGovernanceFactory.MentoTokenAllocationParams memory allocationParams = getTokenAllocationParams();

address mentoGovernanceFactory = contracts.deployed("GovernanceFactory");
transactions.push(
Expand All @@ -76,8 +54,8 @@ contract MUGOV is IMentoUpgrade, GovernanceScript {
mentoGovernanceFactory,
abi.encodeWithSelector(
IGovernanceFactory(0).createGovernance.selector,
contracts.dependency("WatchdogMultisig"),
readAirgrabMerkleRoot(),
contracts.dependency("WatchdogMultisig"), // @TODO: Update final address in deps.json
readAirgrabMerkleRoot(), // @TODO: Update merkle tree after final snapshot
contracts.dependency("FractalSigner"),
allocationParams
)
Expand All @@ -87,6 +65,37 @@ contract MUGOV is IMentoUpgrade, GovernanceScript {
return transactions;
}

function getTokenAllocationParams() internal returns (IGovernanceFactory.MentoTokenAllocationParams memory) {
// ================================ MENTO TOKEN ALLOCATION ================================
// 1. Mento community Treasury (40% 10year emission, 5% immediately available) (45%)
// 2. Mento Labs Team, Investors, Future Hires, Advisors (30%)
// 3. Mento Liquidity Support (10%)
// 4. Airdrop to Celo and Mento stable assets users (5%)
// 5. Airdrop to Celo Community Treasury (5%)
// 6. Mento Reserve Safety Fund (5%)

IGovernanceFactory.MentoTokenAllocationParams memory params;

params.additionalAllocationRecipients = Arrays.addresses(
contracts.dependency("MentoLabsMultisig"), // #2, Mento Labs Team. @TODO: Update final recipient in deps.json
contracts.dependency("MentoLiquiditySupport"), // #3, Liquidity Support. @TODO: Update final recipient in deps.json
contracts.dependency("CeloCommunityTreasury"), // #5, Celo Community Treasury. @TODO: Update final recipient in deps.json
contracts.celoRegistry("Reserve") // #6, Reserve Safety Fund.
);
params.additionalAllocationAmounts = Arrays.uints(300, 100, 50, 50);

// #4, Community Airdrop
params.airgrabAllocation = 50;

// #1, Mento Community Treasury.
// Note that below we only allocate the 5% part for immediate use that goes to governanceTimeLock.
// The reimaining part of the allocation (40%) is automatically allocated to the Emission contract
// by MentoToken.sol during initialization.
params.mentoTreasuryAllocation = 50;

return params;
}

function readAirgrabMerkleRoot() internal view returns (bytes32) {
string memory network = Chain.rpcToken(); // celo | baklava | alfajores
string memory root = vm.projectRoot();
Expand Down
46 changes: 29 additions & 17 deletions script/upgrades/MUGOV/MUGOVChecks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ contract MUGOVChecks is GovernanceScript, Test {
IGovernanceFactory public governanceFactory;

address mentoLabsMultisig;
address mentoLiquiditySupport;
address celoCommunityTreasury;
address reserve;
address watchdogMultisig;
address fractalSigner;

Expand All @@ -44,40 +47,49 @@ contract MUGOVChecks is GovernanceScript, Test {
locking = ILocking(governanceFactory.locking());

mentoLabsMultisig = contracts.dependency("MentoLabsMultisig");
mentoLiquiditySupport = contracts.dependency("MentoLiquiditySupport");
celoCommunityTreasury = contracts.dependency("CeloCommunityTreasury");
reserve = contracts.celoRegistry("Reserve");
watchdogMultisig = contracts.dependency("WatchdogMultisig");
fractalSigner = contracts.dependency("FractalSigner");
}

function run() public {
// Balances:
assertEq(mentoToken.balanceOf(mentoLabsMultisig), 100000000 * 1e18, "MentoLabs multisig balance is incorrect");
assertEq(mentoToken.balanceOf(address(airgrab)), 100000000 * 1e18, "Airgrab balance is incorrect");
console.log("\n ======== 🔍 Checking MUGOV setup ========");

// ============== Token allocation ==============
assertEq(mentoToken.balanceOf(mentoLabsMultisig), 300_000_000 * 1e18, "❌ mentoLabsMultisig allocation");
assertEq(mentoToken.balanceOf(mentoLiquiditySupport), 100_000_000 * 1e18, "❌ mentoLiquiditySupport allocation");
assertEq(mentoToken.balanceOf(celoCommunityTreasury), 50_000_000 * 1e18, "❌ celoCommunityTreasury allocation");
assertEq(mentoToken.balanceOf(reserve), 50_000_000 * 1e18, "❌ reserve allocation");
assertEq(mentoToken.balanceOf(address(airgrab)), 50_000_000 * 1e18, "❌ airgrab allocation");
assertEq(mentoToken.balanceOf(address(governanceTimelock)), 50_000_000 * 1e18, "❌ governanceTimelock allocation");
assertEq(mentoToken.emissionSupply(), 400_000_000 * 1e18, "❌ emission allocation");

assertEq(
mentoToken.balanceOf(address(governanceTimelock)),
100000000 * 1e18,
"Governance timelock balance is incorrect"
mentoToken.totalSupply() + mentoToken.emissionSupply(),
1_000_000_000 * 1e18,
"❌ allocation exceeds 1billion"
);
assertEq(mentoToken.emissionSupply(), 693000000 * 1e18, "Emission supply is incorrect");
console.log("🟢 Mento Token initial allocation minted correctly");

// Mento Token:
// ============== Mento Token ==============:
assertEq(mentoToken.symbol(), "MENTO", "MentoToken: symbol is incorrect");
assertEq(mentoToken.name(), "Mento Token", "MentoToken: name is incorrect");
assertEq(uint256(mentoToken.decimals()), 18, "MentoToken: decimals is incorrect");
assertEq(mentoToken.emission(), address(emission), "MentoToken: Emission address is incorrect");
assertEq(mentoToken.locking(), address(locking), "MentoToken: Locking address is incorrect");
assertEq(mentoToken.owner(), address(governanceTimelock), "MentoToken: Owner address is incorrect");
assertEq(mentoToken.paused(), true, "MentoToken: should be paused");

console.log("🟢 Mento Token setup correctly");

// Emission checks:
// ============== Emission checks ==============:
assertEq(emission.owner(), address(governanceTimelock), "Emission owner is incorrect");
assertEq(emission.mentoToken(), address(mentoToken), "Emission Mento Token is incorrect");
assertEq(emission.emissionTarget(), address(governanceTimelock), "Emission target is incorrect");
console.log("🟢 Emission setup correctly");

// Airgrab checks:
// ============== Airgrab checks ==============:
assertEq(airgrab.root(), airgrabMerkleRoot, "Airgrab root is incorrect");
assertEq(airgrab.fractalSigner(), fractalSigner, "Airgrab fractal signer is incorrect");
assertEq(airgrab.fractalMaxAge(), 15552000, "Airgrab fractal max age is incorrect");
Expand All @@ -86,9 +98,12 @@ contract MUGOVChecks is GovernanceScript, Test {
assertEq(airgrab.token(), address(mentoToken), "Airgrab token is incorrect");
assertEq(airgrab.locking(), address(locking), "Airgrab locking is incorrect");
assertEq(airgrab.mentoTreasury(), address(governanceTimelock), "Airgrab Mento Treasury is incorrect");

// @TODO: Replace below check with 2 weeks after updating mento-core
assertEq(airgrab.endTimestamp() - block.timestamp, 365 days, "Airgrab end timestamp is incorrect");
baroooo marked this conversation as resolved.
Show resolved Hide resolved
console.log("🟢 Airgrab setup correctly");

// Timelock Checks
// ============== Timelock Checks ==============
assertEq(governanceTimelock.getMinDelay(), 2 * 24 * 60 * 60, "Timelock min delay is incorrect");
assertTrue(
governanceTimelock.hasRole(governanceTimelock.PROPOSER_ROLE(), address(mentoGovernor)),
Expand All @@ -109,28 +124,25 @@ contract MUGOVChecks is GovernanceScript, Test {
governanceTimelock.hasRole(governanceTimelock.CANCELLER_ROLE(), watchdogMultisig),
"governanceTimelock canceller role for mentoGovernor is incorrect"
);

console.log("🟢 Governance Timelock setup correctly");

// Mento Governor checks:
// ============== Mento Governor checks ==============:
assertEq(mentoGovernor.token(), address(locking), "MentoGovernor token is incorrect");
assertEq(mentoGovernor.votingDelay(), 0, "MentoGovernor voting delay is incorrect");
assertEq(mentoGovernor.votingPeriod(), 120960, "MentoGovernor voting period is incorrect");
assertEq(mentoGovernor.proposalThreshold(), 1000 * 1e18, "MentoGovernor proposal threshold is incorrect");
assertEq(mentoGovernor.quorumNumerator(), 2, "MentoGovernor quorum numerator is incorrect");
assertEq(mentoGovernor.timelock(), address(governanceTimelock), "MentoGovernor timelock is incorrect");

console.log("🟢 Mento Governor setup correctly");

// Locking checks:
// ============== Locking checks ==============:
assertEq(locking.token(), address(mentoToken), "Locking token is incorrect");
assertEq(locking.minCliffPeriod(), 0, "Locking min cliff period is incorrect");
assertEq(locking.minSlopePeriod(), 1, "Locking max cliff period is incorrect");
assertEq(locking.owner(), address(governanceTimelock), "Locking owner is incorrect");
assertEq(locking.getWeek(), 1, "Locking week is incorrect");
assertEq(locking.symbol(), "veMENTO", "Locking symbol is incorrect");
assertEq(locking.name(), "Mento Vote-Escrow", "Locking name is incorrect");

console.log("🟢 Locking setup correctly");
}

Expand Down
4 changes: 4 additions & 0 deletions script/upgrades/MUGOV/interfaces.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pragma solidity ^0.5.13;
interface IMentoToken {
function emissionSupply() external view returns (uint256);

function totalSupply() external view returns (uint256);

function emission() external view returns (address);

function locking() external view returns (address);
Expand Down Expand Up @@ -36,6 +38,8 @@ interface IAirgrab {

function fractalMaxAge() external view returns (uint256);

function endTimestamp() external view returns (uint256);

function slopePeriod() external view returns (uint32);

function cliffPeriod() external view returns (uint32);
Expand Down
10 changes: 6 additions & 4 deletions script/upgrades/dependencies.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
"ExchangeEUR": "0xC200CD8ac71A63e38646C34b51ee3cBA159dB544",
"ExchangeBRL": "0x28e257d1E73018A116A7C68E9d07eba736D9Ec05",
"GrandaMento": "0xdfd641aB188Add84B317fB0b241F6b879E5EF906",
"MentoLabsMultisig": "0x56fD3F2bEE130e9867942D0F463a16fBE49B8d81",
"WatchdogMultisig": "0x56fD3F2bEE130e9867942D0F463a16fBE49B8d81",
"FractalSigner": "0x2fCAb633adFA6aF8266025D63228047033c3ceD0"
"MentoLabsMultisig": "0xNotDeployingOnBaklava",
"WatchdogMultisig": "0xNotDeployingBaklava",
"FractalSigner": "0xNotDeployingOnBaklava"
},
"44787": {
"BridgedUSDC": "0x87D61dA3d668797786D73BC674F053f87111570d",
Expand All @@ -50,7 +50,9 @@
"ExchangeBRL": "0xf391dcaf77360d39e566b93c8c0ceb7128fa1a08",
"GrandaMento": "0xecf09fcd57b0c8b1fd3de92d59e234b88938485b",
"MentoLabsMultisig": "0x56fD3F2bEE130e9867942D0F463a16fBE49B8d81",
"WatchdogMultisig": "0x56fD3F2bEE130e9867942D0F463a16fBE49B8d81",
"WatchdogMultisig": "0x823655c966be3b6344Efd4D2A0FE8d0a1e3D691B",
"CeloCommunityTreasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"MentoLiquiditySupport": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"FractalSigner": "0x2fCAb633adFA6aF8266025D63228047033c3ceD0"
}
}
Loading