UNPKG

@gooddollar/goodprotocol

Version:
438 lines (390 loc) 14.6 kB
import { Contract, ContractFactory, Signer } from "ethers"; import { network, ethers, upgrades, run } from "hardhat"; import * as safeethers from "ethers"; import { TransactionResponse, TransactionReceipt } from "@ethersproject/providers"; // import Safe from "@gnosis.pm/safe-core-sdk"; import EthersAdapter from "@gnosis.pm/safe-ethers-lib"; // import { MetaTransactionData } from "@gnosis.pm/safe-core-sdk-types"; // import SafeClient from "@gnosis.pm/safe-service-client"; import SafeApiKit from "@safe-global/api-kit"; import Safe, { Eip1193Provider } from "@safe-global/protocol-kit"; import { MetaTransactionData, OperationType } from "@safe-global/types-kit"; import util from "util"; import dao from "../../releases/deployment.json"; const exec = util.promisify(require("child_process").exec); const networkName = network.name === "localhost" ? "production-mainnet" : network.name; let totalGas = 0; const gasUsage = {}; const GAS_SETTINGS = { gasLimit: 10000000 }; let release: { [key: string]: any } = dao[networkName]; export const verifyProductionSigner = signer => { if (signer.address.toLowerCase() !== "0x5128E3C1f8846724cc1007Af9b4189713922E4BB".toLowerCase()) { throw new Error( "signer not 0x5128E3C1f8846724cc1007Af9b4189713922E4BB to get same deployed addresses on production" ); } }; export const printDeploy = async ( c: Contract | TransactionResponse ): Promise<Contract | TransactionReceipt | TransactionResponse> => { if (c instanceof Contract) { await c.deployed(); console.log("deployed to: ", c.address); return c.deployed(); } if (c.wait) { await c.wait(); console.log("tx done:", c.hash); return c.wait(); } return c; }; export 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 }); }; export const deploySuperGoodDollar = async ( { superfluidHost, superfluidInflowNFTLogic, superfluidOutflowNFTLogic }, tokenArgs ) => { const SuperGoodDollar = (await deployDeterministic( { name: "SuperGoodDollar", salt: "SuperGoodDollarLogic" }, [superfluidHost] ).then(printDeploy)) as Contract; const uupsFactory = await ethers.getContractFactory("UUPSProxy"); const GoodDollarProxy = (await deployDeterministic( { name: "SuperGoodDollar", factory: uupsFactory }, [] ).then(printDeploy)) as Contract; await GoodDollarProxy.initializeProxy(SuperGoodDollar.address).then(printDeploy); const OutFlowNFT = (await deployDeterministic( { name: "SuperGoodDollar OutflowNFT", factory: uupsFactory }, [] ).then(printDeploy)) as Contract; const InFlowNFT = (await deployDeterministic( { name: "SuperGoodDollar InflowNFT", factory: uupsFactory }, [] ).then(printDeploy)) as Contract; await OutFlowNFT.initializeProxy(superfluidOutflowNFTLogic); await InFlowNFT.initializeProxy(superfluidInflowNFTLogic); await SuperGoodDollar.attach(GoodDollarProxy.address)[ "initialize(string,string,uint256,address,address,address,address,address,address)" ](...tokenArgs, OutFlowNFT.address, InFlowNFT.address); const GoodDollar = await ethers.getContractAt("ISuperGoodDollar", GoodDollarProxy.address); const constantInflowNFT = await ethers.getContractAt("ConstantInflowNFT", InFlowNFT.address); const constantOutflowNFT = await ethers.getContractAt("ConstantOutflowNFT", OutFlowNFT.address); await constantOutflowNFT .attach(OutFlowNFT.address) .initialize( GoodDollarProxy.address, (await GoodDollar.symbol()) + " Outflow NFT", (await GoodDollar.symbol()) + " COF" ); await constantInflowNFT .attach(InFlowNFT.address) .initialize( GoodDollarProxy.address, (await GoodDollar.symbol()) + " Inflow NFT", (await GoodDollar.symbol()) + " CIF" ); return GoodDollar; }; export const deployDeterministic = async (contract, args: any[], factoryOpts = {}, redeployProxyFactory = false) => { try { let proxyFactory; if (networkName.startsWith("develop") && redeployProxyFactory) { proxyFactory = await (await ethers.getContractFactory("ProxyFactory1967")).deploy(); } else proxyFactory = await ethers.getContractAt("ProxyFactory1967", release.ProxyFactory); const Contract = (contract.factory as ContractFactory) || (await ethers.getContractFactory(contract.name, factoryOpts)); const salt = ethers.BigNumber.from( ethers.utils.keccak256(ethers.utils.toUtf8Bytes(contract.salt || contract.name)) ); if (contract.isUpgradeable === true) { console.log("Deploying:", contract.name, "using proxyfactory", { args, proxyFactory: proxyFactory.address }); const encoded = Contract.interface.encodeFunctionData(contract.initializer || "initialize", args); const tx = await Contract.deploy(GAS_SETTINGS); const impl = await tx.deployed(); console.log("implementation deployed:", contract.name, impl.address); 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)); const proxyAddr = await proxyFactory["getDeploymentAddress(uint256,address)"]( salt, await proxyFactory.signer.getAddress() ); console.log("proxy deployed:", contract.name, proxyAddr); return Contract.attach(proxyAddr); } else { console.log("Deploying:", contract.name, "using proxyfactory code", { proxyFactory: proxyFactory.address, args }); 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(); const proxyAddr = await proxyFactory["getDeploymentAddress(uint256,address,bytes32)"]( salt, await proxyFactory.signer.getAddress(), ethers.utils.keccak256(bytecode) ); console.log("proxy deployed:", contract.name, proxyAddr); return Contract.attach(proxyAddr); } } catch (e) { console.log("Failed deploying contract:", { contract }); throw e; } }; export const executeViaGuardian = async ( contracts, ethValues, functionSigs, functionInputs, guardian: Signer, network?: string ) => { let release: { [key: string]: any } = dao[network || networkName]; const ctrl = await (await ethers.getContractAt("Controller", release.Controller)).connect(guardian); const results = []; for (let i = 0; i < contracts.length; i++) { const contract = contracts[i]; if (!contract) { console.warn("skipping executing missing contract", i, contracts[i], functionSigs[i], functionInputs[i]); continue; } console.log("executing:", contracts[i], functionSigs[i], functionInputs[i]); const sigHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(functionSigs[i])).slice(0, 10); const encoded = ethers.utils.solidityPack(["bytes4", "bytes"], [sigHash, functionInputs[i]]); if (contract.toLowerCase().startsWith((await guardian.getAddress()).toLocaleLowerCase())) { const [, target] = contract.split("_"); console.log("executing directly on target contract:", sigHash, encoded); const tx = await guardian.sendTransaction({ to: target, data: encoded }).then(printDeploy); results.push(tx); } else if (contract === ctrl.address) { console.log("executing directly on controller:", sigHash, encoded); const tx = await guardian.sendTransaction({ to: contract, data: encoded }).then(printDeploy); results.push(tx); } else { const simulationResult = await ctrl.callStatic.genericCall(contract, encoded, release.Avatar, ethValues[i], { from: await guardian.getAddress() }); console.log("executing genericCall:", { sigHash, contract, encoded, simulationResult }); if (simulationResult[0] === false) throw new Error("simulation failed:" + contract); const tx = await ctrl .genericCall(contract, encoded, release.Avatar, ethValues[i], { gasLimit: 8000000 }) .then(printDeploy); // console.log("generic call events:", tx.events); results.push(tx); } } return results; }; export const executeViaSafe = async ( contracts, ethValues, functionSigs, functionInputs, safeAddress: string, safeSignerOrNetwork?: Signer | string, isSimulation = false ) => { if (typeof safeSignerOrNetwork !== "object" && !process.env.SAFEOWNER_PRIVATE_KEY) { throw new Error("safe signer is missing"); } // let safeSigner = new ethers.Wallet( // process.env.SAFEOWNER_PRIVATE_KEY, // new ethers.providers.JsonRpcProvider("https://rpc.flashbots.net") // ); let chainId = 1; let provider = "https://mainnet.infura.io"; if (typeof safeSignerOrNetwork === "string") { switch (safeSignerOrNetwork) { case "mainnet": break; case "celo": chainId = 42220; provider = "https://forno.celo.org"; // safeSigner = new ethers.Wallet(process.env.SAFEOWNER_PRIVATE_KEY).connect( // new ethers.providers.JsonRpcProvider("https://forno.celo.org") // ); break; case "fuse": chainId = 122; provider = "https://rpc.fuse.io"; // safeSigner = new ethers.Wallet(process.env.SAFEOWNER_PRIVATE_KEY).connect( // new ethers.providers.JsonRpcProvider("https://rpc.fuse.io") // ); break; } } else if (safeSignerOrNetwork) { // safeSigner = safeSignerOrNetwork as any; } console.log("safeSigner:", { chainId, safeAddress }); let txServiceUrl; switch (chainId) { case 1: txServiceUrl = "https://safe-transaction-mainnet.safe.global"; break; case 122: txServiceUrl = "https://transaction-fuse.safe.fuse.io"; break; case 42220: txServiceUrl = "https://safe-transaction-celo.safe.global"; break; } console.log("creating safe adapter", { txServiceUrl }); const safeService = new SafeApiKit({ chainId: BigInt(chainId) }); const safeSdk = await Safe.init({ provider, signer: process.env.SAFEOWNER_PRIVATE_KEY, safeAddress }); const senderAddress = await safeSdk.getSafeProvider().getSignerAddress(); // console.log("creating safe client", { txServiceUrl }); // const safeService = new SafeClient({ // txServiceUrl, // ethAdapter // }); // const safeSdk = await Safe.create({ ethAdapter, safeAddress }); let release: { [key: string]: any } = dao[networkName]; const ctrl = await ethers.getContractAt("Controller", release.Controller, null); const safeTransactionData: MetaTransactionData[] = []; for (let i = 0; i < contracts.length; i++) { const contract = contracts[i]; const sigHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(functionSigs[i])).slice(0, 10); console.log("creating tx:", contracts[i], functionSigs[i], functionInputs[i]); const encoded = ethers.utils.solidityPack(["bytes4", "bytes"], [sigHash, functionInputs[i]]); if (contract.toLowerCase().startsWith(safeAddress.toLocaleLowerCase())) { const [, target] = contract.split("_"); const simulationResult = await ethers.provider.call({ to: target, data: encoded, from: safeAddress, value: ethValues[i] }); console.log("executing from guardians safe:", { sigHash, encoded, simulationResult }); safeTransactionData.push({ to: target, value: ethValues[i], data: encoded }); } else if (contract === ctrl.address) { const simulationResult = await ethers.provider.call({ to: ctrl.address, data: encoded, from: safeAddress, value: ethValues[i] }); console.log("executing controller call:", { sigHash, encoded, simulationResult }); safeTransactionData.push({ to: ctrl.address, value: ethValues[i], data: encoded }); } else { console.log("executing genericCall:", { sigHash, encoded, contract, avatar: release.Avatar, value: ethValues[i] }); const simulationResult = await ctrl.callStatic.genericCall(contract, encoded, release.Avatar, ethValues[i], { from: safeAddress }); console.log("executing genericCall simulation result:", { sigHash, simulationResult }); if (isSimulation === true && simulationResult[0] === false) throw new Error("simulation failed:" + contract); const genericEncode = ctrl.interface.encodeFunctionData("genericCall", [ contract, encoded, release.Avatar, ethValues[i] ]); safeTransactionData.push({ to: ctrl.address, value: "0", data: genericEncode }); } } const safeTransaction = await safeSdk.createTransaction({ transactions: safeTransactionData }); const safeTxHash = await safeSdk.getTransactionHash(safeTransaction); const signedHash = await safeSdk.signHash(safeTxHash); // const senderAddress = await safeSigner.getAddress(); console.log("propose safe transaction", { safeAddress, safeTransactionData: safeTransaction.data, safeTxHash, senderSignature: signedHash, senderAddress }); await safeService.proposeTransaction({ safeAddress, safeTransactionData: safeTransaction.data, safeTxHash, senderSignature: signedHash.data, senderAddress }); }; export const verifyContract = async ( address, contractPath, networkName = network.name, proxyName?: string, forcedConstructorArguments?: string ) => { const cmd = `yarn hardhat verify --contract ${contractPath} ${address} ${ forcedConstructorArguments ?? "" } --network ${networkName}`; console.log("running...:", cmd); await exec(cmd).then(({ stdout, stderr }) => { console.log("Result for:", cmd); console.log(stdout); console.log(stderr); }); };