/***
 * 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);
