import { task } from "hardhat/config"; import { Contract, ContractFactory, ContractReceipt, ContractTransaction, PopulatedTransaction, } from "@ethersproject/contracts"; import { Signer } from "@ethersproject/abstract-signer"; import { OwnershipFacet } from "../typechain/OwnershipFacet"; import { IDiamondLoupe, IDiamondCut } from "../typechain"; import { LedgerSigner } from "@anders-t/ethers-ledger"; import { gasPrice, getSelectors, getSighashes, } from "../scripts/helperFunctions"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { sendToMultisig } from "../scripts/libraries/multisig/multisig"; export interface FacetsAndAddSelectors { facetName: string; addSelectors: string[]; removeSelectors: string[]; } type FacetCutType = { Add: 0; Replace: 1; Remove: 2 }; const FacetCutAction: FacetCutType = { Add: 0, Replace: 1, Remove: 2 }; export interface DeployUpgradeTaskArgs { diamondUpgrader: string; diamondAddress: string; facetsAndAddSelectors: string; useMultisig: boolean; useLedger: boolean; initAddress?: string; initCalldata?: string; rawSigs?: boolean; // updateDiamondABI: boolean; } interface Cut { facetAddress: string; action: 0 | 1 | 2; functionSelectors: string[]; } export function convertFacetAndSelectorsToString( facets: FacetsAndAddSelectors[] ): string { let outputString = ""; facets.forEach((facet) => { outputString = outputString.concat( `#${facet.facetName}$$$${facet.addSelectors.join( "*" )}$$$${facet.removeSelectors.join("*")}` ); }); return outputString; } export function convertStringToFacetAndSelectors( facets: string ): FacetsAndAddSelectors[] { const facetArrays: string[] = facets.split("#").filter((string) => { return string.length > 0; }); const output: FacetsAndAddSelectors[] = []; facetArrays.forEach((string) => { const facetsAndAddSelectors = string.split("$$$"); output.push({ facetName: facetsAndAddSelectors[0], addSelectors: facetsAndAddSelectors[1].split("*"), removeSelectors: facetsAndAddSelectors[2].split("*"), }); }); return output; } task( "deployUpgrade", "Deploys a Diamond Cut, given an address, facets and addSelectors, and removeSelectors" ) .addParam("diamondUpgrader", "Address of the multisig signer") .addParam("diamondAddress", "Address of the Diamond to upgrade") .addParam( "facetsAndAddSelectors", "Stringified array of facet names to upgrade, along with an array of add Selectors" ) .addOptionalParam("initAddress", "The facet address to call init function on") .addOptionalParam("initCalldata", "The calldata for init function") .addFlag( "useMultisig", "Set to true if multisig should be used for deploying" ) .addFlag("useLedger", "Set to true if Ledger should be used for signing") // .addFlag("verifyFacets","Set to true if facets should be verified after deployment") .setAction( async (taskArgs: DeployUpgradeTaskArgs, hre: HardhatRuntimeEnvironment) => { const facets: string = taskArgs.facetsAndAddSelectors; const facetsAndAddSelectors: FacetsAndAddSelectors[] = convertStringToFacetAndSelectors( facets ); const diamondUpgrader: string = taskArgs.diamondUpgrader; const diamondAddress: string = taskArgs.diamondAddress; const useMultisig = taskArgs.useMultisig; const useLedger = taskArgs.useLedger; const initAddress = taskArgs.initAddress; const initCalldata = taskArgs.initCalldata; //Instantiate the Signer let signer: Signer; const owner = await ((await hre.ethers.getContractAt( "OwnershipFacet", diamondAddress )) as OwnershipFacet).owner(); const testing = ["hardhat", "localhost"].includes(hre.network.name); if (testing) { await hre.network.provider.request({ method: "hardhat_impersonateAccount", params: [owner], }); signer = await hre.ethers.getSigner(owner); } else if (hre.network.name === "matic") { if (useLedger) { signer = new LedgerSigner(hre.ethers.provider); } else signer = (await hre.ethers.getSigners())[0]; } else { throw Error("Incorrect network selected"); } //Create the cut const deployedFacets = []; const cut: Cut[] = []; for (let index = 0; index < facetsAndAddSelectors.length; index++) { const facet = facetsAndAddSelectors[index]; console.log("facet:", facet); if (facet.facetName.length > 0) { const factory = (await hre.ethers.getContractFactory( facet.facetName )) as ContractFactory; const deployedFacet: Contract = await factory.deploy({ gasPrice: gasPrice, }); await deployedFacet.deployed(); console.log( `Deployed Facet Address for ${facet.facetName}:`, deployedFacet.address ); deployedFacets.push(deployedFacet); const newSelectors = getSighashes(facet.addSelectors, hre.ethers); let existingFuncs = getSelectors(deployedFacet); for (const selector of newSelectors) { if (!existingFuncs.includes(selector)) { const index = newSelectors.findIndex((val) => val == selector); throw Error( `Selector ${selector} (${facet.addSelectors[index]}) not found` ); } } let existingSelectors = getSelectors(deployedFacet); existingSelectors = existingSelectors.filter( (selector) => !newSelectors.includes(selector) ); if (newSelectors.length > 0) { cut.push({ facetAddress: deployedFacet.address, action: FacetCutAction.Add, functionSelectors: newSelectors, }); } //Always replace the existing selectors to prevent duplications if (existingSelectors.length > 0) { cut.push({ facetAddress: deployedFacet.address, action: FacetCutAction.Replace, functionSelectors: existingSelectors, }); } } let removeSelectors: string[]; if (taskArgs.rawSigs == true) { removeSelectors = facet.removeSelectors; } else { removeSelectors = getSighashes(facet.removeSelectors, hre.ethers); } if (removeSelectors.length > 0) { console.log("Removing selectors:", removeSelectors); cut.push({ facetAddress: hre.ethers.constants.AddressZero, action: FacetCutAction.Remove, functionSelectors: removeSelectors, }); } } //Execute the Cut const diamondCut = (await hre.ethers.getContractAt( "IDiamondCut", diamondAddress, signer )) as IDiamondCut; //Helpful for debugging const diamondLoupe = (await hre.ethers.getContractAt( "IDiamondLoupe", diamondAddress, signer )) as IDiamondLoupe; if (testing) { console.log("Diamond cut"); const tx: ContractTransaction = await diamondCut.diamondCut( cut, initAddress ? initAddress : hre.ethers.constants.AddressZero, initCalldata ? initCalldata : "0x", { gasLimit: 8000000 } ); console.log("Diamond cut tx:", tx.hash); const receipt: ContractReceipt = await tx.wait(); if (!receipt.status) { throw Error(`Diamond upgrade failed: ${tx.hash}`); } console.log("Completed diamond cut: ", tx.hash); } else { //Choose to use a multisig or a simple deploy address if (useMultisig) { console.log("Diamond cut"); const tx: PopulatedTransaction = await diamondCut.populateTransaction.diamondCut( cut, initAddress ? initAddress : hre.ethers.constants.AddressZero, initCalldata ? initCalldata : "0x", { gasLimit: 800000 } ); await sendToMultisig(diamondUpgrader, signer, tx, hre.ethers); } else { const tx: ContractTransaction = await diamondCut.diamondCut( cut, initAddress ? initAddress : hre.ethers.constants.AddressZero, initCalldata ? initCalldata : "0x", { gasLimit: 800000 } ); const receipt: ContractReceipt = await tx.wait(); if (!receipt.status) { throw Error(`Diamond upgrade failed: ${tx.hash}`); } console.log("Completed diamond cut: ", tx.hash); } } } );