From 5a9a6e0b53aaf80595607bcd229bb31cce084436 Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 21 Nov 2023 13:57:26 -0800 Subject: [PATCH 01/13] add verification logic to the deployer and etherscan verify to base deploy mission --- src/deploy/deployer/hardhat-deployer.ts | 34 ++++++++++++++++++++++ src/deploy/missions/base-deploy-mission.ts | 19 ++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/deploy/deployer/hardhat-deployer.ts b/src/deploy/deployer/hardhat-deployer.ts index 57cd6d90..b4b67028 100644 --- a/src/deploy/deployer/hardhat-deployer.ts +++ b/src/deploy/deployer/hardhat-deployer.ts @@ -61,4 +61,38 @@ export class HardhatDeployer { async getBytecodeFromChain (address : string) { return this.hre.ethers.provider.getCode(address); } + + async tenderlyVerify ( + proxyName : string, + proxyAddress : string, + implName : string, + implAddress : string, + ) { + if (proxyName) { + await this.hre.tenderly.verify({ + name: proxyName, + address: proxyAddress, + }); + } + + return this.hre.tenderly.verify({ + name: implName, + address: implAddress, + }); + } + + async etherscanVerify ({ + address, + ctorArgs, + } : { + address : string; + ctorArgs ?: TDeployArgs; + }) { + return this.hre.run("verify:verify", { + address, + // this should only be used for non-proxied contracts + // or proxy impls that have actual constructors + constructorArguments: ctorArgs, + }); + } } diff --git a/src/deploy/missions/base-deploy-mission.ts b/src/deploy/missions/base-deploy-mission.ts index 6fe45ae4..d743973b 100644 --- a/src/deploy/missions/base-deploy-mission.ts +++ b/src/deploy/missions/base-deploy-mission.ts @@ -19,6 +19,7 @@ export class BaseDeployMission { campaign : DeployCampaign; logger : TLogger; config : IDeployCampaignConfig; + implAddress! : string | null; constructor ({ campaign, @@ -37,11 +38,11 @@ export class BaseDeployMission { async saveToDB (contract : Contract) { this.logger.debug(`Writing ${this.contractName} to DB...`); - const implAddress = this.proxyData.isProxy + this.implAddress = this.proxyData.isProxy ? await this.campaign.deployer.getProxyImplAddress(contract.address) : null; - const contractDbDoc = this.buildDbObject(contract, implAddress); + const contractDbDoc = this.buildDbObject(contract, this.implAddress); return this.campaign.dbAdapter.writeContract(this.contractName, contractDbDoc); } @@ -136,4 +137,18 @@ export class BaseDeployMission { await this.postDeploy(); } } + + async verify () { + this.logger.info(`Verifying ${this.contractName} on Etherscan...`); + const { address } = await this.campaign[this.instanceName]; + + const ctorArgs = !this.proxyData.isProxy ? this.deployArgs() : undefined; + + await this.campaign.deployer.etherscanVerify({ + address, + ctorArgs, + }); + + await this.logger.info(`Etherscan verification for ${this.contractName} finished successfully.`); + } } From 34e0ccfae43dee42953c7755e49bf5c6638aac80 Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 21 Nov 2023 16:34:34 -0800 Subject: [PATCH 02/13] remove save-tag from pretest script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0d413def..a4ee633a 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "build": "yarn run clean && yarn run compile", "postbuild": "yarn save-tag-bash", "typechain": "hardhat typechain", - "pretest": "yarn mongo:start && yarn save-tag", + "pretest": "yarn mongo:start", "test": "hardhat test", "test-local": "yarn test", "posttest": "yarn mongo:stop", From c1f77bc7d417a3f70df60ce850e74e6f455d32c0 Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 21 Nov 2023 16:35:17 -0800 Subject: [PATCH 03/13] add monitoring data getter to base mission --- src/deploy/missions/base-deploy-mission.ts | 34 +++++++++++++++++++++- src/deploy/missions/contracts/names.ts | 2 ++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/deploy/missions/base-deploy-mission.ts b/src/deploy/missions/base-deploy-mission.ts index d743973b..a4479222 100644 --- a/src/deploy/missions/base-deploy-mission.ts +++ b/src/deploy/missions/base-deploy-mission.ts @@ -7,6 +7,9 @@ import { import { DeployCampaign } from "../campaign/deploy-campaign"; import { IDeployCampaignConfig, TLogger } from "../campaign/types"; import { IContractDbData } from "../db/types"; +import { erc1967ProxyName, transparentProxyName } from "./contracts/names"; +import { ProxyKinds } from "../constants"; +import { ContractByName } from "@tenderly/hardhat-tenderly/dist/tenderly/types"; // TODO dep: @@ -149,6 +152,35 @@ export class BaseDeployMission { ctorArgs, }); - await this.logger.info(`Etherscan verification for ${this.contractName} finished successfully.`); + this.logger.info(`Etherscan verification for ${this.contractName} finished successfully.`); + } + + async getMonitoringData () : Promise> { + const implName = this.contractName; + let implAddress = this.campaign[this.instanceName].address; + + if (this.proxyData.isProxy) { + const proxyName = this.proxyData.kind === ProxyKinds.uups ? erc1967ProxyName : transparentProxyName; + const proxyAddress = this.campaign[this.instanceName].address; + implAddress = this.implAddress || await this.campaign.deployer.getProxyImplAddress(proxyAddress); + + return [ + { + name: proxyName, + address: proxyAddress, + }, + { + name: implName, + address: implAddress, + }, + ]; + } + + return [ + { + name: implName, + address: implAddress, + }, + ]; } } diff --git a/src/deploy/missions/contracts/names.ts b/src/deploy/missions/contracts/names.ts index effc1522..a5dd339d 100644 --- a/src/deploy/missions/contracts/names.ts +++ b/src/deploy/missions/contracts/names.ts @@ -1,4 +1,6 @@ export const erc1967ProxyName = "ERC1967Proxy"; +export const transparentProxyName = "TransparentUpgradeableProxy"; + export const znsNames = { accessController: { From dae568e18d57457a3694c05d03abf6d3fa7e8776 Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 21 Nov 2023 16:35:55 -0800 Subject: [PATCH 04/13] change tenderly verify method in deployer --- src/deploy/deployer/hardhat-deployer.ts | 21 ++++----------------- test/helpers/constants.ts | 1 - 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/deploy/deployer/hardhat-deployer.ts b/src/deploy/deployer/hardhat-deployer.ts index b4b67028..b828e155 100644 --- a/src/deploy/deployer/hardhat-deployer.ts +++ b/src/deploy/deployer/hardhat-deployer.ts @@ -2,6 +2,8 @@ import * as hre from "hardhat"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { TDeployArgs, TProxyKind } from "../missions/types"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import assert from "assert"; +import { ContractByName } from "@tenderly/hardhat-tenderly/dist/tenderly/types"; export class HardhatDeployer { hre : HardhatRuntimeEnvironment; @@ -62,23 +64,8 @@ export class HardhatDeployer { return this.hre.ethers.provider.getCode(address); } - async tenderlyVerify ( - proxyName : string, - proxyAddress : string, - implName : string, - implAddress : string, - ) { - if (proxyName) { - await this.hre.tenderly.verify({ - name: proxyName, - address: proxyAddress, - }); - } - - return this.hre.tenderly.verify({ - name: implName, - address: implAddress, - }); + async tenderlyVerify (contracts : Array) { + return this.hre.tenderly.verify(...contracts); } async etherscanVerify ({ diff --git a/test/helpers/constants.ts b/test/helpers/constants.ts index f1144500..b43a0785 100644 --- a/test/helpers/constants.ts +++ b/test/helpers/constants.ts @@ -87,5 +87,4 @@ export const fixedPricerName = "ZNSFixedPricer"; export const treasuryName = "ZNSTreasury"; export const registrarName = "ZNSRootRegistrar"; export const erc1967ProxyName = "ERC1967Proxy"; -export const transparentProxyName = "TransparentUpgradeableProxy"; export const subRegistrarName = "ZNSSubRegistrar"; From 923017864d079ab737b0918b7f3108a73da67e2a Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 21 Nov 2023 16:36:28 -0800 Subject: [PATCH 05/13] add both methods for etherscan and tenderly to campaign, extend campaign config --- src/deploy/campaign/deploy-campaign.ts | 43 ++++++++++++++++++++++++++ src/deploy/campaign/types.ts | 5 +++ src/deploy/zns-campaign.ts | 4 ++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/deploy/campaign/deploy-campaign.ts b/src/deploy/campaign/deploy-campaign.ts index 03004296..4fb7d7e1 100644 --- a/src/deploy/campaign/deploy-campaign.ts +++ b/src/deploy/campaign/deploy-campaign.ts @@ -11,6 +11,7 @@ import { TDeployMissionCtor } from "../missions/types"; import { BaseDeployMission } from "../missions/base-deploy-mission"; import { Contract } from "ethers"; import { MongoDBAdapter } from "../db/mongo-adapter/mongo-adapter"; +import { ContractByName } from "@tenderly/hardhat-tenderly/dist/tenderly/types"; export class DeployCampaign { @@ -93,6 +94,14 @@ export class DeployCampaign { Promise.resolve() ); + if (this.config.postDeploy.verifyContracts) { + await this.verify(); + } + + if (this.config.postDeploy.monitorContracts) { + await this.monitor(); + } + this.logger.debug("Deploy Campaign execution finished successfully."); } @@ -101,4 +110,38 @@ export class DeployCampaign { // TODO dep: make better logger and decide which levels to call where this.logger.debug(`Data of deployed contract '${contractName}' is added to Campaign state at '${instanceName}'.`); } + + async verify () { + return Object.values(this.state.instances).reduce( + async ( + acc : Promise, + missionInstance : BaseDeployMission, + ) => { + await acc; + return missionInstance.verify(); + }, + Promise.resolve() + ); + } + + async monitor () { + this.logger.info("Pushing contracts to Tenderly..."); + + const contracts = await Object.values(this.state.instances).reduce( + async ( + acc : Promise>, + missionInstance : BaseDeployMission, + ) : Promise> => { + const newAcc = await acc; + const data = await missionInstance.getMonitoringData(); + + return [...newAcc, ...data]; + }, + Promise.resolve([]) + ); + + await this.deployer.tenderlyVerify(contracts); + + this.logger.info(`Tenderly push finished successfully for Project ${this.config.postDeploy.tenderlyProjectSlug}.`); + } } diff --git a/src/deploy/campaign/types.ts b/src/deploy/campaign/types.ts index 51c441dd..b8c8cc21 100644 --- a/src/deploy/campaign/types.ts +++ b/src/deploy/campaign/types.ts @@ -22,6 +22,11 @@ export interface IDeployCampaignConfig { zeroVaultAddress : string; mockMeowToken : boolean; stakingTokenAddress : string; + postDeploy : { + tenderlyProjectSlug : string; + monitorContracts : boolean; + verifyContracts : boolean; + }; } export type TLogger = WinstonLogger | Console; diff --git a/src/deploy/zns-campaign.ts b/src/deploy/zns-campaign.ts index 2202616e..b1bba168 100644 --- a/src/deploy/zns-campaign.ts +++ b/src/deploy/zns-campaign.ts @@ -17,16 +17,18 @@ import { getLogger } from "./logger/create-logger"; export const runZnsCampaign = async ({ config, dbVersion, + deployer, } : { config : IDeployCampaignConfig; dbVersion ?: string; + deployer ?: HardhatDeployer; }) => { // TODO dep: figure out the best place to put this at! hre.upgrades.silenceWarnings(); const logger = getLogger(); - const deployer = new HardhatDeployer(config.deployAdmin); + if (!deployer) deployer = new HardhatDeployer(config.deployAdmin); const dbAdapter = await getMongoAdapter(); From db3212ccfa689eaa619bdefd4fea29c9373b615b Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 21 Nov 2023 16:36:53 -0800 Subject: [PATCH 06/13] rename the test file and add new tests --- ...aign.test.ts => DeployCampaignInt.test.ts} | 1815 +++++++++-------- 1 file changed, 975 insertions(+), 840 deletions(-) rename test/{DeployCampaign.test.ts => DeployCampaignInt.test.ts} (84%) diff --git a/test/DeployCampaign.test.ts b/test/DeployCampaignInt.test.ts similarity index 84% rename from test/DeployCampaign.test.ts rename to test/DeployCampaignInt.test.ts index a23fe72e..e5529366 100644 --- a/test/DeployCampaign.test.ts +++ b/test/DeployCampaignInt.test.ts @@ -1,840 +1,975 @@ -/* eslint-disable @typescript-eslint/no-empty-function, @typescript-eslint/ban-ts-comment, max-classes-per-file */ -import * as hre from "hardhat"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { - DEFAULT_ROYALTY_FRACTION, - DEFAULT_PRICE_CONFIG, - ZNS_DOMAIN_TOKEN_NAME, - ZNS_DOMAIN_TOKEN_SYMBOL, - INVALID_ENV_ERR, - NO_MOCK_PROD_ERR, - STAKING_TOKEN_ERR, - INVALID_CURVE_ERR, - MONGO_URI_ERR, -} from "./helpers"; -import { expect } from "chai"; -import { - MeowTokenDM, - meowTokenName, - meowTokenSymbol, - ZNSAccessControllerDM, - ZNSAddressResolverDM, - ZNSCurvePricerDM, - ZNSDomainTokenDM, ZNSFixedPricerDM, - ZNSRegistryDM, ZNSRootRegistrarDM, ZNSSubRegistrarDM, ZNSTreasuryDM, -} from "../src/deploy/missions/contracts"; -import { znsNames } from "../src/deploy/missions/contracts/names"; -import { IDeployCampaignConfig, TZNSContractState, TLogger } from "../src/deploy/campaign/types"; -import { runZnsCampaign } from "../src/deploy/zns-campaign"; -import { MeowMainnet } from "../src/deploy/missions/contracts/meow-token/mainnet-data"; -import { HardhatDeployer } from "../src/deploy/deployer/hardhat-deployer"; -import { DeployCampaign } from "../src/deploy/campaign/deploy-campaign"; -import { getMongoAdapter, resetMongoAdapter } from "../src/deploy/db/mongo-adapter/get-adapter"; -import { BaseDeployMission } from "../src/deploy/missions/base-deploy-mission"; -import { ResolverTypes } from "../src/deploy/constants"; -import { MongoDBAdapter } from "../src/deploy/db/mongo-adapter/mongo-adapter"; -import { getConfig, validate } from "../src/deploy/campaign/environments"; -import { ethers, BigNumber } from "ethers"; -import { promisify } from "util"; -import { exec } from "child_process"; - -const execAsync = promisify(exec); - - -describe("Deploy Campaign Test", () => { - let deployAdmin : SignerWithAddress; - let admin : SignerWithAddress; - let governor : SignerWithAddress; - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - let userA : SignerWithAddress; - let userB : SignerWithAddress; - let zeroVault : SignerWithAddress; - let campaignConfig : IDeployCampaignConfig; - - let mongoAdapter : MongoDBAdapter; - - before(async () => { - [deployAdmin, admin, governor, zeroVault, userA, userB] = await hre.ethers.getSigners(); - }); - - describe("MEOW Token Ops", () => { - before(async () => { - - campaignConfig = { - deployAdmin, - governorAddresses: [ deployAdmin.address ], - adminAddresses: [ deployAdmin.address, admin.address ], - domainToken: { - name: ZNS_DOMAIN_TOKEN_NAME, - symbol: ZNS_DOMAIN_TOKEN_SYMBOL, - defaultRoyaltyReceiver: deployAdmin.address, - defaultRoyaltyFraction: DEFAULT_ROYALTY_FRACTION, - }, - rootPriceConfig: DEFAULT_PRICE_CONFIG, - zeroVaultAddress: zeroVault.address, - stakingTokenAddress: MeowMainnet.address, - mockMeowToken: true, - }; - }); - - it("should deploy new MeowTokenMock when `mockMeowToken` is true", async () => { - const campaign = await runZnsCampaign({ - config: campaignConfig, - }); - - const { meowToken, dbAdapter } = campaign; - - const toMint = hre.ethers.utils.parseEther("972315"); - // `mint()` only exists on the Mocked contract - await meowToken.connect(deployAdmin).mint( - userA.address, - toMint - ); - - const balance = await meowToken.balanceOf(userA.address); - expect(balance).to.equal(toMint); - - await dbAdapter.dropDB(); - }); - - it("should use existing deployed non-mocked MeowToken contract when `mockMeowToken` is false", async () => { - campaignConfig.mockMeowToken = false; - - // deploy MeowToken contract - const factory = await hre.ethers.getContractFactory("MeowToken"); - const meow = await hre.upgrades.deployProxy( - factory, - [meowTokenName, meowTokenSymbol], - { - kind: "transparent", - }); - - await meow.deployed(); - - campaignConfig.stakingTokenAddress = meow.address; - - const campaign = await runZnsCampaign({ - config: campaignConfig, - }); - - const { - meowToken, - dbAdapter, - state: { - instances: { - meowToken: meowDMInstance, - }, - }, - } = campaign; - - expect(meowToken.address).to.equal(meow.address); - expect(meowDMInstance.contractName).to.equal(znsNames.meowToken.contract); - // TODO dep: what else ?? - - const toMint = hre.ethers.utils.parseEther("972315"); - // `mint()` only exists on the Mocked contract - try { - await meowToken.connect(deployAdmin).mint( - userA.address, - toMint - ); - } catch (e) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(e.message).to.include( - ".mint is not a function" - ); - } - - await dbAdapter.dropDB(); - }); - }); - - describe("Failure Recovery", () => { - const errorMsgDeploy = "FailMissionDeploy"; - const errorMsgPostDeploy = "FailMissionPostDeploy"; - - const loggerMock = { - info: () => {}, - debug: () => {}, - error: () => {}, - }; - - interface IDeployedData { - contract : string; - instance : string; - address ?: string; - } - - const runTest = async ({ - missionList, - placeOfFailure, - deployedNames, - undeployedNames, - failingInstanceName, - callback, - } : { - missionList : Array; - placeOfFailure : string; - deployedNames : Array<{ contract : string; instance : string; }>; - undeployedNames : Array<{ contract : string; instance : string; }>; - failingInstanceName : string; - // eslint-disable-next-line no-shadow - callback ?: (failingCampaign : DeployCampaign) => Promise; - }) => { - const deployer = new HardhatDeployer(deployAdmin); - let dbAdapter = await getMongoAdapter(); - - let toMatchErr = errorMsgDeploy; - if (placeOfFailure === "postDeploy") { - toMatchErr = errorMsgPostDeploy; - } - - const failingCampaign = new DeployCampaign({ - missions: missionList, - deployer, - dbAdapter, - // @ts-ignore - logger: loggerMock, - config: campaignConfig, - }); - - try { - await failingCampaign.execute(); - } catch (e) { - // @ts-ignore - expect(e.message).to.include(toMatchErr); - } - - // check the correct amount of contracts in state - const { contracts } = failingCampaign.state; - expect(Object.keys(contracts).length).to.equal(deployedNames.length); - - if (placeOfFailure === "deploy") { - // it should not deploy AddressResolver - expect(contracts[failingInstanceName]).to.be.undefined; - } else { - // it should deploy AddressResolver - expect(contracts[failingInstanceName].address).to.be.properAddress; - } - - // check DB to verify we only deployed half - const firstRunDeployed = await deployedNames.reduce( - async ( - acc : Promise>, - { contract, instance } : { contract : string; instance : string; } - ) : Promise> => { - const akk = await acc; - const fromDB = await dbAdapter.getContract(contract); - expect(fromDB?.address).to.be.properAddress; - - return [...akk, { contract, instance, address: fromDB?.address }]; - }, - Promise.resolve([]) - ); - - await undeployedNames.reduce( - async ( - acc : Promise, - { contract, instance } : { contract : string; instance : string; } - ) : Promise => { - await acc; - const fromDB = await dbAdapter.getContract(contract); - const fromState = failingCampaign[instance]; - - expect(fromDB).to.be.null; - expect(fromState).to.be.undefined; - }, - Promise.resolve() - ); - - // call whatever callback we passed before the next campaign run - await callback?.(failingCampaign); - - const { curVersion: initialDbVersion } = dbAdapter; - - // reset mongoAdapter instance to make sure we pick up the correct DB version - resetMongoAdapter(); - - // run Campaign again, but normally - const nextCampaign = await runZnsCampaign({ - config: campaignConfig, - }); - - ({ dbAdapter } = nextCampaign); - - // make sure MongoAdapter is using the correct TEMP version - const { curVersion: nextDbVersion } = dbAdapter; - expect(nextDbVersion).to.equal(initialDbVersion); - - // state should have 10 contracts in it - const { state } = nextCampaign; - expect(Object.keys(state.contracts).length).to.equal(10); - expect(Object.keys(state.instances).length).to.equal(10); - expect(state.missions.length).to.equal(10); - // it should deploy AddressResolver - expect(state.contracts.addressResolver.address).to.be.properAddress; - - // check DB to verify we deployed everything - const allNames = deployedNames.concat(undeployedNames); - - await allNames.reduce( - async ( - acc : Promise, - { contract } : { contract : string; } - ) : Promise => { - await acc; - const fromDB = await dbAdapter.getContract(contract); - expect(fromDB?.address).to.be.properAddress; - }, - Promise.resolve() - ); - - // check that previously deployed contracts were NOT redeployed - await firstRunDeployed.reduce( - async (acc : Promise, { contract, instance, address } : IDeployedData) : Promise => { - await acc; - const fromDB = await nextCampaign.dbAdapter.getContract(contract); - const fromState = nextCampaign[instance]; - - expect(fromDB?.address).to.equal(address); - expect(fromState.address).to.equal(address); - }, - Promise.resolve() - ); - - return { - failingCampaign, - nextCampaign, - firstRunDeployed, - }; - }; - - beforeEach(async () => { - [deployAdmin, admin, zeroVault] = await hre.ethers.getSigners(); - - campaignConfig = { - deployAdmin, - governorAddresses: [ deployAdmin.address ], - adminAddresses: [ deployAdmin.address, admin.address ], - domainToken: { - name: ZNS_DOMAIN_TOKEN_NAME, - symbol: ZNS_DOMAIN_TOKEN_SYMBOL, - defaultRoyaltyReceiver: deployAdmin.address, - defaultRoyaltyFraction: DEFAULT_ROYALTY_FRACTION, - }, - rootPriceConfig: DEFAULT_PRICE_CONFIG, - zeroVaultAddress: zeroVault.address, - // TODO dep: what do we pass here for test flow? we don't have a deployed MeowToken contract - stakingTokenAddress: "", - mockMeowToken: true, // 1700083028872 - }; - - mongoAdapter = await getMongoAdapter(loggerMock as TLogger); - }); - - afterEach(async () => { - await mongoAdapter.dropDB(); - }); - - // eslint-disable-next-line max-len - it("[in AddressResolver.deploy() hook] should ONLY deploy undeployed contracts in the run following a failed run", async () => { - // ZNSAddressResolverDM sits in the middle of the Campaign deploy list - // we override this class to add a failure to the deploy() method - class FailingZNSAddressResolverDM extends ZNSAddressResolverDM { - async deploy () { - throw new Error(errorMsgDeploy); - } - } - - const deployedNames = [ - znsNames.accessController, - znsNames.registry, - znsNames.domainToken, - { - contract: znsNames.meowToken.contractMock, - instance: znsNames.meowToken.instance, - }, - ]; - - const undeployedNames = [ - znsNames.addressResolver, - znsNames.curvePricer, - znsNames.treasury, - znsNames.rootRegistrar, - znsNames.fixedPricer, - znsNames.subRegistrar, - ]; - - // call test flow runner - await runTest({ - missionList: [ - ZNSAccessControllerDM, - ZNSRegistryDM, - ZNSDomainTokenDM, - MeowTokenDM, - FailingZNSAddressResolverDM, // failing DM - ZNSCurvePricerDM, - ZNSTreasuryDM, - ZNSRootRegistrarDM, - ZNSFixedPricerDM, - ZNSSubRegistrarDM, - ], - placeOfFailure: "deploy", - deployedNames, - undeployedNames, - failingInstanceName: "addressResolver", - }); - }); - - // eslint-disable-next-line max-len - it("[in AddressResolver.postDeploy() hook] should start from post deploy sequence that failed on the previous run", async () => { - class FailingZNSAddressResolverDM extends ZNSAddressResolverDM { - async postDeploy () { - throw new Error(errorMsgPostDeploy); - } - } - - const deployedNames = [ - znsNames.accessController, - znsNames.registry, - znsNames.domainToken, - { - contract: znsNames.meowToken.contractMock, - instance: znsNames.meowToken.instance, - }, - znsNames.addressResolver, - ]; - - const undeployedNames = [ - znsNames.curvePricer, - znsNames.treasury, - znsNames.rootRegistrar, - znsNames.fixedPricer, - znsNames.subRegistrar, - ]; - - const checkPostDeploy = async (failingCampaign : DeployCampaign) => { - const { - // eslint-disable-next-line no-shadow - registry, - } = failingCampaign; - - // we are checking that postDeploy did not add resolverType to Registry - expect(await registry.getResolverType(ResolverTypes.address)).to.be.equal(ethers.constants.AddressZero); - }; - - // check contracts are deployed correctly - const { - nextCampaign, - } = await runTest({ - missionList: [ - ZNSAccessControllerDM, - ZNSRegistryDM, - ZNSDomainTokenDM, - MeowTokenDM, - FailingZNSAddressResolverDM, // failing DM - ZNSCurvePricerDM, - ZNSTreasuryDM, - ZNSRootRegistrarDM, - ZNSFixedPricerDM, - ZNSSubRegistrarDM, - ], - placeOfFailure: "postDeploy", - deployedNames, - undeployedNames, - failingInstanceName: "addressResolver", - callback: checkPostDeploy, - }); - - // make sure postDeploy() ran properly on the next run - const { - registry, - addressResolver, - } = nextCampaign; - expect(await registry.getResolverType(ResolverTypes.address)).to.be.equal(addressResolver.address); - }); - - // eslint-disable-next-line max-len - it("[in RootRegistrar.deploy() hook] should ONLY deploy undeployed contracts in the run following a failed run", async () => { - class FailingZNSRootRegistrarDM extends ZNSRootRegistrarDM { - async deploy () { - throw new Error(errorMsgDeploy); - } - } - - const deployedNames = [ - znsNames.accessController, - znsNames.registry, - znsNames.domainToken, - { - contract: znsNames.meowToken.contractMock, - instance: znsNames.meowToken.instance, - }, - znsNames.addressResolver, - znsNames.curvePricer, - znsNames.treasury, - ]; - - const undeployedNames = [ - znsNames.rootRegistrar, - znsNames.fixedPricer, - znsNames.subRegistrar, - ]; - - // call test flow runner - await runTest({ - missionList: [ - ZNSAccessControllerDM, - ZNSRegistryDM, - ZNSDomainTokenDM, - MeowTokenDM, - ZNSAddressResolverDM, - ZNSCurvePricerDM, - ZNSTreasuryDM, - FailingZNSRootRegistrarDM, // failing DM - ZNSFixedPricerDM, - ZNSSubRegistrarDM, - ], - placeOfFailure: "deploy", - deployedNames, - undeployedNames, - failingInstanceName: "rootRegistrar", - }); - }); - - // eslint-disable-next-line max-len - it("[in RootRegistrar.postDeploy() hook] should start from post deploy sequence that failed on the previous run", async () => { - class FailingZNSRootRegistrarDM extends ZNSRootRegistrarDM { - async postDeploy () { - throw new Error(errorMsgPostDeploy); - } - } - - const deployedNames = [ - znsNames.accessController, - znsNames.registry, - znsNames.domainToken, - { - contract: znsNames.meowToken.contractMock, - instance: znsNames.meowToken.instance, - }, - znsNames.addressResolver, - znsNames.curvePricer, - znsNames.treasury, - znsNames.rootRegistrar, - ]; - - const undeployedNames = [ - znsNames.fixedPricer, - znsNames.subRegistrar, - ]; - - const checkPostDeploy = async (failingCampaign : DeployCampaign) => { - const { - // eslint-disable-next-line no-shadow - accessController, - // eslint-disable-next-line no-shadow - rootRegistrar, - } = failingCampaign; - - // we are checking that postDeploy did not grant REGISTRAR_ROLE to RootRegistrar - expect(await accessController.isRegistrar(rootRegistrar.address)).to.be.false; - }; - - // check contracts are deployed correctly - const { - nextCampaign, - } = await runTest({ - missionList: [ - ZNSAccessControllerDM, - ZNSRegistryDM, - ZNSDomainTokenDM, - MeowTokenDM, - ZNSAddressResolverDM, - ZNSCurvePricerDM, - ZNSTreasuryDM, - FailingZNSRootRegistrarDM, // failing DM - ZNSFixedPricerDM, - ZNSSubRegistrarDM, - ], - placeOfFailure: "postDeploy", - deployedNames, - undeployedNames, - failingInstanceName: "rootRegistrar", - callback: checkPostDeploy, - }); - - // make sure postDeploy() ran properly on the next run - const { - accessController, - rootRegistrar, - } = nextCampaign; - expect(await accessController.isRegistrar(rootRegistrar.address)).to.be.true; - }); - }); - - describe("Configurable Environment & Validation", () => { - // The `validate` function accepts the environment parameter only for the - // purpose of testing here as manipulating actual environment variables - // like `process.env. = "value"` is not possible in a test environment - // because the Hardhat process for running these tests will not respect these - // changes. `getConfig` calls to `validate` on its own, but never passes a value - // for the environment specifically, that is ever only inferred from the `process.env.ENV_LEVEL` - it("Gets the default configuration correctly", async () => { - // set the environment to get the appropriate variables - const localConfig : IDeployCampaignConfig = await getConfig( - deployAdmin, - zeroVault, - [governor.address], - [admin.address], - ); - - expect(localConfig.deployAdmin.address).to.eq(deployAdmin.address); - expect(localConfig.governorAddresses[0]).to.eq(governor.address); - expect(localConfig.governorAddresses[1]).to.be.undefined; - expect(localConfig.adminAddresses[0]).to.eq(admin.address); - expect(localConfig.adminAddresses[1]).to.be.undefined; - expect(localConfig.domainToken.name).to.eq(ZNS_DOMAIN_TOKEN_NAME); - expect(localConfig.domainToken.symbol).to.eq(ZNS_DOMAIN_TOKEN_SYMBOL); - expect(localConfig.domainToken.defaultRoyaltyReceiver).to.eq(deployAdmin.address); - expect(localConfig.domainToken.defaultRoyaltyFraction).to.eq(DEFAULT_ROYALTY_FRACTION); - expect(localConfig.rootPriceConfig).to.deep.eq(DEFAULT_PRICE_CONFIG); - }); - - it("Confirms encoding functionality works for env variables", async () => { - const sample = "0x123,0x456,0x789"; - const sampleFormatted = ["0x123", "0x456", "0x789"]; - const encoded = btoa(sample); - const decoded = atob(encoded).split(","); - expect(decoded).to.deep.eq(sampleFormatted); - }); - - it("Modifies config to use a random account as the deployer", async () => { - // Run the deployment a second time, clear the DB so everything is deployed - - let zns : TZNSContractState; - - const config : IDeployCampaignConfig = await getConfig( - userB, - userA, - [userB.address, admin.address], // governors - [userB.address, governor.address], // admins - ); - - const campaign = await runZnsCampaign({ - config, - }); - - const { dbAdapter } = campaign; - - /* eslint-disable-next-line prefer-const */ - zns = campaign.state.contracts; - - const rootPaymentConfig = await zns.treasury.paymentConfigs(ethers.constants.HashZero); - - expect(await zns.accessController.isAdmin(userB.address)).to.be.true; - expect(await zns.accessController.isAdmin(governor.address)).to.be.true; - expect(await zns.accessController.isGovernor(admin.address)).to.be.true; - expect(rootPaymentConfig.token).to.eq(zns.meowToken.address); - expect(rootPaymentConfig.beneficiary).to.eq(userA.address); - - await dbAdapter.dropDB(); - }); - - it("Fails when governor or admin addresses are given wrong", async () => { - // Custom addresses must given as the base64 encoded string of comma separated addresses - // e.g. btoa("0x123,0x456,0x789") = 'MHgxMjMsMHg0NTYsMHg3ODk=', which is what should be provided - // We could manipulate envariables through `process.env.` for this test and call `getConfig()` - // but the async nature of HH mocha tests causes this to mess up other tests - // Instead we use the same encoding functions used in `getConfig()` to test the functionality - - /* eslint-disable @typescript-eslint/no-explicit-any */ - try { - atob("[0x123,0x456]"); - } catch (e : any) { - expect(e.message).includes("Invalid character"); - } - - try { - atob("0x123, 0x456"); - } catch (e : any) { - expect(e.message).includes("Invalid character"); - } - - try { - atob("0x123 0x456"); - } catch (e : any) { - expect(e.message).includes("Invalid character"); - } - - try { - atob("'MHgxM jMsMHg0 NTYs MHg3ODk='"); - } catch (e : any) { - expect(e.message).includes("Invalid character"); - } - }); - it("Throws if env variable is invalid", async () => { - try { - const config = await getConfig( - deployAdmin, - zeroVault, - [deployAdmin.address, governor.address], - [deployAdmin.address, admin.address], - ); - - validate(config, "other"); - - /* eslint-disable @typescript-eslint/no-explicit-any */ - } catch (e : any) { - expect(e.message).includes(INVALID_ENV_ERR); - } - }); - - it("Fails to validate when mocking MEOW on prod", async () => { - try { - const config = await getConfig( - deployAdmin, - zeroVault, - [deployAdmin.address, governor.address], - [deployAdmin.address, admin.address], - ); - // Modify the config - config.mockMeowToken = true; - validate(config, "prod"); - - /* eslint-disable @typescript-eslint/no-explicit-any */ - } catch (e : any) { - expect(e.message).includes(NO_MOCK_PROD_ERR); - } - }); - - it("Fails to validate if not using the MEOW token on prod", async () => { - try { - const config = await getConfig( - deployAdmin, - zeroVault, - [deployAdmin.address, governor.address], - [deployAdmin.address, admin.address], - ); - - config.mockMeowToken = false; - config.stakingTokenAddress = "0x123"; - - validate(config, "prod"); - /* eslint-disable @typescript-eslint/no-explicit-any */ - } catch (e : any) { - expect(e.message).includes(STAKING_TOKEN_ERR); - } - }); - - it("Fails to validate if invalid curve for pricing", async () => { - try { - const config = await getConfig( - deployAdmin, - zeroVault, - [deployAdmin.address, governor.address], - [deployAdmin.address, admin.address], - ); - - config.mockMeowToken = false; - config.stakingTokenAddress = MeowMainnet.address; - config.rootPriceConfig.baseLength = BigNumber.from(3); - config.rootPriceConfig.maxLength = BigNumber.from(5); - config.rootPriceConfig.maxPrice = ethers.constants.Zero; - config.rootPriceConfig.minPrice = ethers.utils.parseEther("3"); - - validate(config, "prod"); - /* eslint-disable @typescript-eslint/no-explicit-any */ - } catch (e : any) { - expect(e.message).includes(INVALID_CURVE_ERR); - } - }); - - it("Fails to validate if no mongo uri or local URI in prod", async () => { - try { - const config = await getConfig( - deployAdmin, - zeroVault, - [deployAdmin.address, governor.address], - [deployAdmin.address, admin.address], - ); - - config.mockMeowToken = false; - config.stakingTokenAddress = MeowMainnet.address; - - // Normally we would call to an env variable to grab this value - const uri = ""; - - // Falls back onto the default URI which is for localhost and fails in prod - validate(config, "prod", uri); - /* eslint-disable @typescript-eslint/no-explicit-any */ - } catch (e : any) { - expect(e.message).includes(MONGO_URI_ERR); - } - - try { - const config = await getConfig( - deployAdmin, - zeroVault, - [deployAdmin.address, governor.address], - [deployAdmin.address, admin.address], - ); - - config.mockMeowToken = false; - config.stakingTokenAddress = MeowMainnet.address; - - // Normally we would call to an env variable to grab this value - const uri = "mongodb://localhost:27018"; - - validate(config, "prod", uri); - /* eslint-disable @typescript-eslint/no-explicit-any */ - } catch (e : any) { - expect(e.message).includes(MONGO_URI_ERR); - } - }); - }); - - // TODO dep: add more versioning tests here for DB versions! - describe("Versioning", () => { - let campaign : DeployCampaign; - - before(async () => { - campaignConfig = { - deployAdmin, - governorAddresses: [ deployAdmin.address ], - adminAddresses: [ deployAdmin.address, admin.address ], - domainToken: { - name: ZNS_DOMAIN_TOKEN_NAME, - symbol: ZNS_DOMAIN_TOKEN_SYMBOL, - defaultRoyaltyReceiver: deployAdmin.address, - defaultRoyaltyFraction: DEFAULT_ROYALTY_FRACTION, - }, - rootPriceConfig: DEFAULT_PRICE_CONFIG, - zeroVaultAddress: zeroVault.address, - stakingTokenAddress: MeowMainnet.address, - mockMeowToken: true, - }; - - campaign = await runZnsCampaign({ - config: campaignConfig, - }); - }); - - it("should get the correct git tag + commit hash and write to DB", async () => { - const latestGitTag = (await execAsync("git describe --tags --abbrev=0")).stdout.trim(); - const latestCommit = (await execAsync(`git rev-list -n 1 ${latestGitTag}`)).stdout.trim(); - - const fullGitTag = `${latestGitTag}:${latestCommit}`; - - const { dbAdapter } = campaign; - - const versionDoc = await dbAdapter.getLatestVersion(); - expect(versionDoc?.contractsVersion).to.equal(fullGitTag); - - const deployedVersion = await dbAdapter.getDeployedVersion(); - expect(deployedVersion?.contractsVersion).to.equal(fullGitTag); - }); - }); -}); +/* eslint-disable @typescript-eslint/no-empty-function, @typescript-eslint/ban-ts-comment, max-classes-per-file */ +import * as hre from "hardhat"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { + DEFAULT_ROYALTY_FRACTION, + DEFAULT_PRICE_CONFIG, + ZNS_DOMAIN_TOKEN_NAME, + ZNS_DOMAIN_TOKEN_SYMBOL, + INVALID_ENV_ERR, + NO_MOCK_PROD_ERR, + STAKING_TOKEN_ERR, + INVALID_CURVE_ERR, + MONGO_URI_ERR, erc1967ProxyName, +} from "./helpers"; +import { expect } from "chai"; +import { + MeowTokenDM, + meowTokenName, + meowTokenSymbol, + ZNSAccessControllerDM, + ZNSAddressResolverDM, + ZNSCurvePricerDM, + ZNSDomainTokenDM, ZNSFixedPricerDM, + ZNSRegistryDM, ZNSRootRegistrarDM, ZNSSubRegistrarDM, ZNSTreasuryDM, +} from "../src/deploy/missions/contracts"; +import { transparentProxyName, znsNames } from "../src/deploy/missions/contracts/names"; +import { IDeployCampaignConfig, TZNSContractState, TLogger } from "../src/deploy/campaign/types"; +import { runZnsCampaign } from "../src/deploy/zns-campaign"; +import { MeowMainnet } from "../src/deploy/missions/contracts/meow-token/mainnet-data"; +import { HardhatDeployer } from "../src/deploy/deployer/hardhat-deployer"; +import { DeployCampaign } from "../src/deploy/campaign/deploy-campaign"; +import { getMongoAdapter, resetMongoAdapter } from "../src/deploy/db/mongo-adapter/get-adapter"; +import { BaseDeployMission } from "../src/deploy/missions/base-deploy-mission"; +import { ProxyKinds, ResolverTypes } from "../src/deploy/constants"; +import { MongoDBAdapter } from "../src/deploy/db/mongo-adapter/mongo-adapter"; +import { getConfig, validate } from "../src/deploy/campaign/environments"; +import { ethers, BigNumber } from "ethers"; +import { promisify } from "util"; +import { exec } from "child_process"; +import { TDeployArgs } from "../src/deploy/missions/types"; +import { ContractByName } from "@tenderly/hardhat-tenderly/dist/tenderly/types"; + +const execAsync = promisify(exec); + + +describe("Deploy Campaign Test", () => { + let deployAdmin : SignerWithAddress; + let admin : SignerWithAddress; + let governor : SignerWithAddress; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let userA : SignerWithAddress; + let userB : SignerWithAddress; + let zeroVault : SignerWithAddress; + let campaignConfig : IDeployCampaignConfig; + + let mongoAdapter : MongoDBAdapter; + + before(async () => { + [deployAdmin, admin, governor, zeroVault, userA, userB] = await hre.ethers.getSigners(); + }); + + describe("MEOW Token Ops", () => { + before(async () => { + + campaignConfig = { + deployAdmin, + governorAddresses: [deployAdmin.address], + adminAddresses: [deployAdmin.address, admin.address], + domainToken: { + name: ZNS_DOMAIN_TOKEN_NAME, + symbol: ZNS_DOMAIN_TOKEN_SYMBOL, + defaultRoyaltyReceiver: deployAdmin.address, + defaultRoyaltyFraction: DEFAULT_ROYALTY_FRACTION, + }, + rootPriceConfig: DEFAULT_PRICE_CONFIG, + zeroVaultAddress: zeroVault.address, + stakingTokenAddress: MeowMainnet.address, + mockMeowToken: true, + postDeploy: { + tenderlyProjectSlug: "", + monitorContracts: false, + verifyContracts: false, + }, + }; + }); + + it("should deploy new MeowTokenMock when `mockMeowToken` is true", async () => { + const campaign = await runZnsCampaign({ + config: campaignConfig, + }); + + const { meowToken, dbAdapter } = campaign; + + const toMint = hre.ethers.utils.parseEther("972315"); + // `mint()` only exists on the Mocked contract + await meowToken.connect(deployAdmin).mint( + userA.address, + toMint + ); + + const balance = await meowToken.balanceOf(userA.address); + expect(balance).to.equal(toMint); + + await dbAdapter.dropDB(); + }); + + it("should use existing deployed non-mocked MeowToken contract when `mockMeowToken` is false", async () => { + campaignConfig.mockMeowToken = false; + + // deploy MeowToken contract + const factory = await hre.ethers.getContractFactory("MeowToken"); + const meow = await hre.upgrades.deployProxy( + factory, + [meowTokenName, meowTokenSymbol], + { + kind: "transparent", + }); + + await meow.deployed(); + + campaignConfig.stakingTokenAddress = meow.address; + + const campaign = await runZnsCampaign({ + config: campaignConfig, + }); + + const { + meowToken, + dbAdapter, + state: { + instances: { + meowToken: meowDMInstance, + }, + }, + } = campaign; + + expect(meowToken.address).to.equal(meow.address); + expect(meowDMInstance.contractName).to.equal(znsNames.meowToken.contract); + // TODO dep: what else ?? + + const toMint = hre.ethers.utils.parseEther("972315"); + // `mint()` only exists on the Mocked contract + try { + await meowToken.connect(deployAdmin).mint( + userA.address, + toMint + ); + } catch (e) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(e.message).to.include( + ".mint is not a function" + ); + } + + await dbAdapter.dropDB(); + }); + }); + + describe("Failure Recovery", () => { + const errorMsgDeploy = "FailMissionDeploy"; + const errorMsgPostDeploy = "FailMissionPostDeploy"; + + const loggerMock = { + info: () => { + }, + debug: () => { + }, + error: () => { + }, + }; + + interface IDeployedData { + contract : string; + instance : string; + address ?: string; + } + + const runTest = async ({ + missionList, + placeOfFailure, + deployedNames, + undeployedNames, + failingInstanceName, + callback, + } : { + missionList : Array; + placeOfFailure : string; + deployedNames : Array<{ contract : string; instance : string; }>; + undeployedNames : Array<{ contract : string; instance : string; }>; + failingInstanceName : string; + // eslint-disable-next-line no-shadow + callback ?: (failingCampaign : DeployCampaign) => Promise; + }) => { + const deployer = new HardhatDeployer(deployAdmin); + let dbAdapter = await getMongoAdapter(); + + let toMatchErr = errorMsgDeploy; + if (placeOfFailure === "postDeploy") { + toMatchErr = errorMsgPostDeploy; + } + + const failingCampaign = new DeployCampaign({ + missions: missionList, + deployer, + dbAdapter, + // @ts-ignore + logger: loggerMock, + config: campaignConfig, + }); + + try { + await failingCampaign.execute(); + } catch (e) { + // @ts-ignore + expect(e.message).to.include(toMatchErr); + } + + // check the correct amount of contracts in state + const { contracts } = failingCampaign.state; + expect(Object.keys(contracts).length).to.equal(deployedNames.length); + + if (placeOfFailure === "deploy") { + // it should not deploy AddressResolver + expect(contracts[failingInstanceName]).to.be.undefined; + } else { + // it should deploy AddressResolver + expect(contracts[failingInstanceName].address).to.be.properAddress; + } + + // check DB to verify we only deployed half + const firstRunDeployed = await deployedNames.reduce( + async ( + acc : Promise>, + { contract, instance } : { contract : string; instance : string; } + ) : Promise> => { + const akk = await acc; + const fromDB = await dbAdapter.getContract(contract); + expect(fromDB?.address).to.be.properAddress; + + return [...akk, { contract, instance, address: fromDB?.address }]; + }, + Promise.resolve([]) + ); + + await undeployedNames.reduce( + async ( + acc : Promise, + { contract, instance } : { contract : string; instance : string; } + ) : Promise => { + await acc; + const fromDB = await dbAdapter.getContract(contract); + const fromState = failingCampaign[instance]; + + expect(fromDB).to.be.null; + expect(fromState).to.be.undefined; + }, + Promise.resolve() + ); + + // call whatever callback we passed before the next campaign run + await callback?.(failingCampaign); + + const { curVersion: initialDbVersion } = dbAdapter; + + // reset mongoAdapter instance to make sure we pick up the correct DB version + resetMongoAdapter(); + + // run Campaign again, but normally + const nextCampaign = await runZnsCampaign({ + config: campaignConfig, + }); + + ({ dbAdapter } = nextCampaign); + + // make sure MongoAdapter is using the correct TEMP version + const { curVersion: nextDbVersion } = dbAdapter; + expect(nextDbVersion).to.equal(initialDbVersion); + + // state should have 10 contracts in it + const { state } = nextCampaign; + expect(Object.keys(state.contracts).length).to.equal(10); + expect(Object.keys(state.instances).length).to.equal(10); + expect(state.missions.length).to.equal(10); + // it should deploy AddressResolver + expect(state.contracts.addressResolver.address).to.be.properAddress; + + // check DB to verify we deployed everything + const allNames = deployedNames.concat(undeployedNames); + + await allNames.reduce( + async ( + acc : Promise, + { contract } : { contract : string; } + ) : Promise => { + await acc; + const fromDB = await dbAdapter.getContract(contract); + expect(fromDB?.address).to.be.properAddress; + }, + Promise.resolve() + ); + + // check that previously deployed contracts were NOT redeployed + await firstRunDeployed.reduce( + async (acc : Promise, { contract, instance, address } : IDeployedData) : Promise => { + await acc; + const fromDB = await nextCampaign.dbAdapter.getContract(contract); + const fromState = nextCampaign[instance]; + + expect(fromDB?.address).to.equal(address); + expect(fromState.address).to.equal(address); + }, + Promise.resolve() + ); + + return { + failingCampaign, + nextCampaign, + firstRunDeployed, + }; + }; + + beforeEach(async () => { + [deployAdmin, admin, zeroVault] = await hre.ethers.getSigners(); + + campaignConfig = { + deployAdmin, + governorAddresses: [deployAdmin.address], + adminAddresses: [deployAdmin.address, admin.address], + domainToken: { + name: ZNS_DOMAIN_TOKEN_NAME, + symbol: ZNS_DOMAIN_TOKEN_SYMBOL, + defaultRoyaltyReceiver: deployAdmin.address, + defaultRoyaltyFraction: DEFAULT_ROYALTY_FRACTION, + }, + rootPriceConfig: DEFAULT_PRICE_CONFIG, + zeroVaultAddress: zeroVault.address, + // TODO dep: what do we pass here for test flow? we don't have a deployed MeowToken contract + stakingTokenAddress: "", + mockMeowToken: true, // 1700083028872 + postDeploy: { + tenderlyProjectSlug: "", + monitorContracts: false, + verifyContracts: false, + }, + }; + + mongoAdapter = await getMongoAdapter(loggerMock as TLogger); + }); + + afterEach(async () => { + await mongoAdapter.dropDB(); + }); + + // eslint-disable-next-line max-len + it("[in AddressResolver.deploy() hook] should ONLY deploy undeployed contracts in the run following a failed run", async () => { + // ZNSAddressResolverDM sits in the middle of the Campaign deploy list + // we override this class to add a failure to the deploy() method + class FailingZNSAddressResolverDM extends ZNSAddressResolverDM { + async deploy () { + throw new Error(errorMsgDeploy); + } + } + + const deployedNames = [ + znsNames.accessController, + znsNames.registry, + znsNames.domainToken, + { + contract: znsNames.meowToken.contractMock, + instance: znsNames.meowToken.instance, + }, + ]; + + const undeployedNames = [ + znsNames.addressResolver, + znsNames.curvePricer, + znsNames.treasury, + znsNames.rootRegistrar, + znsNames.fixedPricer, + znsNames.subRegistrar, + ]; + + // call test flow runner + await runTest({ + missionList: [ + ZNSAccessControllerDM, + ZNSRegistryDM, + ZNSDomainTokenDM, + MeowTokenDM, + FailingZNSAddressResolverDM, // failing DM + ZNSCurvePricerDM, + ZNSTreasuryDM, + ZNSRootRegistrarDM, + ZNSFixedPricerDM, + ZNSSubRegistrarDM, + ], + placeOfFailure: "deploy", + deployedNames, + undeployedNames, + failingInstanceName: "addressResolver", + }); + }); + + // eslint-disable-next-line max-len + it("[in AddressResolver.postDeploy() hook] should start from post deploy sequence that failed on the previous run", async () => { + class FailingZNSAddressResolverDM extends ZNSAddressResolverDM { + async postDeploy () { + throw new Error(errorMsgPostDeploy); + } + } + + const deployedNames = [ + znsNames.accessController, + znsNames.registry, + znsNames.domainToken, + { + contract: znsNames.meowToken.contractMock, + instance: znsNames.meowToken.instance, + }, + znsNames.addressResolver, + ]; + + const undeployedNames = [ + znsNames.curvePricer, + znsNames.treasury, + znsNames.rootRegistrar, + znsNames.fixedPricer, + znsNames.subRegistrar, + ]; + + const checkPostDeploy = async (failingCampaign : DeployCampaign) => { + const { + // eslint-disable-next-line no-shadow + registry, + } = failingCampaign; + + // we are checking that postDeploy did not add resolverType to Registry + expect(await registry.getResolverType(ResolverTypes.address)).to.be.equal(ethers.constants.AddressZero); + }; + + // check contracts are deployed correctly + const { + nextCampaign, + } = await runTest({ + missionList: [ + ZNSAccessControllerDM, + ZNSRegistryDM, + ZNSDomainTokenDM, + MeowTokenDM, + FailingZNSAddressResolverDM, // failing DM + ZNSCurvePricerDM, + ZNSTreasuryDM, + ZNSRootRegistrarDM, + ZNSFixedPricerDM, + ZNSSubRegistrarDM, + ], + placeOfFailure: "postDeploy", + deployedNames, + undeployedNames, + failingInstanceName: "addressResolver", + callback: checkPostDeploy, + }); + + // make sure postDeploy() ran properly on the next run + const { + registry, + addressResolver, + } = nextCampaign; + expect(await registry.getResolverType(ResolverTypes.address)).to.be.equal(addressResolver.address); + }); + + // eslint-disable-next-line max-len + it("[in RootRegistrar.deploy() hook] should ONLY deploy undeployed contracts in the run following a failed run", async () => { + class FailingZNSRootRegistrarDM extends ZNSRootRegistrarDM { + async deploy () { + throw new Error(errorMsgDeploy); + } + } + + const deployedNames = [ + znsNames.accessController, + znsNames.registry, + znsNames.domainToken, + { + contract: znsNames.meowToken.contractMock, + instance: znsNames.meowToken.instance, + }, + znsNames.addressResolver, + znsNames.curvePricer, + znsNames.treasury, + ]; + + const undeployedNames = [ + znsNames.rootRegistrar, + znsNames.fixedPricer, + znsNames.subRegistrar, + ]; + + // call test flow runner + await runTest({ + missionList: [ + ZNSAccessControllerDM, + ZNSRegistryDM, + ZNSDomainTokenDM, + MeowTokenDM, + ZNSAddressResolverDM, + ZNSCurvePricerDM, + ZNSTreasuryDM, + FailingZNSRootRegistrarDM, // failing DM + ZNSFixedPricerDM, + ZNSSubRegistrarDM, + ], + placeOfFailure: "deploy", + deployedNames, + undeployedNames, + failingInstanceName: "rootRegistrar", + }); + }); + + // eslint-disable-next-line max-len + it("[in RootRegistrar.postDeploy() hook] should start from post deploy sequence that failed on the previous run", async () => { + class FailingZNSRootRegistrarDM extends ZNSRootRegistrarDM { + async postDeploy () { + throw new Error(errorMsgPostDeploy); + } + } + + const deployedNames = [ + znsNames.accessController, + znsNames.registry, + znsNames.domainToken, + { + contract: znsNames.meowToken.contractMock, + instance: znsNames.meowToken.instance, + }, + znsNames.addressResolver, + znsNames.curvePricer, + znsNames.treasury, + znsNames.rootRegistrar, + ]; + + const undeployedNames = [ + znsNames.fixedPricer, + znsNames.subRegistrar, + ]; + + const checkPostDeploy = async (failingCampaign : DeployCampaign) => { + const { + // eslint-disable-next-line no-shadow + accessController, + // eslint-disable-next-line no-shadow + rootRegistrar, + } = failingCampaign; + + // we are checking that postDeploy did not grant REGISTRAR_ROLE to RootRegistrar + expect(await accessController.isRegistrar(rootRegistrar.address)).to.be.false; + }; + + // check contracts are deployed correctly + const { + nextCampaign, + } = await runTest({ + missionList: [ + ZNSAccessControllerDM, + ZNSRegistryDM, + ZNSDomainTokenDM, + MeowTokenDM, + ZNSAddressResolverDM, + ZNSCurvePricerDM, + ZNSTreasuryDM, + FailingZNSRootRegistrarDM, // failing DM + ZNSFixedPricerDM, + ZNSSubRegistrarDM, + ], + placeOfFailure: "postDeploy", + deployedNames, + undeployedNames, + failingInstanceName: "rootRegistrar", + callback: checkPostDeploy, + }); + + // make sure postDeploy() ran properly on the next run + const { + accessController, + rootRegistrar, + } = nextCampaign; + expect(await accessController.isRegistrar(rootRegistrar.address)).to.be.true; + }); + }); + + describe("Configurable Environment & Validation", () => { + // The `validate` function accepts the environment parameter only for the + // purpose of testing here as manipulating actual environment variables + // like `process.env. = "value"` is not possible in a test environment + // because the Hardhat process for running these tests will not respect these + // changes. `getConfig` calls to `validate` on its own, but never passes a value + // for the environment specifically, that is ever only inferred from the `process.env.ENV_LEVEL` + it("Gets the default configuration correctly", async () => { + // set the environment to get the appropriate variables + const localConfig : IDeployCampaignConfig = await getConfig( + deployAdmin, + zeroVault, + [governor.address], + [admin.address], + ); + + expect(localConfig.deployAdmin.address).to.eq(deployAdmin.address); + expect(localConfig.governorAddresses[0]).to.eq(governor.address); + expect(localConfig.governorAddresses[1]).to.be.undefined; + expect(localConfig.adminAddresses[0]).to.eq(admin.address); + expect(localConfig.adminAddresses[1]).to.be.undefined; + expect(localConfig.domainToken.name).to.eq(ZNS_DOMAIN_TOKEN_NAME); + expect(localConfig.domainToken.symbol).to.eq(ZNS_DOMAIN_TOKEN_SYMBOL); + expect(localConfig.domainToken.defaultRoyaltyReceiver).to.eq(deployAdmin.address); + expect(localConfig.domainToken.defaultRoyaltyFraction).to.eq(DEFAULT_ROYALTY_FRACTION); + expect(localConfig.rootPriceConfig).to.deep.eq(DEFAULT_PRICE_CONFIG); + }); + + it("Confirms encoding functionality works for env variables", async () => { + const sample = "0x123,0x456,0x789"; + const sampleFormatted = ["0x123", "0x456", "0x789"]; + const encoded = btoa(sample); + const decoded = atob(encoded).split(","); + expect(decoded).to.deep.eq(sampleFormatted); + }); + + it("Modifies config to use a random account as the deployer", async () => { + // Run the deployment a second time, clear the DB so everything is deployed + + let zns : TZNSContractState; + + const config : IDeployCampaignConfig = await getConfig( + userB, + userA, + [userB.address, admin.address], // governors + [userB.address, governor.address], // admins + ); + + const campaign = await runZnsCampaign({ + config, + }); + + const { dbAdapter } = campaign; + + /* eslint-disable-next-line prefer-const */ + zns = campaign.state.contracts; + + const rootPaymentConfig = await zns.treasury.paymentConfigs(ethers.constants.HashZero); + + expect(await zns.accessController.isAdmin(userB.address)).to.be.true; + expect(await zns.accessController.isAdmin(governor.address)).to.be.true; + expect(await zns.accessController.isGovernor(admin.address)).to.be.true; + expect(rootPaymentConfig.token).to.eq(zns.meowToken.address); + expect(rootPaymentConfig.beneficiary).to.eq(userA.address); + + await dbAdapter.dropDB(); + }); + + it("Fails when governor or admin addresses are given wrong", async () => { + // Custom addresses must given as the base64 encoded string of comma separated addresses + // e.g. btoa("0x123,0x456,0x789") = 'MHgxMjMsMHg0NTYsMHg3ODk=', which is what should be provided + // We could manipulate envariables through `process.env.` for this test and call `getConfig()` + // but the async nature of HH mocha tests causes this to mess up other tests + // Instead we use the same encoding functions used in `getConfig()` to test the functionality + + /* eslint-disable @typescript-eslint/no-explicit-any */ + try { + atob("[0x123,0x456]"); + } catch (e : any) { + expect(e.message).includes("Invalid character"); + } + + try { + atob("0x123, 0x456"); + } catch (e : any) { + expect(e.message).includes("Invalid character"); + } + + try { + atob("0x123 0x456"); + } catch (e : any) { + expect(e.message).includes("Invalid character"); + } + + try { + atob("'MHgxM jMsMHg0 NTYs MHg3ODk='"); + } catch (e : any) { + expect(e.message).includes("Invalid character"); + } + }); + it("Throws if env variable is invalid", async () => { + try { + const config = await getConfig( + deployAdmin, + zeroVault, + [deployAdmin.address, governor.address], + [deployAdmin.address, admin.address], + ); + + validate(config, "other"); + + /* eslint-disable @typescript-eslint/no-explicit-any */ + } catch (e : any) { + expect(e.message).includes(INVALID_ENV_ERR); + } + }); + + it("Fails to validate when mocking MEOW on prod", async () => { + try { + const config = await getConfig( + deployAdmin, + zeroVault, + [deployAdmin.address, governor.address], + [deployAdmin.address, admin.address], + ); + // Modify the config + config.mockMeowToken = true; + validate(config, "prod"); + + /* eslint-disable @typescript-eslint/no-explicit-any */ + } catch (e : any) { + expect(e.message).includes(NO_MOCK_PROD_ERR); + } + }); + + it("Fails to validate if not using the MEOW token on prod", async () => { + try { + const config = await getConfig( + deployAdmin, + zeroVault, + [deployAdmin.address, governor.address], + [deployAdmin.address, admin.address], + ); + + config.mockMeowToken = false; + config.stakingTokenAddress = "0x123"; + + validate(config, "prod"); + /* eslint-disable @typescript-eslint/no-explicit-any */ + } catch (e : any) { + expect(e.message).includes(STAKING_TOKEN_ERR); + } + }); + + it("Fails to validate if invalid curve for pricing", async () => { + try { + const config = await getConfig( + deployAdmin, + zeroVault, + [deployAdmin.address, governor.address], + [deployAdmin.address, admin.address], + ); + + config.mockMeowToken = false; + config.stakingTokenAddress = MeowMainnet.address; + config.rootPriceConfig.baseLength = BigNumber.from(3); + config.rootPriceConfig.maxLength = BigNumber.from(5); + config.rootPriceConfig.maxPrice = ethers.constants.Zero; + config.rootPriceConfig.minPrice = ethers.utils.parseEther("3"); + + validate(config, "prod"); + /* eslint-disable @typescript-eslint/no-explicit-any */ + } catch (e : any) { + expect(e.message).includes(INVALID_CURVE_ERR); + } + }); + + it("Fails to validate if no mongo uri or local URI in prod", async () => { + try { + const config = await getConfig( + deployAdmin, + zeroVault, + [deployAdmin.address, governor.address], + [deployAdmin.address, admin.address], + ); + + config.mockMeowToken = false; + config.stakingTokenAddress = MeowMainnet.address; + + // Normally we would call to an env variable to grab this value + const uri = ""; + + // Falls back onto the default URI which is for localhost and fails in prod + validate(config, "prod", uri); + /* eslint-disable @typescript-eslint/no-explicit-any */ + } catch (e : any) { + expect(e.message).includes(MONGO_URI_ERR); + } + + try { + const config = await getConfig( + deployAdmin, + zeroVault, + [deployAdmin.address, governor.address], + [deployAdmin.address, admin.address], + ); + + config.mockMeowToken = false; + config.stakingTokenAddress = MeowMainnet.address; + + // Normally we would call to an env variable to grab this value + const uri = "mongodb://localhost:27018"; + + validate(config, "prod", uri); + /* eslint-disable @typescript-eslint/no-explicit-any */ + } catch (e : any) { + expect(e.message).includes(MONGO_URI_ERR); + } + }); + }); + + // TODO dep: add more versioning tests here for DB versions! + describe("Versioning", () => { + let campaign : DeployCampaign; + + before(async () => { + campaignConfig = { + deployAdmin, + governorAddresses: [deployAdmin.address], + adminAddresses: [deployAdmin.address, admin.address], + domainToken: { + name: ZNS_DOMAIN_TOKEN_NAME, + symbol: ZNS_DOMAIN_TOKEN_SYMBOL, + defaultRoyaltyReceiver: deployAdmin.address, + defaultRoyaltyFraction: DEFAULT_ROYALTY_FRACTION, + }, + rootPriceConfig: DEFAULT_PRICE_CONFIG, + zeroVaultAddress: zeroVault.address, + stakingTokenAddress: MeowMainnet.address, + mockMeowToken: true, + postDeploy: { + tenderlyProjectSlug: "", + monitorContracts: false, + verifyContracts: false, + }, + }; + + campaign = await runZnsCampaign({ + config: campaignConfig, + }); + }); + + it("should get the correct git tag + commit hash and write to DB", async () => { + const latestGitTag = (await execAsync("git describe --tags --abbrev=0")).stdout.trim(); + const latestCommit = (await execAsync(`git rev-list -n 1 ${latestGitTag}`)).stdout.trim(); + + const fullGitTag = `${latestGitTag}:${latestCommit}`; + + const { dbAdapter } = campaign; + + const versionDoc = await dbAdapter.getLatestVersion(); + expect(versionDoc?.contractsVersion).to.equal(fullGitTag); + + const deployedVersion = await dbAdapter.getDeployedVersion(); + expect(deployedVersion?.contractsVersion).to.equal(fullGitTag); + }); + }); + + describe("Verify - Monitor", () => { + let config : IDeployCampaignConfig; + + before (async () => { + [deployAdmin, admin, zeroVault] = await hre.ethers.getSigners(); + + config = { + deployAdmin, + governorAddresses: [deployAdmin.address], + adminAddresses: [deployAdmin.address, admin.address], + domainToken: { + name: ZNS_DOMAIN_TOKEN_NAME, + symbol: ZNS_DOMAIN_TOKEN_SYMBOL, + defaultRoyaltyReceiver: deployAdmin.address, + defaultRoyaltyFraction: DEFAULT_ROYALTY_FRACTION, + }, + rootPriceConfig: DEFAULT_PRICE_CONFIG, + zeroVaultAddress: zeroVault.address, + stakingTokenAddress: MeowMainnet.address, + mockMeowToken: true, + postDeploy: { + tenderlyProjectSlug: "", + monitorContracts: false, + verifyContracts: true, + }, + }; + }); + + afterEach(async () => { + await mongoAdapter.dropDB(); + }); + + it("should prepare the correct data for each contract when verifying on Etherscan", async () => { + const verifyData : Array<{ address : string; ctorArgs ?: TDeployArgs; }> = []; + class HardhatDeployerMock extends HardhatDeployer { + async etherscanVerify (args : { + address : string; + ctorArgs ?: TDeployArgs; + }) { + verifyData.push(args); + } + } + + const deployer = new HardhatDeployerMock(deployAdmin); + + const campaign = await runZnsCampaign({ + deployer, + config, + }); + + const { state: { contracts } } = campaign; + ({ dbAdapter: mongoAdapter } = campaign); + + Object.values(contracts).forEach(({ address }, idx) => { + if (idx === 0) { + expect(verifyData[idx].ctorArgs).to.be.deep.eq([config.governorAddresses, config.adminAddresses]); + } + + expect(verifyData[idx].address).to.equal(address); + }); + }); + + it.only("should prepare the correct contract data when pushing to Tenderly Project", async () => { + let tenderlyData : Array = []; + class HardhatDeployerMock extends HardhatDeployer { + async tenderlyVerify (contracts : Array) { + tenderlyData = contracts; + } + } + + const deployer = new HardhatDeployerMock(deployAdmin); + + config.postDeploy.monitorContracts = true; + config.postDeploy.verifyContracts = false; + + const campaign = await runZnsCampaign({ + deployer, + config, + }); + + const { state: { instances } } = campaign; + ({ dbAdapter: mongoAdapter } = campaign); + + let idx = 0; + await Object.values(instances).reduce( + async (acc, instance) => { + await acc; + + const dbData = await instance.getFromDB(); + + if (instance.proxyData.isProxy) { + const proxyName = instance.proxyData.kind === ProxyKinds.uups + ? erc1967ProxyName + : transparentProxyName; + // check proxy + expect(tenderlyData[idx].address).to.be.eq(dbData?.address); + expect(tenderlyData[idx].name).to.be.eq(proxyName); + + // check impl + expect(tenderlyData[idx + 1].address).to.be.eq(dbData?.implementation); + expect(tenderlyData[idx + 1].name).to.be.eq(dbData?.name); + expect(tenderlyData[idx + 1].name).to.be.eq(instance.contractName); + idx += 2; + } else { + expect(tenderlyData[idx].address).to.equal(dbData?.address); + expect(tenderlyData[idx].name).to.equal(dbData?.name); + expect(tenderlyData[idx].name).to.equal(instance.contractName); + idx++; + } + }, + Promise.resolve() + ); + }); + }); +}); \ No newline at end of file From aa17b11f2f33211258b104b045f0172673847d23 Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 21 Nov 2023 16:37:36 -0800 Subject: [PATCH 07/13] small errors --- src/deploy/deployer/hardhat-deployer.ts | 1 - test/DeployCampaignInt.test.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/deploy/deployer/hardhat-deployer.ts b/src/deploy/deployer/hardhat-deployer.ts index b828e155..58bc62d1 100644 --- a/src/deploy/deployer/hardhat-deployer.ts +++ b/src/deploy/deployer/hardhat-deployer.ts @@ -2,7 +2,6 @@ import * as hre from "hardhat"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { TDeployArgs, TProxyKind } from "../missions/types"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import assert from "assert"; import { ContractByName } from "@tenderly/hardhat-tenderly/dist/tenderly/types"; export class HardhatDeployer { diff --git a/test/DeployCampaignInt.test.ts b/test/DeployCampaignInt.test.ts index e5529366..d428c454 100644 --- a/test/DeployCampaignInt.test.ts +++ b/test/DeployCampaignInt.test.ts @@ -920,7 +920,7 @@ describe("Deploy Campaign Test", () => { }); }); - it.only("should prepare the correct contract data when pushing to Tenderly Project", async () => { + it("should prepare the correct contract data when pushing to Tenderly Project", async () => { let tenderlyData : Array = []; class HardhatDeployerMock extends HardhatDeployer { async tenderlyVerify (contracts : Array) { From cad49ebe6e04d6fb8b8b02fb1d44abafba64aa63 Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 21 Nov 2023 16:40:45 -0800 Subject: [PATCH 08/13] export git tag saver and use in test, rename save-tag-bash to save-tag --- package.json | 5 ++--- src/utils/git-tag/save-tag.ts | 12 ++---------- test/DeployCampaignInt.test.ts | 3 +++ 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index a4ee633a..a8ae761d 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "lint": "yarn lint-sol & yarn lint-ts --no-error-on-unmatched-pattern", "clean": "hardhat clean", "build": "yarn run clean && yarn run compile", - "postbuild": "yarn save-tag-bash", + "postbuild": "yarn save-tag", "typechain": "hardhat typechain", "pretest": "yarn mongo:start", "test": "hardhat test", @@ -28,8 +28,7 @@ "devnet": "ts-node src/tenderly/devnet/devnet-execute.ts", "gas-cost": "ts-node src/utils/gas-costs.ts", "docgen": "hardhat docgen", - "save-tag": "ts-node src/utils/git-tag/save-tag.ts", - "save-tag-bash": "chmod a+x ./src/utils/git-tag/save-tag.sh && bash ./src/utils/git-tag/save-tag.sh", + "save-tag": "chmod a+x ./src/utils/git-tag/save-tag.sh && bash ./src/utils/git-tag/save-tag.sh", "mongo:start": "docker-compose up -d", "mongo:stop": "docker-compose stop", "mongo:down": "docker-compose down", diff --git a/src/utils/git-tag/save-tag.ts b/src/utils/git-tag/save-tag.ts index 6eccbbe9..2d04781b 100644 --- a/src/utils/git-tag/save-tag.ts +++ b/src/utils/git-tag/save-tag.ts @@ -9,7 +9,7 @@ const execAsync = promisify(exec); const logger = getLogger(); -const acquireLatestGitTag = async () => { +export const acquireLatestGitTag = async () => { const gitTag = await execAsync("git describe --tags --abbrev=0"); const tag = gitTag.stdout.trim(); @@ -24,17 +24,9 @@ const acquireLatestGitTag = async () => { return full; }; -const saveTag = async () => { +export const saveTag = async () => { const tag = await acquireLatestGitTag(); fs.writeFileSync(tagFilePath, tag, "utf8"); logger.info(`Saved git tag-commit to ${tagFilePath}}`); }; - -saveTag() - // eslint-disable-next-line @typescript-eslint/no-empty-function - .then(process.exit(0)) - .catch(e => { - logger.error(e); - process.exit(1); - }); diff --git a/test/DeployCampaignInt.test.ts b/test/DeployCampaignInt.test.ts index d428c454..0669cd2c 100644 --- a/test/DeployCampaignInt.test.ts +++ b/test/DeployCampaignInt.test.ts @@ -39,6 +39,7 @@ import { promisify } from "util"; import { exec } from "child_process"; import { TDeployArgs } from "../src/deploy/missions/types"; import { ContractByName } from "@tenderly/hardhat-tenderly/dist/tenderly/types"; +import { saveTag } from "../src/utils/git-tag/save-tag"; const execAsync = promisify(exec); @@ -816,6 +817,8 @@ describe("Deploy Campaign Test", () => { let campaign : DeployCampaign; before(async () => { + await saveTag(); + campaignConfig = { deployAdmin, governorAddresses: [deployAdmin.address], From 095c2b7ae5fe16311d79c8d393cb8a2d67620407 Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 21 Nov 2023 16:41:19 -0800 Subject: [PATCH 09/13] fix deploy helper --- test/helpers/deploy/deploy-zns.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helpers/deploy/deploy-zns.ts b/test/helpers/deploy/deploy-zns.ts index ded295e6..d5615c39 100644 --- a/test/helpers/deploy/deploy-zns.ts +++ b/test/helpers/deploy/deploy-zns.ts @@ -35,7 +35,6 @@ import { registrarName, registryName, subRegistrarName, - transparentProxyName, treasuryName, meowTokenMockName, ZNS_DOMAIN_TOKEN_NAME, @@ -48,6 +47,7 @@ import { getProxyImplAddress } from "../utils"; import { BigNumber } from "ethers"; import { ICurvePriceConfig } from "../../../src/deploy/missions/types"; import { meowTokenName, meowTokenSymbol } from "../../../src/deploy/missions/contracts"; +import { transparentProxyName } from "../../../src/deploy/missions/contracts/names"; export const deployAccessController = async ({ From 0b57813e215f7f3e01758618f2160be3c912df51 Mon Sep 17 00:00:00 2001 From: Kirill Date: Wed, 22 Nov 2023 12:23:06 -0800 Subject: [PATCH 10/13] add ENV vars and their validation --- src/deploy/campaign/environments.ts | 17 ++++++++++++++++- src/deploy/run-campaign.ts | 3 +-- test/DeployCampaignInt.test.ts | 2 ++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/deploy/campaign/environments.ts b/src/deploy/campaign/environments.ts index 76bc360f..c298d779 100644 --- a/src/deploy/campaign/environments.ts +++ b/src/deploy/campaign/environments.ts @@ -125,6 +125,11 @@ export const getConfig = ( zeroVaultAddress: process.env.ZERO_VAULT_ADDRESS ? process.env.ZERO_VAULT_ADDRESS : zeroVault.address, mockMeowToken: process.env.MOCK_MEOW_TOKEN ? !!process.env.MOCK_MEOW_TOKEN : true, stakingTokenAddress: process.env.STAKING_TOKEN_ADDRESS ? process.env.STAKING_TOKEN_ADDRESS : MeowMainnet.address, + postDeploy: { + tenderlyProjectSlug: process.env.TENDERLY_PROJECT_SLUG ? process.env.TENDERLY_PROJECT_SLUG : "", + monitorContracts: process.env.MONITOR_CONTRACTS ? !!process.env.MONITOR_CONTRACTS : false, + verifyContracts: process.env.VERIFY_CONTRACTS ? !!process.env.VERIFY_CONTRACTS : false, + }, }; // Will throw an error based on any invalid setup, given the `ENV_LEVEL` set @@ -159,9 +164,19 @@ export const validate = ( requires(config.stakingTokenAddress === MeowMainnet.address, STAKING_TOKEN_ERR); requires(validatePrice(config.rootPriceConfig), INVALID_CURVE_ERR); requires(!mongoUri.includes("localhost"), MONGO_URI_ERR); + + if (config.postDeploy.verifyContracts) { + requires(!!process.env.ETHERSCAN_API_KEY, "Must provide an Etherscan API Key to verify contracts"); + } + + if (config.postDeploy.monitorContracts) { + requires(!!process.env.TENDERLY_PROJECT_SLUG, "Must provide a Tenderly Project Slug to monitor contracts"); + requires(!!process.env.TENDERLY_ACCOUNT_ID, "Must provide a Tenderly Account ID to monitor contracts"); + requires(!!process.env.TENDERLY_ACCESS_KEY, "Must provide a Tenderly Access Key to monitor contracts"); + } } - // If we reach this code, there is an env variable but it's not valid. + // If we reach this code, there is an env variable, but it's not valid. throw new Error(INVALID_ENV_ERR); }; diff --git a/src/deploy/run-campaign.ts b/src/deploy/run-campaign.ts index 42b82762..f655ac5b 100644 --- a/src/deploy/run-campaign.ts +++ b/src/deploy/run-campaign.ts @@ -9,14 +9,13 @@ const runCampaign = async () => { const [deployer, zeroVault] = await hre.ethers.getSigners(); // Reading `ENV_LEVEL` environment variable to determine rules to be enforced - const config = await getConfig( + const config = getConfig( deployer, zeroVault, ); await runZnsCampaign({ config, - logger, }); }; diff --git a/test/DeployCampaignInt.test.ts b/test/DeployCampaignInt.test.ts index 0669cd2c..dcc4fbeb 100644 --- a/test/DeployCampaignInt.test.ts +++ b/test/DeployCampaignInt.test.ts @@ -867,6 +867,8 @@ describe("Deploy Campaign Test", () => { before (async () => { [deployAdmin, admin, zeroVault] = await hre.ethers.getSigners(); + // TODO dep ver: add proper checks for the new config values + // and make sure tenderly and etherscan ENV data is acquired correctly config = { deployAdmin, governorAddresses: [deployAdmin.address], From 41e3fa9ac575524e3fb357ca2ee4c10b34c67f5c Mon Sep 17 00:00:00 2001 From: Kirill Date: Wed, 22 Nov 2023 12:44:32 -0800 Subject: [PATCH 11/13] fix tests --- test/DeployCampaignInt.test.ts | 2 +- test/ZNSRootRegistrar.test.ts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/test/DeployCampaignInt.test.ts b/test/DeployCampaignInt.test.ts index dcc4fbeb..9956f139 100644 --- a/test/DeployCampaignInt.test.ts +++ b/test/DeployCampaignInt.test.ts @@ -632,7 +632,7 @@ describe("Deploy Campaign Test", () => { let zns : TZNSContractState; - const config : IDeployCampaignConfig = await getConfig( + const config : IDeployCampaignConfig = getConfig( userB, userA, [userB.address, admin.address], // governors diff --git a/test/ZNSRootRegistrar.test.ts b/test/ZNSRootRegistrar.test.ts index 3967543f..d89b8f93 100644 --- a/test/ZNSRootRegistrar.test.ts +++ b/test/ZNSRootRegistrar.test.ts @@ -69,18 +69,15 @@ describe("ZNSRootRegistrar", () => { // zeroVault address is used to hold the fee charged to the user when registering [deployer, zeroVault, user, operator, governor, admin, randomUser] = await hre.ethers.getSigners(); - const config : IDeployCampaignConfig = await getConfig( + const config : IDeployCampaignConfig = getConfig( deployer, zeroVault, [deployer.address, governor.address], [deployer.address, admin.address], ); - const logger = getLogger(); - const campaign = await runZnsCampaign({ config, - logger, }); zns = campaign.state.contracts; From 51d321c8ad8466e1cbe9ee9c44979fbc6cace922 Mon Sep 17 00:00:00 2001 From: Kirill Date: Wed, 22 Nov 2023 12:44:47 -0800 Subject: [PATCH 12/13] move URLs for networks to ENV vars --- hardhat.config.ts | 8 ++------ src/deploy/campaign/environments.ts | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 7663e14e..53808368 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -98,21 +98,17 @@ const config : HardhatUserConfig = { }, networks: { mainnet: { - url: "https://mainnet.infura.io/v3/97e75e0bbc6a4419a5dd7fe4a518b917", + url: `${process.env.MAINNET_RPC_URL}`, gasPrice: 80000000000, }, sepolia: { - url: "https://eth-sepolia.g.alchemy.com/v2/mX2eTgCe-osaWbsEJ1s7sRgmjZ1c178y", + url: `${process.env.SEPOLIA_RPC_URL}`, timeout: 10000000, // accounts: [ // Comment out for CI, uncomment this when using Sepolia // `${process.env.TESTNET_PRIVATE_KEY_A}`, // `${process.env.TESTNET_PRIVATE_KEY_B}`, // ] }, - goerli: { - url: "https://goerli.infura.io/v3/77c3d733140f4c12a77699e24cb30c27", - timeout: 10000000, - }, devnet: { // Add current URL that you spawned if not using automated spawning url: `${process.env.DEVNET_RPC_URL}`, diff --git a/src/deploy/campaign/environments.ts b/src/deploy/campaign/environments.ts index c298d779..25b6c108 100644 --- a/src/deploy/campaign/environments.ts +++ b/src/deploy/campaign/environments.ts @@ -127,8 +127,8 @@ export const getConfig = ( stakingTokenAddress: process.env.STAKING_TOKEN_ADDRESS ? process.env.STAKING_TOKEN_ADDRESS : MeowMainnet.address, postDeploy: { tenderlyProjectSlug: process.env.TENDERLY_PROJECT_SLUG ? process.env.TENDERLY_PROJECT_SLUG : "", - monitorContracts: process.env.MONITOR_CONTRACTS ? !!process.env.MONITOR_CONTRACTS : false, - verifyContracts: process.env.VERIFY_CONTRACTS ? !!process.env.VERIFY_CONTRACTS : false, + monitorContracts: process.env.MONITOR_CONTRACTS === "true", + verifyContracts: process.env.VERIFY_CONTRACTS === "true", }, }; From 1aed845241da0a8f18ebc12a837fa2dc93806706 Mon Sep 17 00:00:00 2001 From: Kirill Date: Wed, 22 Nov 2023 16:38:58 -0800 Subject: [PATCH 13/13] try fixing the yaml --- hardhat.config.ts | 2 +- tenderly.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 53808368..bb4eda5a 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -35,7 +35,7 @@ subtask(TASK_TEST_RUN_MOCHA_TESTS) // does not work properly locally or in CI, so we // keep it commented out and uncomment when using DevNet // locally. -// !!! Uncomment this when using Tenderly DevNet !!! +// !!! Uncomment this when using Tenderly !!! // tenderly.setup({ automaticVerifications: false }); const config : HardhatUserConfig = { diff --git a/tenderly.yaml b/tenderly.yaml index a8d04310..1640f689 100644 --- a/tenderly.yaml +++ b/tenderly.yaml @@ -1,4 +1,6 @@ -account_id: e750f9f2-65bc-4765-84a8-7f8dbddd73ee +account_id: "zer0-os" +project_slug: "zns-sepolia-test" +provider: "" exports: hardhat: project_slug: zer0-os/zns-1 @@ -17,5 +19,3 @@ exports: istanbul_block: 0 berlin_block: 0 london_block: 0 -project_slug: zer0-os/zns-1 -provider: ""