@gooddollar/goodprotocol
Version:
GoodDollar Protocol
428 lines (377 loc) • 16.7 kB
text/typescript
/***
* Upgrade Reserve to fix distribution bug and use only disthelper for distribution
* Upgrade fundmanager to allow minting of ubi even without collecting interest, removing ubi distribution
* Upgrade distribution helper to work with new bridge replacing multichain
* Deploy fee formula to prevent usage of multichain bridge
* Mint G$s to new bridge to provide exit liquidity instead of multichain
* Upgrade Plan:
* - fuse: give mint permissions to new bridge (upgradeSidechain)
* - celo: give mint permissions to new bridge (upgradeSidechain)
* Mainnet:
* - deploy new impl, fund manager, distribution helper, fee formula
* - call disthelper updateAddresses
* - create guardians safe proposal to upgrade reserve + fundmanager + distribution helper
* - create guardians safe proposal to set the new distribution helper amounts,recipients, and bridges * ** THIS SHOULD BE DONE ONLY AFTER BRIDGE WAS DEPLOYED **
* - create guardians safe proposal to set the disthelper fee settings
* - create guardians safe proposal to mint G$s to bridge
* - create guardians safe proposal to set new fee formula
* - create guardians safe action to move UBI held in escrow to bridge (to balance manually minting UBI on celo)
*/
/** NOTICE **/
/**
* To test it on a fork make sure to first deploy the messagepassingbridge @gooddollar/GoodBridge deployMessagePassingBridge.ts
* to the same fork
*/
import { network, ethers } from "hardhat";
import { Contract } from "ethers";
import { defaultsDeep } from "lodash";
import prompt from "prompt";
import { reset } from "@nomicfoundation/hardhat-network-helpers";
import {
deployDeterministic,
printDeploy,
executeViaGuardian,
executeViaSafe,
verifyProductionSigner
} from "../multichain-deploy/helpers";
import ProtocolSettings from "../../releases/deploy-settings.json";
import dao from "../../releases/deployment.json";
import { verifyContract } from "../multichain-deploy/helpers";
import { IStaticOracle } from "../../types";
let { name: networkName } = network;
export const upgrade = async () => {
const isProduction = networkName.includes("production");
let [root, ...signers] = await ethers.getSigners();
if (isProduction) verifyProductionSigner(root);
let guardian = root;
//simulate produciton on fork
if (network.name === "hardhat" || network.name === "fork") {
networkName = "production-mainnet";
}
let release: { [key: string]: any } = dao[networkName];
let protocolSettings = defaultsDeep({}, ProtocolSettings[networkName], ProtocolSettings["default"]);
//simulate on fork, make sure safe has enough eth to simulate txs
if (network.name === "hardhat" || network.name === "fork") {
guardian = await ethers.getImpersonatedSigner(protocolSettings.guardiansSafe);
networkName = "production-mainnet";
await root.sendTransaction({ value: ethers.constants.WeiPerEther.mul(3), to: protocolSettings.guardiansSafe });
}
const rootBalance = await ethers.provider.getBalance(root.address).then(_ => _.toString());
const guardianBalance = await ethers.provider.getBalance(guardian.address).then(_ => _.toString());
const MULTICHAIN_BALANCE = "51098079793";
const NEWBRIDGE = release["MpbBridge"];
const gd = await ethers.getContractAt("IERC20", release["GoodDollar"]);
const GUARDIANS_BALANCE = await gd.balanceOf(guardian.address);
const bridgeDeployed = await ethers.provider.getCode(NEWBRIDGE).then(_ => _ != "0x");
if (!bridgeDeployed) throw new Error("bridge not deployed yet");
console.log("got signers:", {
networkName,
root: root.address,
guardian: guardian.address,
balance: rootBalance,
guardianBalance: guardianBalance
});
let networkEnv = networkName.split("-")[0];
const fuseNetwork = networkEnv;
if (networkEnv === "fuse") networkEnv = "development";
const celoNetwork = networkEnv + "-celo";
console.log("deploying new implementatios...");
let newReserveImpl = (await ethers.deployContract("GoodReserveCDai").then(printDeploy)) as Contract;
let newFundmanagerImpl = (await ethers.deployContract("GoodFundManager").then(printDeploy)) as Contract;
let newDisthelperImpl = (await ethers.deployContract("DistributionHelper").then(printDeploy)) as Contract;
let newFeeFormula = (await ethers.deployContract("MultichainFeeFormula").then(printDeploy)) as Contract;
if (isProduction) {
await verifyContract(newReserveImpl.address, "GoodReserveCDai", networkName);
await verifyContract(newFundmanagerImpl.address, "GoodFundManager", networkName);
await verifyContract(newDisthelperImpl.address, "DistributionHelper", networkName);
await verifyContract(newFeeFormula.address, "MultichainFeeFormula", networkName);
}
// make sure price oracle for fuse/celo/eth has enough observations
console.log("preparing price oracle...");
const oracle = (await ethers.getContractAt(
"IStaticOracle",
"0xB210CE856631EeEB767eFa666EC7C1C57738d438"
)) as IStaticOracle;
const [celoPool] = await oracle.callStatic.prepareSpecificFeeTiersWithTimePeriod(
await newDisthelperImpl.CELO_TOKEN(),
await newDisthelperImpl.WETH_TOKEN(),
[3000],
60
);
const [fusePool] = await oracle.callStatic.prepareSpecificFeeTiersWithTimePeriod(
await newDisthelperImpl.FUSE_TOKEN(),
await newDisthelperImpl.WETH_TOKEN(),
[3000],
60
);
const [usdcPool] = await oracle.callStatic.prepareSpecificFeeTiersWithTimePeriod(
await newDisthelperImpl.USDC_TOKEN(),
await newDisthelperImpl.WETH_TOKEN(),
[3000],
60
);
const pool = await ethers.getContractAt(
[
"function slot0() view returns (uint160 sqrtPriceX96,int24 sqrtPriceX96,uint16 observationIndex,uint16 observationCardinality,uint16 observationCardinalityNext,uint8 feeProtocol,bool unlocked)"
],
celoPool
);
const validPools: Array<[string, boolean]> = await Promise.all(
[celoPool, fusePool, usdcPool].map(async _ => {
const { observationCardinalityNext } = await pool.attach(_).slot0();
return [_, observationCardinalityNext < 5];
})
);
console.log({ celoPool, fusePool, usdcPool, validPools });
await oracle
.prepareSpecificPoolsWithTimePeriod(
validPools.filter(_ => _[1]).map(_ => _[0]),
60
)
.then(printDeploy);
console.log("executing proposals");
const proposalContracts = [
release.NameService, //controller -> set bridge contract in nameservice
release.GoodReserveCDai, //controller -> upgrade reserve
release.GoodFundManager, //controller -> upgrade fundmanager
release.DistributionHelper, //controller -> upgrade disthelper
release.DistributionHelper, //update addresses
release.DistributionHelper, //set fee settings
release.DistributionHelper, //remove distribution to guardians
release.DistributionHelper, //remove distribution to community pool on fuse
release.DistributionHelper, //set new distribution params
release.DistributionHelper, //set new distribution params
// release.DistributionHelper, //set new distribution params
release.DistributionHelper, //set new distribution params
release.Controller, //mint G$s to bridge
release.GoodDollar, // upgrade feeformula
protocolSettings.guardiansSafe + "_" + release.GoodDollar //transfer G$s from guardians to bridge
];
const proposalEthValues = proposalContracts.map(_ => 0);
const proposalFunctionSignatures = [
"setAddresses(bytes32[],address[])", // set new bridge name
"upgradeTo(address)", //upgrade reserve
"upgradeTo(address)", //upgrade fundmanager
"upgradeTo(address)", //upgrade disthelper
"updateAddresses()", //upgrade disthelper
"setFeeSettings((uint128,uint128,uint128,uint128,uint128,uint8))",
"addOrUpdateRecipient((uint32,uint32,address,uint8))", // remove guardians distribution
"addOrUpdateRecipient((uint32,uint32,address,uint8))", // remove fuse community distribution
"addOrUpdateRecipient((uint32,uint32,address,uint8))", // fuse distribution
"addOrUpdateRecipient((uint32,uint32,address,uint8))", // celo distribution
// "addOrUpdateRecipient((uint32,uint32,address,uint8))", // savings rewards distribution
"addOrUpdateRecipient((uint32,uint32,address,uint8))", // community pool distribution on celo
"mintTokens(uint256,address,address)", //mint G$ to bridge
"setFormula(address)", // upgrade feeformula
"transfer(address,uint256)" // transfer G$s from guardians to bridge
];
const proposalFunctionInputs = [
ethers.utils.defaultAbiCoder.encode(
["bytes32[]", "address[]"],
[
[
ethers.utils.keccak256(ethers.utils.toUtf8Bytes("MPBBRIDGE_CONTRACT")),
ethers.utils.keccak256(ethers.utils.toUtf8Bytes("MULTICHAIN_ROUTER")),
ethers.utils.keccak256(ethers.utils.toUtf8Bytes("MULTICHAIN_ANYGOODDOLLAR"))
],
[NEWBRIDGE, ethers.constants.AddressZero, ethers.constants.AddressZero]
]
), //setAddresses(bytes32[],address[])"
ethers.utils.defaultAbiCoder.encode(["address"], [newReserveImpl.address]),
ethers.utils.defaultAbiCoder.encode(["address"], [newFundmanagerImpl.address]),
ethers.utils.defaultAbiCoder.encode(["address"], [newDisthelperImpl.address]),
ethers.constants.HashZero,
//uint128 axelarBaseFeeUSD;uint128 bridgeExecuteGas;uint128 targetChainGasPrice;uint128 maxFee;uint128 minBalanceForFees;uint8 percentageToSellForFee;
//0.1$ base fee, 400000 bridge execute, 5 gwei gas price, 5e15 eth max fee, min balance 1e16, percentage 5%
ethers.utils.defaultAbiCoder.encode(
["uint128", "uint128", "uint128", "uint128", "uint128", "uint8"],
["100000000000000000", "400000", "5000000000", "5000000000000000", "10000000000000000", "5"]
),
ethers.utils.defaultAbiCoder.encode(
["uint32", "uint32", "address", "uint8"],
[0, 1, dao[networkName].GuardiansSafe, 3] //0% to guardians (contract transfer)
),
ethers.utils.defaultAbiCoder.encode(
["uint32", "uint32", "address", "uint8"],
[0, 122, dao[fuseNetwork].CommunitySafe, 0] //0% to community safe on celo(Fuse bridge)
),
ethers.utils.defaultAbiCoder.encode(
["uint32", "uint32", "address", "uint8"],
[4000, 122, dao[fuseNetwork].UBIScheme, 0] //40% chainId 122 ubischeme 0-fuse bridge
),
ethers.utils.defaultAbiCoder.encode(
["uint32", "uint32", "address", "uint8"],
[4000, 42220, dao[celoNetwork].UBIScheme, 2] //40% chainId 42220 ubischeme 2-axelar bridge
),
// ethers.utils.defaultAbiCoder.encode(
// ["uint32", "uint32", "address", "uint8"],
// [1000, 42220, dao[celoNetwork].GoodDollarMintBurnWrapper, 2] //10% chainId 42220 mintburnwrapper 2-axelar bridge
// ),
ethers.utils.defaultAbiCoder.encode(
["uint32", "uint32", "address", "uint8"],
[1000, 42220, dao[celoNetwork].CommunitySafe, 2] //10% chainId 42220 community treasury 2-axelar bridge
),
ethers.utils.defaultAbiCoder.encode(
["uint256", "address", "address"],
[MULTICHAIN_BALANCE, NEWBRIDGE, release["Avatar"]] // mint same amount locked on multichain to new bridge
),
ethers.utils.defaultAbiCoder.encode(
["address"],
[newFeeFormula.address] // set the new formula
),
ethers.utils.defaultAbiCoder.encode(
["address", "uint256"],
[NEWBRIDGE, GUARDIANS_BALANCE] // transfer G$ to bridge
)
];
if (isProduction) {
await executeViaSafe(
proposalContracts,
proposalEthValues,
proposalFunctionSignatures,
proposalFunctionInputs,
protocolSettings.guardiansSafe,
"mainnet"
);
} else {
//simulation or dev envs
await executeViaGuardian(
proposalContracts,
proposalEthValues,
proposalFunctionSignatures,
proposalFunctionInputs,
guardian,
networkName
);
}
//perform sanity checks on fork, for production we need to wait until everything executed
if (!isProduction) {
let fm = await ethers.getContractAt("GoodFundManager", release.GoodFundManager);
let dh = await ethers.getContractAt("DistributionHelper", release.DistributionHelper);
let r = await ethers.getContractAt("GoodReserveCDai", release.GoodReserveCDai);
console.log(await dh.distributionRecipients(0));
console.log(await dh.distributionRecipients(1));
console.log(await dh.distributionRecipients(2));
console.log(await dh.distributionRecipients(3));
console.log(await dh.distributionRecipients(4));
// console.log(await dh.distributionRecipients(5));
console.log(
"gd balances: guardians:",
await gd.balanceOf(guardian.address),
" bridge:",
await gd.balanceOf(NEWBRIDGE)
);
console.log("gdx/discount disabled", await r.gdxDisabled(), await r.discountDisabled());
let tx = await (await fm.collectInterest([], true)).wait();
console.log(tx.events);
}
};
export const upgradeSidechain = async sidechain => {
let [root] = await ethers.getSigners();
const isProduction = networkName.includes("production");
const isForkSimulation = networkName === "localhost";
let networkEnv = networkName.split("-")[0];
if (isForkSimulation) networkEnv = "production";
if (networkEnv === "fuse") networkEnv = "development";
if (sidechain === "celo") networkEnv = networkEnv + "-celo";
let release: { [key: string]: any } = dao[networkEnv];
let guardian = root;
//simulate on fork, make sure safe has enough eth to simulate txs
if (network.name === "localhost" || network.name === "fork") {
guardian = await ethers.getImpersonatedSigner(release.GuardiansSafe);
await root.sendTransaction({ value: ethers.constants.WeiPerEther.mul(3), to: guardian.address });
}
const NEWBRIDGE = release["MpbBridge"];
console.log({ networkEnv, NEWBRIDGE, guardian: guardian.address, isForkSimulation, isProduction });
const proposalContracts = [
release.NameService,
release.GoodDollarMintBurnWrapper, // give new bridge mint permissions
release.GoodDollarMintBurnWrapper, // remove multichain as minter
release.GoodDollarMintBurnWrapper, // unpause minting
release.GoodDollarMintBurnWrapper // set guardian
];
const proposalEthValues = proposalContracts.map(_ => 0);
const proposalFunctionSignatures = [
"setAddresses(bytes32[],address[])", // set new bridge name
"addMinter(address,uint256,uint256,uint32,uint256,uint256,uint32,bool)",
"revokeRole(bytes32,address)",
"unpause(bytes32)",
"grantRole(bytes32,address)"
];
const proposalFunctionInputs = [
ethers.utils.defaultAbiCoder.encode(
["bytes32[]", "address[]"],
[
[
ethers.utils.keccak256(ethers.utils.toUtf8Bytes("MPBBRIDGE_CONTRACT")),
ethers.utils.keccak256(ethers.utils.toUtf8Bytes("MULTICHAIN_ROUTER"))
],
[NEWBRIDGE, ethers.constants.AddressZero]
]
), //setAddresses(bytes32[],address[])"
ethers.utils.defaultAbiCoder.encode(
["address", "uint256", "uint256", "uint32", "uint256", "uint256", "uint32", "bool"],
[
NEWBRIDGE,
0,
sidechain === "celo" ? ethers.constants.WeiPerEther.mul(300).mul(1e6) : "30000000000",
5000,
0,
0,
0,
false
]
),
ethers.utils.defaultAbiCoder.encode(
["bytes32", "address"],
["0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6", release.MultichainRouter]
),
ethers.utils.defaultAbiCoder.encode(
["bytes32"],
["0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6"]
),
ethers.utils.defaultAbiCoder.encode(
["bytes32", "address"],
[
"0x55435dd261a4b9b3364963f7738a7a662ad9c84396d64be3365284bb7f0a5041",
"0x66582D24FEaD72555adaC681Cc621caCbB208324"
]
)
];
if (isProduction) {
await executeViaSafe(
proposalContracts,
proposalEthValues,
proposalFunctionSignatures,
proposalFunctionInputs,
release.GuardiansSafe,
sidechain
);
} else {
await executeViaGuardian(
proposalContracts,
proposalEthValues,
proposalFunctionSignatures,
proposalFunctionInputs,
guardian,
networkEnv
);
}
};
export const main = async () => {
prompt.start();
const { network } = await prompt.get(["network"]);
console.log("running step:", { network });
switch (network) {
case "celo":
await upgradeSidechain("celo");
break;
case "fuse":
await upgradeSidechain("fuse");
break;
case "mainnet":
await upgrade();
break;
}
};
if (process.argv[1].includes("gip-15")) main().catch(console.log);