@gooddollar/goodprotocol
Version:
GoodDollar Protocol
438 lines (390 loc) • 14.6 kB
text/typescript
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);
});
};