diff --git a/.circleci/config.yml b/.circleci/config.yml index f3efe8dd..a3e13bda 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,7 +28,7 @@ jobs: command: yarn check-coverage #Validate code is adequately covered by unit tests - persist_to_workspace: root: ~/repo - paths: . + paths: [.] publish: <<: *defaults steps: diff --git a/package.json b/package.json index 034d1ba6..0d413def 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,9 @@ "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", "typechain": "hardhat typechain", - "pretest": "yarn mongo:start", + "pretest": "yarn mongo:start && yarn save-tag", "test": "hardhat test", "test-local": "yarn test", "posttest": "yarn mongo:stop", @@ -27,6 +28,8 @@ "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", "mongo:start": "docker-compose up -d", "mongo:stop": "docker-compose stop", "mongo:down": "docker-compose down", diff --git a/src/deploy/db/mongo-adapter/mongo-adapter.ts b/src/deploy/db/mongo-adapter/mongo-adapter.ts index 2c427f2c..94071180 100644 --- a/src/deploy/db/mongo-adapter/mongo-adapter.ts +++ b/src/deploy/db/mongo-adapter/mongo-adapter.ts @@ -1,9 +1,11 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ +import fs from "fs"; import { TLogger } from "../../campaign/types"; import { Collection, Db, MongoClient, MongoClientOptions } from "mongodb"; import { IDBVersion, IMongoDBAdapterArgs } from "./types"; import { COLL_NAMES, VERSION_TYPES } from "./constants"; import { IContractDbData } from "../types"; +import { tagFilePath } from "../../../utils/git-tag/constants"; // eslint-disable-next-line @typescript-eslint/no-var-requires require("dotenv").config(); @@ -84,7 +86,9 @@ export class MongoDBAdapter { // Contract methods async getContract (contractName : string, version ?: string) { - if (!version) version = await this.getCheckLatestVersion(); + if (!version) { + ({ dbVersion: version } = await this.getCheckLatestVersion()); + } return this.contracts.findOne({ name: contractName, @@ -93,7 +97,9 @@ export class MongoDBAdapter { } async writeContract (contractName : string, data : IContractDbData, version ?: string) { - if (!version) version = await this.getCheckLatestVersion(); + if (!version) { + ({ dbVersion: version } = await this.getCheckLatestVersion()); + } await this.contracts.insertOne({ ...data, @@ -119,10 +125,10 @@ export class MongoDBAdapter { if (version) { finalVersion = version; - if (version !== deployedV || !deployedV) { + if (!deployedV || version !== deployedV.dbVersion) { // we should only have a single TEMP version at any given time - if (version !== tempV && tempV) { - await this.clearDBForVersion(tempV); + if (tempV && version !== tempV.dbVersion) { + await this.clearDBForVersion(tempV.dbVersion); } await this.createUpdateTempVersion(finalVersion); @@ -134,10 +140,10 @@ export class MongoDBAdapter { this.logger.info(`No version provided to MongoDBAdapter, using current timestamp as new TEMP version: ${finalVersion}`); await this.createUpdateTempVersion(finalVersion); } else if (!deployedV) { - finalVersion = tempV as string; + finalVersion = tempV?.dbVersion as string; this.logger.info(`Using existing MongoDB TEMP version: ${finalVersion}`); } else { - finalVersion = deployedV; + finalVersion = deployedV.dbVersion; this.logger.info(`Using existing MongoDB DEPLOYED version: ${finalVersion}`); } } @@ -146,11 +152,11 @@ export class MongoDBAdapter { } async finalizeDeployedVersion (version ?: string) { - const finalV = version || await this.getTempVersion(); + const finalV = version || (await this.getTempVersion())?.dbVersion; if (!finalV) return; - const deployedV = await this.getDeployedVersion(); + const deployedV = (await this.getDeployedVersion())?.dbVersion; if (finalV !== deployedV) { // archive the current DEPLOYED version await this.versions.updateOne( @@ -166,7 +172,8 @@ export class MongoDBAdapter { // create new DEPLOYED version await this.versions.insertOne({ type: VERSION_TYPES.deployed, - version: finalV, + dbVersion: finalV, + contractsVersion: this.getContractsVersionFromFile(), }); // now remove the TEMP version @@ -198,27 +205,27 @@ export class MongoDBAdapter { return v; } - async getTempVersion () : Promise { + async getTempVersion () : Promise { const v = await this.versions.findOne({ type: VERSION_TYPES.temp, }); if (!v) return null; - return v.version; + return v; } - async getDeployedVersion () : Promise { + async getDeployedVersion () : Promise { const v = await this.versions.findOne({ type: VERSION_TYPES.deployed, }); if (!v) return null; - return v.version; + return v; } - async getLatestVersion () : Promise { + async getLatestVersion () : Promise { const v = await this.getTempVersion(); if (v) return v; @@ -226,12 +233,26 @@ export class MongoDBAdapter { return this.getDeployedVersion(); } + getContractsVersionFromFile () { + if (!fs.existsSync(tagFilePath)) { + throw Error(`No git tag found at ${tagFilePath}`); + } + + const tag = fs.readFileSync(tagFilePath, "utf8").trim(); + this.logger.info(`Git tag found at ${tagFilePath}: ${tag}`); + + return tag; + } + async createUpdateTempVersion (version : string) { + const contractsVersion = this.getContractsVersionFromFile(); + return this.versions.updateOne({ type: VERSION_TYPES.temp, }, { $set: { - version, + dbVersion: version, + contractsVersion, }, }, { upsert: true, diff --git a/src/deploy/db/mongo-adapter/types.ts b/src/deploy/db/mongo-adapter/types.ts index a8184e82..910ae0f5 100644 --- a/src/deploy/db/mongo-adapter/types.ts +++ b/src/deploy/db/mongo-adapter/types.ts @@ -10,6 +10,7 @@ export interface IMongoDBAdapterArgs { } export interface IDBVersion { - version : string; + dbVersion : string; + contractsVersion : string; type : string; } diff --git a/src/deploy/zns-campaign.ts b/src/deploy/zns-campaign.ts index 1ee7565c..2202616e 100644 --- a/src/deploy/zns-campaign.ts +++ b/src/deploy/zns-campaign.ts @@ -1,4 +1,4 @@ -import { IDeployCampaignConfig, TLogger } from "./campaign/types"; +import { IDeployCampaignConfig } from "./campaign/types"; import { HardhatDeployer } from "./deployer/hardhat-deployer"; import { DeployCampaign } from "./campaign/deploy-campaign"; import { @@ -11,20 +11,21 @@ import { import * as hre from "hardhat"; import { getMongoAdapter } from "./db/mongo-adapter/get-adapter"; +import { getLogger } from "./logger/create-logger"; export const runZnsCampaign = async ({ config, - logger, dbVersion, } : { config : IDeployCampaignConfig; - logger : TLogger; dbVersion ?: string; }) => { // TODO dep: figure out the best place to put this at! hre.upgrades.silenceWarnings(); + const logger = getLogger(); + const deployer = new HardhatDeployer(config.deployAdmin); const dbAdapter = await getMongoAdapter(); diff --git a/src/utils/git-tag/constants.ts b/src/utils/git-tag/constants.ts new file mode 100644 index 00000000..506f7fe3 --- /dev/null +++ b/src/utils/git-tag/constants.ts @@ -0,0 +1,2 @@ +export const tagFile = "git-tag.txt"; +export const tagFilePath = `${process.cwd()}/artifacts/${tagFile}`; diff --git a/src/utils/git-tag/get-tag.ts b/src/utils/git-tag/get-tag.ts new file mode 100644 index 00000000..486e0c2f --- /dev/null +++ b/src/utils/git-tag/get-tag.ts @@ -0,0 +1,17 @@ +import fs from "fs"; +import { tagFilePath } from "./constants"; +import { getLogger } from "../../deploy/logger/create-logger"; + + +const logger = getLogger(); + +export const getGitTag = () => { + if (!fs.existsSync(tagFilePath)) { + throw Error(`No git tag found at ${tagFilePath}`); + } + + const tag = fs.readFileSync(tagFilePath, "utf8").trim(); + logger.info(`Git tag found at ${tagFilePath}: ${tag}`); + + return tag; +}; diff --git a/src/utils/git-tag/save-tag.sh b/src/utils/git-tag/save-tag.sh new file mode 100644 index 00000000..efd181a5 --- /dev/null +++ b/src/utils/git-tag/save-tag.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +tag=$(git describe --tags --abbrev=0) +echo "Last tag: $tag" +commit=$(git rev-list -n 1 "$tag") +echo "Commit: $commit" +echo "$tag:$commit" > ./artifacts/git-tag.txt +echo "Git tag saved to ./artifacts/git-tag.txt" \ No newline at end of file diff --git a/src/utils/git-tag/save-tag.ts b/src/utils/git-tag/save-tag.ts new file mode 100644 index 00000000..6eccbbe9 --- /dev/null +++ b/src/utils/git-tag/save-tag.ts @@ -0,0 +1,40 @@ +import { exec } from "child_process"; +import { promisify } from "util"; +import { getLogger } from "../../deploy/logger/create-logger"; +import fs from "fs"; +import { tagFilePath } from "./constants"; + + +const execAsync = promisify(exec); +const logger = getLogger(); + + +const acquireLatestGitTag = async () => { + const gitTag = await execAsync("git describe --tags --abbrev=0"); + const tag = gitTag.stdout.trim(); + + logger.info(`Latest git tag acquired: ${tag}`); + + const commitHash = await execAsync(`git rev-list -n 1 ${tag}`); + const commit = commitHash.stdout.trim(); + + const full = `${tag}:${commit}`; + logger.info(`Git commit hash acquired for tag: ${commit}. Full: ${full}`); + + return full; +}; + +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/DeployCampaign.test.ts b/test/DeployCampaign.test.ts index 414735f0..a23fe72e 100644 --- a/test/DeployCampaign.test.ts +++ b/test/DeployCampaign.test.ts @@ -25,7 +25,6 @@ import { } from "../src/deploy/missions/contracts"; import { znsNames } from "../src/deploy/missions/contracts/names"; import { IDeployCampaignConfig, TZNSContractState, TLogger } from "../src/deploy/campaign/types"; -import { getLogger } from "../src/deploy/logger/create-logger"; 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"; @@ -36,6 +35,10 @@ 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", () => { @@ -51,9 +54,6 @@ describe("Deploy Campaign Test", () => { let mongoAdapter : MongoDBAdapter; - // TODO dep: move logger to runZNSCampaign() - const logger = getLogger(); - before(async () => { [deployAdmin, admin, governor, zeroVault, userA, userB] = await hre.ethers.getSigners(); }); @@ -81,7 +81,6 @@ describe("Deploy Campaign Test", () => { it("should deploy new MeowTokenMock when `mockMeowToken` is true", async () => { const campaign = await runZnsCampaign({ config: campaignConfig, - logger, }); const { meowToken, dbAdapter } = campaign; @@ -117,7 +116,6 @@ describe("Deploy Campaign Test", () => { const campaign = await runZnsCampaign({ config: campaignConfig, - logger, }); const { @@ -262,7 +260,6 @@ describe("Deploy Campaign Test", () => { // run Campaign again, but normally const nextCampaign = await runZnsCampaign({ config: campaignConfig, - logger, }); ({ dbAdapter } = nextCampaign); @@ -626,11 +623,8 @@ describe("Deploy Campaign Test", () => { [userB.address, governor.address], // admins ); - const logger = getLogger(); - const campaign = await runZnsCampaign({ config, - logger, }); const { dbAdapter } = campaign; @@ -801,4 +795,46 @@ describe("Deploy Campaign Test", () => { } }); }); + + // 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); + }); + }); });