/**
 * Mainnet:
 * 0. deploy nameService
 * 1. deploy votingmachine  +  reputation
 * 2. deploy Reserve, MarketMaker
 * 3. deploy FundManager
 * 4. deploy ubi staking contracts
 */

import { network, ethers, upgrades, run } from "hardhat";
import { isFunction, get, omitBy } from "lodash";
import { getImplementationAddress } from "@openzeppelin/upgrades-core";
import pressAnyKey from "press-any-key";
import { AaveStakingFactory, CompoundStakingFactory, ProxyFactory1967 } from "../../types";
import SchemeRegistrarABI from "@gooddollar/goodcontracts/build/contracts/SchemeRegistrar.json";
import releaser from "../releaser";
import {
  GReputation,
  SchemeRegistrar,
  CompoundVotingMachine,
  ProtocolUpgrade,
  ProtocolUpgradeFuse,
  NameService
} from "../../types";
import { getFounders } from "../getFounders";
import OldDAO from "../../releases/olddao.json";

import ProtocolSettings from "../../releases/deploy-settings.json";
import { keccak256 } from "ethers/lib/utils";

let GAS_SETTINGS: any = {
  maxPriorityFeePerGas: ethers.utils.parseUnits("1", "gwei"),
  maxFeePerGas: ethers.utils.parseUnits("50", "gwei"),
  gasLimit: 30000000
};

let totalGas = 0;
const gasUsage = {};
const countTotalGas = async (tx, name) => {
  let res = tx;
  if (tx.deployTransaction) tx = tx.deployTransaction;
  if (tx.wait) res = await tx.wait();
  if (res.gasUsed) {
    totalGas += parseInt(res.gasUsed);
    gasUsage[name] = gasUsage[name] || 0;
    gasUsage[name] += parseInt(res.gasUsed);
  } else console.log("no gas data", { res, tx });
};

console.log({
  network: network.name,
  upgrade: process.env.UPGRADE
});
const { name } = network;

export const main = async (networkName = name, isPerformUpgrade = true, olddao?): Promise<{ [key: string]: any }> => {
  const isProduction = networkName.startsWith("production");
  if (isProduction && networkName.includes("mainnet")) {
    GAS_SETTINGS.gasLimit = 6000000;
    GAS_SETTINGS.maxFeePerGas = ethers.utils.parseUnits("80", "gwei");
  } else if (network.config.chainId === 122) {
    //case we are on fusefuse
    GAS_SETTINGS = {
      gasLimit: 6000000,
      gasPrice: ethers.utils.parseUnits("1", "gwei")
    };
  } else if (network.config.chainId === 3 || network.config.chainId === 42) {
    GAS_SETTINGS = {
      maxPriorityFeePerGas: ethers.utils.parseUnits("1", "gwei"),
      maxFeePerGas: ethers.utils.parseUnits("10", "gwei"),
      gasLimit: 6000000
    };
  }

  const isDappTest = networkName.startsWith("dapptest");
  const isTest = network.name === "hardhat" || isDappTest;
  const isCoverage = process.env.CODE_COVERAGE;
  const isDevelop = !isProduction;
  const isMainnet = networkName.includes("mainnet");
  let protocolSettings = {
    ...ProtocolSettings["default"],
    ...ProtocolSettings[networkName]
  };
  console.log(`networkName ${networkName}`, {
    isTest,
    isDappTest,
    isCoverage,
    isMainnet,
    isDevelop
  });
  const dao = olddao || OldDAO[networkName];
  const fse = require("fs-extra");
  const ProtocolAddresses = await fse.readJson("releases/deployment.json");
  const newfusedao = await ProtocolAddresses[networkName.replace(/\-mainnet/, "")];
  const newdao = ProtocolAddresses[networkName] || {};

  let [root, proxyDeployer] = await ethers.getSigners();

  let avatar = dao.Avatar;
  let controller = dao.Controller;
  let repStateId = isMainnet ? "fuse" : "rootState";

  const founders = await getFounders(networkName);

  console.log(`root: ${root.address} founders: ${founders.map(_ => _.address)}`);

  const compoundTokens = [
    {
      name: "cdai",
      address: (protocolSettings.compound != undefined && protocolSettings.compound.cdai) || dao.cDAI,
      usdOracle: (protocolSettings.compound != undefined && protocolSettings.compound.daiUsdOracle) || dao.DAIUsdOracle,
      compUsdOracle:
        (protocolSettings.compound != undefined && protocolSettings.compound.compUsdOracle) || dao.COMPUsdOracle,
      swapPath: []
    }
  ];

  const aaveTokens = [
    {
      name: "usdc",
      address: protocolSettings.aave.usdc || dao.USDC,
      usdOracle: protocolSettings.aave.usdcUsdOracle || dao.USDCUsdOracle,
      aaveUsdOracle: protocolSettings.aave.aaveUsdOracle || dao.AAVEUsdOracle,
      swapPath: [get(protocolSettings, "aave.usdc", dao.USDC), get(protocolSettings, "compound.dai", dao.DAI)]
    }
  ];

  let release: { [key: string]: any } = newdao;

  const toDeployUpgradable = [
    {
      network: "mainnet",
      name: "NameService",
      args: [
        controller,
        [
          "CONTROLLER",
          "AVATAR",
          "IDENTITY",
          "GOODDOLLAR",
          "CONTRIBUTION_CALCULATION",
          "BANCOR_FORMULA",
          "DAI",
          "CDAI",
          "COMP",
          "BRIDGE_CONTRACT",
          "UNISWAP_ROUTER",
          "GAS_PRICE_ORACLE",
          "DAI_ETH_ORACLE",
          "ETH_USD_ORACLE"
        ].map(_ => ethers.utils.keccak256(ethers.utils.toUtf8Bytes(_))),
        [
          controller,
          avatar,
          dao.Identity,
          dao.GoodDollar,
          dao.Contribution,
          protocolSettings.bancor || dao.BancorFormula,
          get(protocolSettings, "compound.dai", dao.DAI),
          get(protocolSettings, "compound.cdai", dao.cDAI),
          get(protocolSettings, "compound.comp", dao.COMP),
          dao.ForeignBridge,
          protocolSettings.uniswapRouter || dao.UniswapRouter,
          !isMainnet || dao.GasPriceOracle || protocolSettings.chainlink.gasPrice, //should fail if missing only on mainnet
          !isMainnet || dao.DAIEthOracle || protocolSettings.chainlink.dai_eth,
          !isMainnet || dao.ETHUsdOracle || protocolSettings.chainlink.eth_usd
        ]
      ]
    },
    {
      network: "fuse",
      name: "NameService",
      args: [
        controller,
        ["CONTROLLER", "AVATAR", "IDENTITY", "GOODDOLLAR", "BRIDGE_CONTRACT"].map(_ =>
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes(_))
        ),
        [controller, avatar, dao.Identity, dao.GoodDollar, dao.HomeBridge]
      ]
    },
    {
      network: "both",
      name: "GReputation",
      initializer: "initialize(address, string, bytes32, uint256)",
      args: [
        () => get(release, "NameService", newdao.NameService),
        repStateId,
        protocolSettings.governance.gdaoAirdrop, //should fail on real deploy if not set
        protocolSettings.governance.gdaoTotalSupply //should fail on real deploy if not set
      ]
    },
    {
      network: "both",
      name: "CompoundVotingMachine",
      args: [
        () => get(release, "NameService", newdao.NameService),
        protocolSettings.governance.proposalVotingPeriod,
        protocolSettings.governance.guardian || root.address,
        () => get(release, "GReputation", newdao.GReputation)
      ]
    },
    {
      network: "mainnet",
      name: "GoodMarketMaker",
      args: [
        () => get(release, "NameService", newdao.NameService),
        protocolSettings.expansionRatio.nom,
        protocolSettings.expansionRatio.denom
      ]
    },
    {
      network: "mainnet",
      name: "GoodReserveCDai",
      initializer: "initialize(address, bytes32)",
      args: [() => get(release, "NameService", newdao.NameService), protocolSettings.gdxAirdrop]
    },
    {
      network: "mainnet",
      name: "ExchangeHelper",
      initializer: "initialize(address)",
      args: [() => get(release, "NameService", newdao.NameService)]
    },
    {
      network: "mainnet",
      name: "GoodFundManager",
      args: [() => get(release, "NameService", newdao.NameService)]
    },
    {
      network: "mainnet",
      name: "StakersDistribution",
      args: [() => get(release, "NameService", newdao.NameService)]
    },
    {
      network: "fuse",
      name: "ClaimersDistribution",
      args: [() => get(release, "NameService", newdao.NameService)]
    },
    {
      network: "fuse",
      name: "GovernanceStaking",
      args: [() => get(release, "NameService", newdao.NameService)],
      isUpgradable: false
    },
    {
      network: "fuse",
      name: "UBIScheme",
      initializer: "initialize(address, address, uint256)",
      args: [() => get(release, "NameService", newdao.NameService), dao.FirstClaimPool, 14]
    },
    {
      network: "mainnet",
      name: "ProtocolUpgrade",
      args: [dao.Controller, root.address],
      isUpgradable: false,
      initializer: null
    },
    {
      network: "fuse",
      name: "ProtocolUpgradeFuse",
      args: [dao.Controller, root.address],
      isUpgradable: false
    },
    {
      network: "mainnet",
      name: "UniswapV2SwapHelper",
      args: [],
      isUpgradable: false
    },
    {
      network: "mainnet",
      name: "CompoundStakingFactory",
      args: [],
      isUpgradable: false,
      libraries: ["UniswapV2SwapHelper"]
    },
    {
      network: "mainnet",
      name: "AaveStakingFactory",
      args: [],
      isUpgradable: false,
      libraries: ["UniswapV2SwapHelper"]
    }
  ];

  let proxyFactory: ProxyFactory1967;
  const getProxyFactory = async () => {
    if (isDevelop === false && newdao.ProxyFactory) {
      return (proxyFactory = (await ethers.getContractAt(
        "ProxyFactory1967",
        newdao.ProxyFactory
      )) as unknown as ProxyFactory1967);
    } else {
      console.info("deploying ProxyFactory1967", isDevelop, newdao.ProxyFactory);
      // await pressAnyKey();

      const pf = await (await ethers.getContractFactory("ProxyFactory1967", root)).deploy(GAS_SETTINGS);
      await pf.deployed();
      await releaser({ ProxyFactory: pf.address }, networkName, "deployment", false);
      return (proxyFactory = pf.connect(root) as unknown as ProxyFactory1967);
    }
  };

  const deployDeterministic = async (contract, args: any[], factoryOpts = {}) => {
    try {
      const Contract = await ethers.getContractFactory(contract.name, factoryOpts);

      const salt = ethers.BigNumber.from(keccak256(ethers.utils.toUtf8Bytes(contract.name)));

      if (contract.isUpgradable !== false) {
        if (isCoverage) {
          console.log("Deploying:", contract.name, "using proxy");
          //coverage has large contracts doesnt work with proxy factory
          const tx = await upgrades.deployProxy(Contract, args, {
            initializer: contract.initializer,
            kind: "uups",
            unsafeAllowLinkedLibraries: true
          });
          await countTotalGas(tx, contract.name);
          return tx;
        }
        console.log("Deploying:", contract.name, "using proxyfactory");
        const encoded = Contract.interface.encodeFunctionData(contract.initializer || "initialize", args);
        const tx = await Contract.deploy(GAS_SETTINGS);
        const impl = await tx.deployed();
        await countTotalGas(tx, contract.name);

        const tx2 = await proxyFactory.deployProxy(salt, impl.address, encoded, GAS_SETTINGS);
        await countTotalGas(tx2, contract.name);
        const deployTx = await tx2.wait().catch(e => console.error("failed to deploy proxy, assuming it exists...", e));
        return ethers.getContractAt(
          contract.name,
          await proxyFactory["getDeploymentAddress(uint256,address)"](salt, root.address)
        );
      } else {
        //for some reason deploying with link library via proxy doesnt work on hardhat test env
        if (isTest === false) {
          console.log("Deploying:", contract.name, "using proxyfactory code");
          const constructor = Contract.interface.encodeDeploy(args);
          const bytecode = ethers.utils.solidityPack(["bytes", "bytes"], [Contract.bytecode, constructor]);
          const deployTx = await (await proxyFactory.deployCode(salt, bytecode, GAS_SETTINGS)).wait();
          return ethers.getContractAt(
            contract.name,
            await proxyFactory["getDeploymentAddress(uint256,address,bytes32)"](salt, root.address, keccak256(bytecode))
          );
        } else {
          console.log("Deploying:", contract.name, "using regular");
          const tx = await Contract.deploy(...args, GAS_SETTINGS);
          await countTotalGas(tx, contract.name);
          const impl = await tx.deployed();
          return impl;
        }
      }
    } catch (e) {
      console.log("Failed deploying contract:", { contract });
      throw e;
    }
  };

  const deployContracts = async () => {
    console.log({ dao, newdao, protocolSettings });

    await getProxyFactory();
    console.info("got proxyfactory at:", proxyFactory.address);
    for (let contract of toDeployUpgradable) {
      if (contract.network !== "both" && (contract.network === "mainnet") !== isMainnet) {
        console.log(contract, " Skipping non mainnet/sidechain contract:", contract.network, contract.name);
        continue;
      }
      if (isDevelop === false && newdao[contract.name]) {
        console.log(
          contract.name,
          " Skipping deployed contract at:",
          newdao[contract.name],
          "upgrading:",
          !!process.env.UPGRADE
        );
        continue;
      }

      const args = await Promise.all(contract.args.map(async _ => await (isFunction(_) ? _() : _)));

      console.log(`deploying contract upgrade ${contract.name}`, {
        args
        // release
        // pf: ProxyFactory.factory.address
      });
      // await pressAnyKey();
      let opts = {};
      if (contract.libraries) {
        let libraries = {};
        contract.libraries.forEach(l => (libraries[l] = release[l]));
        opts = { libraries };
      }
      const Contract = await ethers.getContractFactory(contract.name, opts);

      let deployed = await deployDeterministic(contract, args, opts);

      console.log(`${contract.name} deployed to: ${deployed.address}`);
      release[contract.name] = deployed.address;
      await releaser(release, networkName, "deployment", false);
    }

    if (!isProduction || get(release, "StakingContracts", []).length == 0) {
      const { DonationsStaking, StakingContracts } = isMainnet && (await deployStakingContracts(release));
      release["StakingContracts"] = StakingContracts;
      release["DonationsStaking"] = DonationsStaking;
      console.log("staking contracts result:", {
        StakingContracts,
        DonationsStaking
      });
    }

    release["network"] = networkName;
    release["networkId"] = network.config.chainId || 4447;
    if (!isMainnet) {
      release["HomeBridge"] = dao.HomeBridge;
      release["SignupBonus"] = dao.SignupBonus;
      release["OneTimePayments"] = dao.OneTimePayments;
      release["Invites"] = dao.Invites;
      release["AdminWallet"] = dao.AdminWallet;
    } else {
      release["ForeignBridge"] = dao.ForeignBridge;
      release["Contribution"] = dao.Contribution;
    }

    release["Identity"] = dao.Identity;
    release["GoodDollar"] = dao.GoodDollar;
    release["Controller"] = dao.Controller;
    release["Avatar"] = avatar;
    release["FirstClaimPool"] = dao.FirstClaimPool;
    release["ProxyAdmin"] = dao.ProxyAdmin;
    release["BancorFormula"] = protocolSettings.bancor || dao.BancorFormula;

    release["DAI"] = get(protocolSettings, "compound.dai", dao.DAI);
    release["cDAI"] = get(protocolSettings, "compound.cdai", dao.cDAI);
    release["COMP"] = get(protocolSettings, "compound.comp", dao.COMP);

    release = omitBy(release, _ => _ === undefined);
    let res = Object.assign(newdao, release);
    await releaser(release, networkName);
    return release;
  };

  // const proveNewRep = async () => {
  //   console.log("prooving new rep...");
  //   if (networkName.includes("production") === false) {
  //     const proofs = [
  //       [
  //         "0x23d8bd1cdfa398986bb91927d3011fb1ded1425b6ae3ff794e497235481fe57f",
  //         "0xe4ac4e67088f036e8dc535fee10a3ad42065e444d2b0bd3668e0df21e1590db3",
  //       ],
  //       ["0x4c01c2c86a047dc65fc8ff0a1d9ac11842597af9a363711e4db7dcabcfda307b"],
  //       [
  //         "0x235dc3126b01e763befb96ead059e3f19d0380e65e477e6ebb95c1d9fc90e0b7",
  //         "0xe4ac4e67088f036e8dc535fee10a3ad42065e444d2b0bd3668e0df21e1590db3",
  //       ],
  //     ];
  //     let proofResults = await Promise.all(
  //       founders.map((f, idx) =>
  //         grep
  //           .connect(f)
  //           .proveBalanceOfAtBlockchain(repStateId, f.address, 100, proofs[idx])
  //           .then((_) => _.wait())
  //       )
  //     );
  //     console.log(
  //       "proofs:",
  //       proofResults.map((_) => _.events)
  //     );
  //   } else {
  //     //prove foundation multi sig account
  //     const proof = [];
  //     const foundationAddress = protocolSettings.governance.foundationAddress;
  //     let proofResult = await grep
  //       .proveBalanceOfAtBlockchain(
  //         repStateId,
  //         foundationAddress,
  //         12000000,
  //         proof
  //       )
  //       .then((_) => _.wait());

  //     console.log("proofs:", proofResult.events);
  //   }
  // };

  const performUpgrade = async release => {
    const isKovan = networkName.includes("kovan");
    const upgrade: ProtocolUpgrade = (await ethers.getContractAt(
      "ProtocolUpgrade",
      release.ProtocolUpgrade
    )) as unknown as ProtocolUpgrade;

    console.log("performing protocol v2 upgrade on Mainnet...", {
      release,
      dao
    });
    console.log("upgrading nameservice + staking rewards...");
    let tx;
    tx = await (
      await upgrade.upgradeBasic(
        release.NameService,
        [
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes("RESERVE")),
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes("MARKET_MAKER")),
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes("FUND_MANAGER")),
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes("REPUTATION")),
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes("GDAO_STAKERS")),
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes("BRIDGE_CONTRACT")),
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes("UBI_RECIPIENT")),
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes("EXCHANGE_HELPER"))
        ],
        [
          release.GoodReserveCDai,
          release.GoodMarketMaker,
          release.GoodFundManager,
          release.GReputation,
          release.StakersDistribution,
          dao.ForeignBridge,
          isKovan ? root.address : newfusedao.UBIScheme, //fake for kovan
          release.ExchangeHelper
        ],
        release.StakingContracts.map((_: any) => _[0]),
        release.StakingContracts.map((_: any) => _[1])
      )
    ).wait();
    await countTotalGas(tx, "call upgrade basic");

    console.log("upgrading reserve...", {
      params: [release.NameService, dao.Reserve, dao.MarketMaker, dao.FundManager, release.COMP]
    });
    tx = await upgrade.upgradeReserve(
      release.NameService,
      dao.Reserve,
      dao.MarketMaker,
      dao.FundManager,
      release.COMP,
      GAS_SETTINGS
    );
    await countTotalGas(tx, "call upgrade reserve");
    console.log("upgrading donationstaking...", {
      params: [
        release.NameService,
        dao.DonationsStaking, //old
        release.DonationsStaking
      ]
    });
    tx = await upgrade.upgradeDonationStaking(
      release.NameService,
      dao.DonationsStaking, //old
      release.DonationsStaking, //new
      dao.DAIStaking,
      GAS_SETTINGS
    );
    await countTotalGas(tx, "call upgrade donations");
    console.log("Donation staking upgraded");
    //extract just the addresses without the rewards
    // release.StakingContracts = release.StakingContracts.map((_) => _[0]);

    if (isProduction) {
      console.log("SKIPPING GOVERNANCE UPGRADE FOR PRODUCTION. RUN IT MANUALLY");
    } else {
      console.log("upgrading governance...");

      tx = await upgrade.upgradeGovernance(dao.SchemeRegistrar, dao.UpgradeScheme, release.CompoundVotingMachine);
      await countTotalGas(tx, "call upgrade gov");
    }
  };

  const performUpgradeFuse = async release => {
    const upgrade: ProtocolUpgradeFuse = (await ethers.getContractAt(
      "ProtocolUpgradeFuse",
      release.ProtocolUpgradeFuse
    )) as unknown as ProtocolUpgradeFuse;

    console.log("performing protocol v2 upgrade on Fuse...", { release, dao });
    await upgrade
      .upgrade(
        release.NameService,
        //old contracts
        [dao.SchemeRegistrar || ethers.constants.AddressZero, dao.UpgradeScheme, dao.UBIScheme, dao.FirstClaimPool],
        release.UBIScheme,
        [
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes("REPUTATION")),
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes("BRIDGE_CONTRACT")),
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes("UBISCHEME")),
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes("GDAO_STAKING")),
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes("GDAO_CLAIMERS"))
        ],
        [
          release.GReputation,
          dao.HomeBridge,
          release.UBIScheme,
          release.GovernanceStaking,
          release.ClaimersDistribution
        ],
        GAS_SETTINGS
      )
      .then(_ => countTotalGas(_, "fuse basic upgrade"));

    if (isProduction) {
      console.log("SKIPPING GOVERNANCE UPGRADE FOR PRODUCTION. RUN IT MANUALLY");
    } else {
      console.log("upgrading governance...");

      await upgrade.upgradeGovernance(
        dao.SchemeRegistrar,
        dao.UpgradeScheme,
        release.CompoundVotingMachine,
        GAS_SETTINGS
      );
    }
  };

  //give Avatar permissions to the upgrade process contract
  const voteProtocolUpgrade = async release => {
    const Upgrade = release.ProtocolUpgrade || release.ProtocolUpgradeFuse;

    console.log("approve upgrade scheme in dao...", Upgrade, dao.SchemeRegistrar);

    const schemeRegistrar: SchemeRegistrar = (await ethers.getContractAt(
      "SchemeRegistrar",
      dao.SchemeRegistrar
    )) as unknown as SchemeRegistrar;
    console.log("proprosing scheme ...");

    const proposal = await (
      await schemeRegistrar.proposeScheme(
        avatar,
        Upgrade,
        ethers.constants.HashZero,
        "0x0000001F",
        ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ProtocolUpgrade"))
      )
    ).wait();
    await countTotalGas(proposal, "propose upgrade");

    console.log("proposal tx:", proposal.transactionHash);
    let proposalId = proposal.events.find(_ => _.event === "NewSchemeProposal").args._proposalId;

    console.log("proposal", { scheme: Upgrade, proposalId });

    console.log("voting...");

    const absoluteVote = await ethers.getContractAt(
      ["function vote(bytes32,uint,uint,address) returns (bool)"],
      dao.AbsoluteVote
    );

    console.log("voteUpgradeScheme", {
      absoluteVote: dao.AbsoluteVote,
      founders
    });
    await Promise.all(
      founders.slice(1).map(f =>
        absoluteVote
          .connect(f)
          .vote(proposalId, 1, 0, f.address, {
            ...GAS_SETTINGS,
            gasLimit: 300000
          })
          .then(_ => countTotalGas(_.wait(), "vote"))
          .catch(e => console.log("founder vote failed:", f.address, e.message))
      )
    );
  };

  const deployStakingContracts = async release => {
    const isRopsten = networkName === "fuse-mainnet" || networkName === "staging-mainnet";
    console.log("deployStakingContracts", {
      factory: release.CompoundStakingFactory,
      ns: release.NameService
    });
    const compfactory = await ethers.getContractAt("CompoundStakingFactory", release.CompoundStakingFactory);
    const aavefactory = await ethers.getContractAt("AaveStakingFactory", release.AaveStakingFactory);
    const compps = compoundTokens.map(async token => {
      let rewardsPerBlock = protocolSettings.staking.rewardsPerBlock;
      console.log("deployStakingContracts", {
        token,
        settings: protocolSettings.staking,
        rewardsPerBlock,
        factory: compfactory.address,
        params: [
          token.address,
          release.NameService,
          protocolSettings.staking.fullRewardsThreshold, //blocks before switching for 0.5x rewards to 1x multiplier
          token.usdOracle,
          token.compUsdOracle
        ]
      });
      const tx = await (
        await compfactory["cloneAndInit(address,address,uint64,address,address,address[])"](
          token.address,
          release.NameService,
          protocolSettings.staking.fullRewardsThreshold, //blocks before switching for 0.5x rewards to 1x multiplier
          token.usdOracle,
          token.compUsdOracle,
          token.swapPath,
          GAS_SETTINGS
        )
      ).wait();
      await countTotalGas(tx, "deploy comp staking");
      const log = tx.events.find(_ => _.event === "Deployed");
      if (!log.args.proxy) throw new Error(`staking contract deploy failed ${token}`);
      return [log.args.proxy, rewardsPerBlock];
    });

    await Promise.all(compps);
    // const compps = [
    //   Promise.resolve(["0x9999c40c8b88c740076b15d2e708db6a7a071b53", 13888])
    // ];
    let deployed;
    if (!isDappTest && (!isRopsten || isTest)) {
      const aaveps = aaveTokens.map(async token => {
        let rewardsPerBlock = (protocolSettings.staking.rewardsPerBlock / 2) //aave gets half of the rewards
          .toFixed(0);
        console.log("deployStakingContracts aave", {
          token,
          settings: protocolSettings.staking,
          rewardsPerBlock,
          params: [
            token.address,
            get(protocolSettings, "aave.lendingPool", dao.AaveLendingPool),
            release.NameService,
            protocolSettings.staking.fullRewardsThreshold, //blocks before switching for 0.5x rewards to 1x multiplier
            token.usdOracle,

            get(protocolSettings, "aave.incentiveController", dao.AaveIncentiveController),
            token.aaveUsdOracle,
            token.swapPath,
            GAS_SETTINGS
          ]
        });
        const tx = await (
          await aavefactory["cloneAndInit(address,address,address,uint64,address,address,address,address[])"](
            token.address,
            get(protocolSettings, "aave.lendingPool", dao.AaveLendingPool),
            release.NameService,
            protocolSettings.staking.fullRewardsThreshold, //blocks before switching for 0.5x rewards to 1x multiplier
            token.usdOracle,
            get(protocolSettings, "aave.incentiveController", dao.AaveIncentiveController),
            token.aaveUsdOracle,
            token.swapPath,
            GAS_SETTINGS
          )
        ).wait();
        await countTotalGas(tx, "deploy aave staking");
        const log = tx.events.find(_ => _.event === "Deployed");
        if (!log.args.proxy) throw new Error(`staking contract deploy failed ${token}`);
        return [log.args.proxy, rewardsPerBlock];
      });

      // const aaveps = [
      //   Promise.resolve(["0x8f0c4f59b4c593193e5b5e0224d848ac803ad1a2", 13888 / 2])
      // ];
      await Promise.all(aaveps);
      deployed = await Promise.all(compps.concat(aaveps));
    } else {
      deployed = await Promise.all(compps);
    }

    console.log("deploying donation staking");
    console.log(`release ${release}`);
    const deployedDonationsStaking = await deployDeterministic(
      {
        network: "mainnet",
        name: "DonationsStaking",
        isUpgradable: true
      },
      [
        release.NameService,
        deployed[0][0],
        ["0x0000000000000000000000000000000000000000", get(protocolSettings, "compound.dai", dao.DAI)],
        [get(protocolSettings, "compound.dai", dao.DAI), "0x0000000000000000000000000000000000000000"]
      ],
      { libraries: { UniswapV2SwapHelper: release["UniswapV2SwapHelper"] } }
    );
    // await countTotalGas(deployedDonationsStaking);

    console.log(`DonationsStaking deployed to: ${deployedDonationsStaking.address}`);

    return {
      DonationsStaking: deployedDonationsStaking.address,
      StakingContracts: deployed
    };
  };
  const verifyContracts = async release => {
    for (let contract of toDeployUpgradable) {
      if (contract.network !== "both" && (contract.network === "mainnet") !== isMainnet) {
        console.log(
          contract,
          " Skipping verification non mainnet/sidechain contract:",
          contract.network,
          contract.name
        );
        continue;
      }
      console.log("Running contract verification:", { contract }, release[contract.name]);
      if (contract.isUpgradable !== false) {
        const implementationAddress = await getImplementationAddress(network.provider, release[contract.name]);
        try {
          await run("verify:verify", {
            address: implementationAddress
          });
        } catch (err) {
          console.log("err", err);
        }
      } else {
        try {
          await run("verify:verify", {
            address: release[contract.name],
            constructorArguments: contract.args,
            libraries: {
              UniswapV2SwapHelper: release["UniswapV2SwapHelper"]
            }
          });
        } catch (err) {
          console.log("err", err);
        }
      }
    }
  };

  await deployContracts();

  if (isPerformUpgrade) {
    console.log("deployed contracts", { totalGas, dao, release });
    if (isTest === false) await pressAnyKey();
    await voteProtocolUpgrade(release);
    if (isTest === false) await pressAnyKey();
    console.log("voted contracts", { totalGas });
    isMainnet && (await performUpgrade(release));
    !isMainnet && (await performUpgradeFuse(release));
    console.log("upgraded contracts", { totalGas });
  }
  if (isMainnet && !isTest && !isCoverage) {
    await verifyContracts(release);
  }

  await releaser(release, networkName);
  return release;
  // await proveNewRep();
};
if (network.name !== "hardhat" && process.argv[1].includes("upgradeToV2.ts")) {
  main(name, false)
    .catch(e => {
      console.log(e);
      throw e;
    })
    .finally(() => {
      console.log({ totalGas, gasUsage });
    });
}
