UNPKG

@gooddollar/goodprotocol

Version:
846 lines (764 loc) 32.5 kB
/*** * Mainnet: * FIXES: * - prevent hacked funds burnFrom * - set GOOD rewards to 0 * - prevent untrusted contracts in goodfundmanager * - use bonding curve for actual cDAI balance (prevent the "buy" instead of "transferTo" used in hack to trick reserve into minting UBI from interest) * - fix reserve calculations of expansion/currentprice * - fix exit contribution calculations * - add requirement of guardians to approve on-chain proposals * - reserve should not trust exchange helper * - resere should not trust fundmanager for its starting balance * Changes: * - mainnet no longer main token chain. so bridge now mints/burns instead of lock/unlock * - require guardians to approve proposals * * PLAN: * - upgrade compoundvotingmachine * - upgrade reserve * - upgrade exchangeHelper * - upgrade goodfundmanager * - upgrade governance * - upgrade goodmarketmaker * - pause staking * - prevent fusebridge usage * - set GOOD rewards to 0 * - blacklist hacked accounts to prevent burn (transfer already blocked done via tax) * - upgrade/stop fuse bridge + withdraw funds * - withdraw funds from MPB bridge * - burn withdrawn funds * - upgrade MPB bridge contract to mint/burn * - give minting rights to the MPB (by adding it as scheme) * - switch fuse distribution to use lz bridge insted of deprecated fuse bridge * * Fuse: * * Changes: * - require guardians to approve proposals * PLAN: * - upgrade compoundvotingmachine * - prevent old fuse bridge usage * - upgrade MPB bridge contract * - give minting rights to the MPB (by adding it as scheme) * - remove mint rights to bridge given through mintburnwrapper * * Celo: * Changes: * - Upgrade MPB contract * - Deploy new distribution helper * * PLAN: * - upgrade MP bridge contract * - remove minting rights from bridge (since now it is lock/unlock) * - mint tokens to MPB to match G$ supply on Ethereum+Fuse-Minus locked funds * - deploy CeloDistributionHelper * - add recipients to distribution helper * - give minting rights to mento broker directly on token * - give minting rights to mento expansion controller directly on token * - create the mento G$/CUSD exchange * - set the expansion rate */ // add distributionhelper recipients import { network, ethers, upgrades } from "hardhat"; import { reset } from "@nomicfoundation/hardhat-network-helpers"; import { defaultsDeep, last } from "lodash"; import prompt from "prompt"; // import mpbDeployments from "@gooddollar/bridge-contracts/release/mpb.json" import { executeViaGuardian, executeViaSafe, verifyProductionSigner } from "../multichain-deploy/helpers"; import ProtocolSettings from "../../releases/deploy-settings.json"; import dao from "../../releases/deployment.json"; import { CeloDistributionHelper, Controller, FuseOldBridgeKill, GoodMarketMaker, IBancorExchangeProvider, IBroker, IGoodDollar, IGoodDollarExchangeProvider, IGoodDollarExpansionController, IMentoReserve, ProtocolUpgradeV4Mento } from "../../types"; import releaser from "../releaser"; let { name: networkName } = network; // hacker and hacked multichain bridge accounts const LOCKED_ACCOUNTS = [ "0xeC577447D314cf1e443e9f4488216651450DBE7c", "0xD17652350Cfd2A37bA2f947C910987a3B1A1c60d", "0x6738fA889fF31F82d9Fe8862ec025dbE318f3Fde" ]; // TODO: import from bridge-contracts package const mpbDeployments = { "1": [ { name: "mainnet", MessagePassingBridge_Implementation: { address: "0x618fae127b803eABf72f9e86a88A7505eEBf218a" } } ], "122": [ { name: "fuse", MessagePassingBridge_Implementation: { address: "0xFCd61ccB982ce77192E3D18a5AE3326DcE0B6874" } } ], "42220": [ { name: "celo", MessagePassingBridge_Implementation: { address: "0x2537f22E7B2D5d14E7f571fA67FCd846d73317f6" } } ] }; const isSimulation = network.name === "hardhat" || network.name === "fork" || network.name === "localhost"; export const upgradeMainnet = async (network, checksOnly) => { const isProduction = networkName.includes("production"); let [root, ...signers] = await ethers.getSigners(); if (isProduction) verifyProductionSigner(root); let guardian = root; //simulate produciton on fork if (isSimulation) { 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 (isSimulation && !checksOnly) { // await reset("https://cloudflare-eth.com/"); guardian = await ethers.getImpersonatedSigner(protocolSettings.guardiansSafe); 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()); console.log("got signers:", { networkName, root: root.address, guardian: guardian.address, balance: rootBalance, guardianBalance: guardianBalance }); const gd = (await ethers.getContractAt("IGoodDollar", release.GoodDollar)) as IGoodDollar; const fuseBridgeBalance = await gd.balanceOf(release.ForeignBridge); const mpbBridgeBalance = await gd.balanceOf(release.MpbBridge); const totalToBurn = fuseBridgeBalance.add(mpbBridgeBalance); const mpbImplementation = mpbDeployments["1"].find(_ => _.name === "mainnet")["MessagePassingBridge_Implementation"] .address; // test blacklisting to prevent burn by hacker if (isSimulation && !checksOnly) { const locked = await ethers.getImpersonatedSigner(LOCKED_ACCOUNTS[0]); const tx = await gd .connect(locked) .burn("10", { maxFeePerGas: 30e9, maxPriorityFeePerGas: 1e9, gasLimit: 200000 }) .then(_ => _.wait()) .then(_ => _.status) .catch(e => e); console.log("Burn tx before:", tx); } const startSupply = await gd.totalSupply(); console.log( `Total bridge balances to burn: Total Supply: ${startSupply.toNumber() / 1e2} Fuse: ${ fuseBridgeBalance.toNumber() / 1e2 } Celo: ${mpbBridgeBalance.toNumber() / 1e2} Total: ${totalToBurn.toNumber() / 1e2}` ); console.log("executing proposals"); // const reserveImpl = await ethers.deployContract("GoodReserveCDai"); // const goodFundManagerImpl = await ethers.deployContract("GoodFundManager"); // const exchangeHelperImpl = await ethers.deployContract("ExchangeHelper"); // const stakersDistImpl = await ethers.deployContract("StakersDistribution"); // const govImpl = await ethers.deployContract("CompoundVotingMachine"); // const distHelperImpl = await ethers.deployContract("DistributionHelper"); // const marketMakerImpl = await ethers.deployContract("GoodMarketMaker"); const reserveImpl = { address: "0x18BcdF79A724648bF34eb06701be81bD072A2384" }; const goodFundManagerImpl = { address: "0xAACbaaB8571cbECEB46ba85B5981efDB8928545e" }; const exchangeHelperImpl = { address: "0x24614Ad257F4d09fCcaec024c65C40C060E73e9D" }; const stakersDistImpl = { address: "0x92c69a12C2Ffc54AfCfa320bE7305ffd0f5782E0" }; const govImpl = { address: "0x807D0066d60a0a8312B6D191f60a6938a527E971" }; const distHelperImpl = { address: "0xAE2cd2a9513215961D344a2581DcAB678598eEDf" }; const marketMakerImpl = { address: "0x8520633e40574e9550f6a0436b0F8D56F3b99BD0" }; console.log("deployed impls", { reserveImpl: reserveImpl.address, goodFundManagerImpl: goodFundManagerImpl.address, exchangeHelperImpl: exchangeHelperImpl.address, stakersDistImpl: stakersDistImpl.address, govImpl: govImpl.address, distHelperImpl: distHelperImpl.address, marketMakerImpl: marketMakerImpl.address }); const proposalActions = [ [ release.StakersDistribution, "setMonthlyReputationDistribution(uint256)", ethers.utils.defaultAbiCoder.encode(["uint256"], [0]), "0" ], //set GOOD rewards to 0 [ release.GoodReserveCDai, "setReserveRatioDailyExpansion(uint256,uint256)", ethers.utils.defaultAbiCoder.encode(["uint256", "uint256"], [999711382710978, 1e15]), 0 ], //expansion ratio [ release.GoodReserveCDai, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [reserveImpl.address]), "0" ], //upgrade reserve [ release.GoodFundManager, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [goodFundManagerImpl.address]), "0" ], //upgrade fundmanager [ release.ExchangeHelper, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [exchangeHelperImpl.address]), "0" ], //upgrade exchangehelper [release.ExchangeHelper, "setAddresses()", "0x", "0"], // activate upgrade changes [ release.DistributionHelper, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [distHelperImpl.address]), "0" ], //upgrade disthelper [ release.StakersDistribution, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [stakersDistImpl.address]), "0" ], //upgrade stakers dist [ release.GoodMarketMaker, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [marketMakerImpl.address]), "0" ], //upgrade mm [ release.CompoundVotingMachine, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [govImpl.address]), "0" ], // upgrade gov [release.StakingContractsV3[0][0], "pause(bool)", ethers.utils.defaultAbiCoder.encode(["bool"], [true]), "0"], // pause staking [release.StakingContractsV3[1][0], "pause(bool)", ethers.utils.defaultAbiCoder.encode(["bool"], [true]), "0"], // pause staking [ release.ForeignBridge, "setExecutionDailyLimit(uint256)", ethers.utils.defaultAbiCoder.encode(["uint256"], [0]), "0" ], // prevent from using [ release.ForeignBridge, "claimTokens(address,address)", ethers.utils.defaultAbiCoder.encode(["address", "address"], [release.GoodDollar, release.Avatar]), "0" ], // claim bridge tokens to avatar [ release.Identity, "addBlacklisted(address)", ethers.utils.defaultAbiCoder.encode(["address"], [LOCKED_ACCOUNTS[0]]), "0" ], // set locked G$ accounts as blacklisted [ release.Identity, "addBlacklisted(address)", ethers.utils.defaultAbiCoder.encode(["address"], [LOCKED_ACCOUNTS[1]]), "0" ], // set locked G$ accounts as blacklisted [ release.Identity, "addBlacklisted(address)", ethers.utils.defaultAbiCoder.encode(["address"], [LOCKED_ACCOUNTS[2]]), "0" ], // set locked G$ accounts as blacklisted [ release.MpbBridge, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [mpbImplementation]), "0" ], // mpb upgrade [ release.MpbBridge, "withdraw(address,uint256)", ethers.utils.defaultAbiCoder.encode(["address", "uint256"], [release.GoodDollar, 0]), "0" ], // claim bridge tokens to avatar [release.GoodDollar, "burn(uint256)", ethers.utils.defaultAbiCoder.encode(["uint256"], [totalToBurn]), "0"], // burn tokens [ release.Controller, "registerScheme(address,bytes32,bytes4,address)", ethers.utils.defaultAbiCoder.encode( ["address", "bytes32", "bytes4", "address"], [ release.MpbBridge, //scheme ethers.constants.HashZero, //paramshash "0x00000001", //permissions - minimal release.Avatar ] ), "0" ], //minting rigts to our bridge [ release.DistributionHelper, "addOrUpdateRecipient((uint32,uint32,address,uint8))", ethers.utils.defaultAbiCoder.encode( ["uint32", "uint32", "address", "uint8"], [0, 122, dao["production"].UBIScheme, 1] //0% chainId 122 ubischeme 1-lz bridge ), "0" ], // switch to lz bridge for fuse [ release.GoodReserveCDai, "grantRole(bytes32,address)", ethers.utils.defaultAbiCoder.encode( ["bytes32", "address"], [ "0x65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a", //pauser role release.Avatar ] ), "0" ] // give avatar reserve pauser role ]; const [proposalContracts, proposalFunctionSignatures, proposalFunctionInputs, proposalEthValues] = [ proposalActions.map(_ => _[0]), proposalActions.map(_ => _[1]), proposalActions.map(_ => _[2]), proposalActions.map(_ => _[3]) ]; if (isProduction) { await executeViaSafe( proposalContracts, proposalEthValues, proposalFunctionSignatures, proposalFunctionInputs, protocolSettings.guardiansSafe, "mainnet" ); } else if (!checksOnly) { //simulation or dev envs await executeViaGuardian( proposalContracts, proposalEthValues, proposalFunctionSignatures, proposalFunctionInputs, guardian, networkName ); } if (isSimulation) { await mainnetPostChecks(totalToBurn, startSupply); } }; const mainnetPostChecks = async (totalToBurn, startSupply) => { networkName = "production-mainnet"; let release: { [key: string]: any } = dao[networkName]; let [root, ...signers] = await ethers.getSigners(); const gd = await ethers.getContractAt("IGoodDollar", release.GoodDollar); const locked = await ethers.getImpersonatedSigner(LOCKED_ACCOUNTS[0]); const tx = await gd .connect(locked) .burn("10", { maxFeePerGas: 30e9, maxPriorityFeePerGas: 1e9, gasLimit: 200000 }) .then(_ => _.wait()) .then(_ => _.status) .catch(e => e); console.log("Burn tx after should fail:", tx); const finalSupply = await gd.totalSupply(); const burnOk = finalSupply.add(totalToBurn).eq(startSupply); console.log("Burn check:", burnOk ? "Success" : "Failed"); const mm = (await ethers.getContractAt("GoodMarketMaker", release.GoodMarketMaker)) as GoodMarketMaker; const newExpansion = await mm.reserveRatioDailyExpansion(); console.log( "new expansion set:", newExpansion, newExpansion.mul(1e15).div(ethers.utils.parseEther("1000000000")).toNumber() / 1e15 === 0.999711382710978 ); const [mpbBalance, fuseBalance] = await Promise.all([ gd.balanceOf(release.MpbBridge), gd.balanceOf(release.ForeignBridge) ]); console.log("bridges shouuld have 0 balance as tokens have been burned", { mpbBalance, fuseBalance }); }; export const upgradeFuse = async network => { let [root] = await ethers.getSigners(); const isProduction = networkName.includes("production"); let networkEnv = networkName.split("-")[0]; if (isSimulation) networkEnv = "production"; let release: { [key: string]: any } = dao[networkEnv]; let guardian = root; //simulate on fork, make sure safe has enough eth to simulate txs if (isSimulation) { // await reset("https://rpc.fuse.io"); guardian = await ethers.getImpersonatedSigner(release.GuardiansSafe); await root.sendTransaction({ value: ethers.constants.WeiPerEther.mul(3), to: guardian.address }); } const mpbImplementation = mpbDeployments["122"].find(_ => _.name === "fuse")["MessagePassingBridge_Implementation"] .address; const govImpl = await ethers.getContractAt("CompoundVotingMachine", "0x9373046bbC6D381129B49aC3334881390df1CB13"); //await ethers.deployContract("CompoundVotingMachine"); const killBridge = (await ethers.getContractAt( "FuseOldBridgeKill", "0x4b93275D500929c2E0146D4fda0f411550C63eFC" )) as FuseOldBridgeKill; //(await ethers.deployContract("FuseOldBridgeKill")) as FuseOldBridgeKill; const ctrl = await ethers.getContractAt("Controller", release.Controller); console.log({ networkEnv, mpbImplementation, guardian: guardian.address, isSimulation, isProduction }); const proposalActions = [ [ release.HomeBridge, "upgradeToAndCall(uint256,address,bytes)", // upgrade and call end ethers.utils.defaultAbiCoder.encode( ["uint256", "address", "bytes"], [2, killBridge.address, killBridge.interface.encodeFunctionData("end")] ), "0" ], // prevent from using [ release.CompoundVotingMachine, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [govImpl.address]), "0" ], //upgrade [ release.MpbBridge, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [mpbImplementation]), "0" ], //upgrade [ release.Controller, "registerScheme(address,bytes32,bytes4,address)", //make sure mpb is a registered scheme so it can mint G$ tokens ethers.utils.defaultAbiCoder.encode( ["address", "bytes32", "bytes4", "address"], [ release.MpbBridge, //scheme ethers.constants.HashZero, //paramshash "0x00000001", //permissions - minimal release.Avatar ] ), "0" ], // set mpb as minter = add as scheme [ release.GoodDollarMintBurnWrapper, "revokeRole(bytes32,address)", ethers.utils.defaultAbiCoder.encode( ["bytes32", "address"], [ethers.utils.keccak256(ethers.utils.toUtf8Bytes("MINTER_ROLE")), release.MpbBridge] ), "0" ] //remove bridge minting rights via wrapper ]; const [proposalContracts, proposalFunctionSignatures, proposalFunctionInputs, proposalEthValues] = [ proposalActions.map(_ => _[0]), proposalActions.map(_ => _[1]), proposalActions.map(_ => _[2]), proposalActions.map(_ => _[3]) ]; if (isProduction) { await executeViaSafe( proposalContracts, proposalEthValues, proposalFunctionSignatures, proposalFunctionInputs, release.GuardiansSafe, "fuse" ); } else { await executeViaGuardian( proposalContracts, proposalEthValues, proposalFunctionSignatures, proposalFunctionInputs, guardian, networkEnv ); } if (isSimulation) { const isMPBScheme = await ctrl.isSchemeRegistered(release.MpbBridge, release.Avatar); const isFuseBridge = await ctrl.isSchemeRegistered(release.HomeBridge, release.Avatar); console.log("MPB scheme registration check:", isMPBScheme ? "Success" : "Failed"); console.log("Fuse bridge scheme de-registration check:", !isFuseBridge ? "Success" : "Failed"); } }; export const upgradeCelo = async (network, checksOnly) => { let [root] = await ethers.getSigners(); const isProduction = networkName.includes("production"); let networkEnv = networkName; if (isSimulation) networkEnv = network; let release: { [key: string]: any } = dao[networkEnv]; let guardian = root; console.log("signer:", root.address, { networkEnv, isSimulation, isProduction, release }); const cusd = await ethers.getContractAt("IERC20", release.CUSD); const gd = await ethers.getContractAt("GoodDollar", release.GoodDollar); const mentoReserve = (await ethers.getContractAt("IMentoReserve", release.MentoReserve)) as IMentoReserve; const mentoExchange = (await ethers.getContractAt( "IBancorExchangeProvider", release.MentoExchangeProvider )) as IBancorExchangeProvider; //simulate on fork, make sure safe has enough eth to simulate txs let DIST_HELPER_MIN_CELO_BALANCE = ethers.utils.parseEther("2"); if (isSimulation && !checksOnly) { DIST_HELPER_MIN_CELO_BALANCE = ethers.utils.parseEther("0.1"); // await reset("https://rpc.ankr.com/celo"); await root.sendTransaction({ value: ethers.utils.parseEther("0.5"), to: release.Avatar }); const avatar = await ethers.getImpersonatedSigner(release.Avatar); const reserveOwner = await ethers.getImpersonatedSigner(await mentoReserve.owner()); const eids = await mentoExchange.getExchangeIds(); if (eids.length > 0) { await mentoExchange.connect(avatar).destroyExchange(eids[0], 0); } const devCUSD = await ethers.getContractAt( [ "function mint(address,uint) external returns (uint)", "function setValidators(address) external", "function owner() view returns(address)" ], release.CUSD ); console.log("minting devCUSD"); const cusdOwner = await ethers.getImpersonatedSigner(await devCUSD.owner()); await root.sendTransaction({ value: ethers.utils.parseEther("0.5"), to: cusdOwner.address }); await devCUSD.connect(cusdOwner).setValidators(release.Avatar).catch(console.log); await devCUSD.connect(avatar).mint(root.address, ethers.utils.parseEther("2000000")).catch(console.log); console.log("transfering cusd to reserve"); await cusd.connect(root).transfer(release.MentoReserve, ethers.utils.parseEther("200000")); guardian = await ethers.getImpersonatedSigner(release.GuardiansSafe); await root.sendTransaction({ value: ethers.utils.parseEther("0.5"), to: guardian.address }); } else if (!isProduction && !checksOnly) { DIST_HELPER_MIN_CELO_BALANCE = ethers.utils.parseEther("0.1"); const mentoReserve = (await ethers.getContractAt("IMentoReserve", release.MentoReserve)) as IMentoReserve; const ctrl = (await ethers.getContractAt("Controller", release.Controller)) as Controller; const eids = await mentoExchange.getExchangeIds(); if (eids.length > 0) { await ( await ctrl.genericCall( mentoExchange.address, mentoExchange.interface.encodeFunctionData("destroyExchange", [eids[0], 0]), release.Avatar, 0 ) ).wait(); } const devCUSD = await ethers.getContractAt( ["function mint(address,uint) external returns (uint)", "function setValidators(address) external"], release.CUSD ); await ( await ctrl.genericCall( devCUSD.address, devCUSD.interface.encodeFunctionData("setValidators", [release.Avatar]), release.Avatar, 0 ) ).wait(); await ( await ctrl.genericCall( devCUSD.address, devCUSD.interface.encodeFunctionData("mint", [root.address, ethers.utils.parseEther("2000000")]), release.Avatar, 0 ) ).wait(); if ((await cusd.balanceOf(release.MentoReserve)).lt(ethers.utils.parseEther("200000"))) { await cusd.transfer(release.MentoReserve, ethers.utils.parseEther("200000")); } } const mpbImplementation = mpbDeployments["42220"].find(_ => _.name === "celo")["MessagePassingBridge_Implementation"] .address; const bridgeLocked = ["0xa3247276DbCC76Dd7705273f766eB3E8a5ecF4a5", "0xD5D11eE582c8931F336fbcd135e98CEE4DB8CCB0"]; const locked = [ "0xD17652350Cfd2A37bA2f947C910987a3B1A1c60d", "0xeC577447D314cf1e443e9f4488216651450DBE7c", "0x6738fA889fF31F82d9Fe8862ec025dbE318f3Fde" ]; const ethprovider = new ethers.providers.JsonRpcProvider("https://rpc.flashbots.net"); const fuseprovider = new ethers.providers.JsonRpcProvider("https://rpc.fuse.io"); const TOTAL_LOCKED = ( await Promise.all( locked .concat(bridgeLocked) .map(_ => gd.connect(ethprovider).attach(dao["production-mainnet"].GoodDollar).balanceOf(_)) ) ).reduce((prev, cur) => prev.add(cur), ethers.constants.Zero); const TOTAL_SUPPLY_ETH = await gd.connect(ethprovider).attach(dao["production-mainnet"].GoodDollar).totalSupply(); const TOTAL_SUPPLY_FUSE = await gd.connect(fuseprovider).attach(dao["production"].GoodDollar).totalSupply(); const TOTAL_SUPPLY_CELO = await gd.totalSupply(); const TOTAL_GLOBAL_SUPPLY = TOTAL_SUPPLY_ETH.add(TOTAL_SUPPLY_FUSE) .sub(TOTAL_LOCKED) .mul(ethers.BigNumber.from("10000000000000000")) //convert to 18 decimals .add(TOTAL_SUPPLY_CELO); const exchangeParams = [release.CUSD, release.GoodDollar, 0, 0, 0, 0]; //address reserveAsset;address tokenAddress;uint256 tokenSupply;uint256 reserveBalance;uint32 reserveRatio;uint32 exitConribution; console.log({ networkEnv, mpbImplementation, guardian: guardian.address, isSimulation, isProduction, release, TOTAL_GLOBAL_SUPPLY }); const mentoUpgrade = release.MentoUpgradeHelper ? ((await ethers.getContractAt("ProtocolUpgradeV4Mento", release.MentoUpgradeHelper)) as ProtocolUpgradeV4Mento) : await ethers.deployContract("ProtocolUpgradeV4Mento", [release.Avatar]); let distHelper = release.CeloDistributionHelper ? ((await ethers.getContractAt("CeloDistributionHelper", release.CeloDistributionHelper)) as CeloDistributionHelper) : ((await upgrades.deployProxy( await ethers.getContractFactory("CeloDistributionHelper"), [release.NameService, "0x00851A91a3c4E9a4c1B48df827Bacc1f884bdE28"], //static oracle for uniswap { initializer: "initialize" } )) as CeloDistributionHelper); release.MentoUpgradeHelper = mentoUpgrade.address; release.CeloDistributionHelper = distHelper.address; if (!isSimulation) { releaser(release, networkEnv); } console.log("deployed mentoUpgrade", { distribuitonHelper: distHelper.address, mentoUpgrade: mentoUpgrade.address, distHelperAvatar: await distHelper.avatar() }); const proposalContracts = [ release.CeloDistributionHelper, //set fee settings release.CeloDistributionHelper, //add ubi recipient release.CeloDistributionHelper, //add community treasury recipient release.MpbBridge, // upgrade release.MpbBridge && release.GoodDollarMintBurnWrapper, // remove minting rights from bridge release.GoodDollar, // set mento broker as minter release.GoodDollar, // set reserve expansion controller as minter release.Controller, // register upgrade contract mentoUpgrade.address // create the exchange + set expansion rate ]; const proposalEthValues = proposalContracts.map(_ => 0); const proposalFunctionSignatures = [ "setFeeSettings((uint128,uint128,uint8,uint8))", "addOrUpdateRecipient((uint32,uint32,address,uint8))", "addOrUpdateRecipient((uint32,uint32,address,uint8))", "upgradeTo(address)", "revokeRole(bytes32,address)", // mpb is now lock/unlock doesnt need minting "addMinter(address)", "addMinter(address)", "registerScheme(address,bytes32,bytes4,address)", "upgrade(address,(address,address,uint256,uint256,uint32,uint32),address,address,address,uint256)" //Controller _controller,PoolExchange memory _exchange,address _mentoExchange,address _mentoController, address _distHelper ]; console.log("preparing inputs..."); const proposalFunctionInputs = [ //uint128 maxFee;uint128 minBalanceForFees;uint8 percentageToSellForFee; //2 celo max fee for lz bridge, min balance 2 celo, max percentage to sell 1%, max slippage 5% ethers.utils.defaultAbiCoder.encode( ["uint128", "uint128", "uint8", "uint8"], [ethers.utils.parseEther("2"), DIST_HELPER_MIN_CELO_BALANCE, "1", "5"] ), ethers.utils.defaultAbiCoder.encode(["uint32", "uint32", "address", "uint8"], [9000, 42220, release.UBIScheme, 1]), // ubi pool recipient ethers.utils.defaultAbiCoder.encode( ["uint32", "uint32", "address", "uint8"], [1000, 42220, release.CommunitySafe, 1] ), //community treasury recipient ethers.utils.defaultAbiCoder.encode(["address"], [mpbImplementation]), release.MpbBridge && ethers.utils.defaultAbiCoder.encode( ["bytes32", "address"], [ethers.utils.keccak256(ethers.utils.toUtf8Bytes("MINTER_ROLE")), release.MpbBridge] ), ethers.utils.defaultAbiCoder.encode(["address"], [release.MentoBroker]), ethers.utils.defaultAbiCoder.encode(["address"], [release.MentoExpansionController]), ethers.utils.defaultAbiCoder.encode( ["address", "bytes32", "bytes4", "address"], [mentoUpgrade.address, ethers.constants.HashZero, "0x0000001f", release.Avatar] ), ethers.utils.defaultAbiCoder.encode( ["address", "(address,address,uint256,uint256,uint32,uint32)", "address", "address", "address", "uint256"], [ release.Controller, exchangeParams, release.MentoExchangeProvider, release.MentoExpansionController, release.CeloDistributionHelper, TOTAL_GLOBAL_SUPPLY ] ) ]; console.log({ exchangeParams, mentoExchange: release.MentoExchangeProvider }); console.log("executing upgrade...", { proposalContracts, proposalFunctionInputs, proposalFunctionSignatures }); if (isProduction && !checksOnly) { await executeViaSafe( proposalContracts, proposalEthValues, proposalFunctionSignatures, proposalFunctionInputs, release.GuardiansSafe, "celo" ); } else if (!checksOnly) { await executeViaGuardian( proposalContracts, proposalEthValues, proposalFunctionSignatures, proposalFunctionInputs, guardian, networkEnv ); } if (isSimulation || !isProduction) { const swapper = networkEnv.includes("production") ? await ethers.getImpersonatedSigner("0x66582D24FEaD72555adaC681Cc621caCbB208324") : root; const supplyAfter = await (await ethers.getContractAt("IGoodDollar", release.GoodDollar)).totalSupply(); console.log("Supply after upgrade:", { supplyAfter, TOTAL_GLOBAL_SUPPLY }); const isBrokerMinter = await gd.isMinter(release.MentoBroker); const isExpansionMinter = await gd.isMinter(release.MentoExpansionController); const mentoExchange = await ethers.getContractAt("IBancorExchangeProvider", release.MentoExchangeProvider); const mentoBroker = (await ethers.getContractAt("IBroker", release.MentoBroker)) as IBroker; const eids = await mentoExchange.getExchangeIds(); const exchange = await mentoExchange.getPoolExchange(eids[0]); const price = (await mentoExchange.currentPrice(eids[0])) / 1e18; console.log("current price:", price); console.log("Exchange:", exchange, eids[0]); console.log("Broker minter check:", isBrokerMinter ? "Success" : "Failed"); console.log("Expansion minter check:", isExpansionMinter ? "Success" : "Failed"); console.log("balance before swap:", await gd.balanceOf(swapper.address), await cusd.balanceOf(swapper.address)); await cusd.connect(swapper).approve(release.MentoBroker, ethers.utils.parseEther("1000")); await mentoBroker .connect(swapper) .swapIn(mentoExchange.address, eids[0], cusd.address, gd.address, ethers.utils.parseEther("1000"), 0) .then(_ => _.wait()); console.log( "Balance after swap:", swapper.address, await gd.balanceOf(swapper.address), await cusd.balanceOf(swapper.address) ); const mentomint = (await ethers.getContractAt( "IGoodDollarExpansionController", release.MentoExpansionController )) as IGoodDollarExpansionController; await cusd.connect(swapper).approve(mentomint.address, ethers.utils.parseEther("1000")); const tx = await ( await mentomint.connect(swapper).mintUBIFromInterest(eids[0], ethers.utils.parseEther("1000")) ).wait(); console.log( "mint from interest:", tx.events.find(_ => _.event === "InterestUBIMinted").args.amount.toString() / 1e18 ); console.log("price after interest mint:", (await mentoExchange.currentPrice(eids[0])) / 1e18); const distTx = await (await distHelper.onDistribution(0, { gasLimit: 2000000 })).wait(); const { distributionRecipients, distributed } = distTx.events.find(_ => _.event === "Distribution").args; console.log("Distribution events:", distributionRecipients, distributed, distTx.events.length); const bridgeBalance = await gd.balanceOf("0xa3247276DbCC76Dd7705273f766eB3E8a5ecF4a5"); console.log("Brigde balance should equal other chains total supply:", { bridgeBalance, isEqual: bridgeBalance.eq(TOTAL_GLOBAL_SUPPLY.sub(TOTAL_SUPPLY_CELO)) }); } }; export const main = async () => { prompt.start(); const { network } = await prompt.get(["network"]); console.log("running step:", { network }); const chain = last(network.split("-")); switch (chain) { case "mainnet": await upgradeMainnet(network, true); break; case "fuse": await upgradeFuse(network); break; case "celo": await upgradeCelo(network, true); break; } }; main().catch(console.log);