From af4777080becff412977ddb4ff511a210c00acbc Mon Sep 17 00:00:00 2001 From: RnkSngh Date: Wed, 23 Oct 2024 16:21:23 -0400 Subject: [PATCH] add support for multiple solc versions (as is required to support optimism contracts deployments) --- package.json | 2 +- src/deploy.ts | 30 +++++++++++--- src/evm/schemas/contract.ts | 1 + src/scripts/update-contracts-script.ts | 10 +++-- src/scripts/verify-contract-script.ts | 12 +++--- src/utils/constants.ts | 2 + src/utils/io.ts | 55 +++++++++++++++++--------- 7 files changed, 78 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 2b7d2233..bb8a3220 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@open-ibc/vibc-core-smart-contracts", - "version": "4.0.5", + "version": "4.0.6", "main": "dist/index.js", "bin": { "verify-vibc-core-smart-contracts": "./dist/scripts/verify-contract-script.js", diff --git a/src/deploy.ts b/src/deploy.ts index d1178d15..e2f87f0d 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -38,6 +38,21 @@ export async function updateNoncesForSender( return nonces; } +// Converts a factory to a name. If a solc version is specified (which needs to happen for multiple solc versions in a foundry/hardhat project, then it will return the file with the constructed name) +export const getFactoryFileName = ( + factoryName: string, + solcVersion: string | undefined +) => { + if (!solcVersion) return `${factoryName}__factory`; + + // Filter version string to remove periods e.g. 0.8.15 -> 0815 + const versionStr = solcVersion + .split("") + .filter((c) => c !== "." && c !== "v") + .join(""); + return `${factoryName}${versionStr}__factory`; +}; + /** * Return deployment libraries, factory, factory constructor, * and rendered arguments for a contract deployment @@ -48,11 +63,12 @@ const getDeployData = ( env: StringToStringMap, libraries: any[] = [], init: { args: any[]; signature: string } | undefined, - contractFactories: Record + contractFactories: Record, + solcVersion: string | undefined ) => { + const contractFactoryFileName = getFactoryFileName(factoryName, solcVersion); // @ts-ignore - const contractFactoryConstructor = - contractFactories[`${factoryName}__factory`]; + const contractFactoryConstructor = contractFactories[contractFactoryFileName]; assert( contractFactoryConstructor, `cannot find contract factory constructor for contract: ${factoryName}` @@ -67,7 +83,7 @@ const getDeployData = ( const factory = new contractFactoryConstructor(...libs); if (!factory) { throw new Error( - `cannot load contract factory for contract: ${factoryName} with factory name: ${factoryName}__factory` + `cannot load contract factory for contract: ${factoryName} from factory file: ${contractFactoryFileName}` ); } @@ -107,7 +123,8 @@ export const deployContract = async ( env, contract.libraries, contract.init, - contractFactories + contractFactories, + contract.solcVersion ); logger.info( @@ -115,7 +132,7 @@ export const deployContract = async ( contract.name } with args: [${constructorData.args}] with libraries: ${JSON.stringify( constructorData.libraries - )}` + )} ` ); let deployedAddr = `new.${contract.name}.address`; const deployer = accountRegistry.mustGet( @@ -164,6 +181,7 @@ export const deployContract = async ( name: contract.name, args: constructorData.args, libraries: constructorData.libraries, + solcVersion: contract.solcVersion, }; writeDeployedContractToFile(chain, contractObject); } diff --git a/src/evm/schemas/contract.ts b/src/evm/schemas/contract.ts index c30e7dbc..82888459 100644 --- a/src/evm/schemas/contract.ts +++ b/src/evm/schemas/contract.ts @@ -27,6 +27,7 @@ export const ContractItemSchema = z }) ), abi: z.optional(z.any()), + solcVersion: z.optional(z.string()), }) .strict(); diff --git a/src/scripts/update-contracts-script.ts b/src/scripts/update-contracts-script.ts index f000836e..1039b059 100644 --- a/src/scripts/update-contracts-script.ts +++ b/src/scripts/update-contracts-script.ts @@ -8,7 +8,7 @@ import { UPDATE_SPECS_PATH } from "../utils/constants"; import { parseArgsFromCLI } from "../utils/io"; async function main() { - const { chain, accounts, args, extraBindingsPath, extraArtifactsPath } = + const { chain, accounts, args, extraBindingsPath, externalContractsPath } = await parseArgsFromCLI(); const updateSpecs = (args.UPDATE_SPECS_PATH as string) || UPDATE_SPECS_PATH; @@ -27,17 +27,21 @@ async function main() { } } + const existingContracts = externalContractsPath + ? ContractRegistryLoader.loadSingle(externalContractsPath) + : ContractRegistryLoader.emptySingle(); + updateContractsForChain( chain, accounts.mustGet(chain.chainName), - ContractRegistryLoader.emptySingle(), + existingContracts, contractUpdates, getOutputLogger(), { dryRun: false, forceDeployNewContracts: false, writeContracts: true, - extraContractFactories: extraContractFactories ?? {}, + extraContractFactories: extraContractFactories ?? undefined, } ); } diff --git a/src/scripts/verify-contract-script.ts b/src/scripts/verify-contract-script.ts index 2f5c78d0..fcecd0a0 100644 --- a/src/scripts/verify-contract-script.ts +++ b/src/scripts/verify-contract-script.ts @@ -10,19 +10,19 @@ import { $, cd } from "zx"; import { MODULE_ROOT_PATH } from "../utils/constants"; import { Logger } from "winston"; import { getMainLogger } from "../utils/cli"; -import { ContractItemSchema } from "../evm/schemas/contract"; +import { ContractItem, ContractItemSchema } from "../evm/schemas/contract"; import { loadContractUpdateRegistry } from "../evm/schemas/contractUpdate"; const verifyContract = async ( - deploymentName: string, + deployedContract: ContractItem, chainFolder: ChainFolder, etherscanApiKey: string, verifierUrl: string, logger: Logger ) => { // Read deployment file, so that we can find path to artifact - const deployment = await readFromDeploymentFile(deploymentName, chainFolder); - const metadata = await readMetadata(deployment.factory); + const deployment = await readFromDeploymentFile(deployedContract.name, chainFolder); + const metadata = await readMetadata(deployment.factory, deployedContract.solcVersion); const compilationTarget = JSON.parse(metadata).settings.compilationTarget; const contractFile = Object.keys(compilationTarget)[0]; const contractPath = `${contractFile}:${compilationTarget[contractFile]}`; @@ -44,7 +44,7 @@ const verifyContract = async ( } logger.info( - `verifying ${deploymentName}'s deployment with ${deployment.factory} ${ + `verifying ${deployedContract.name}'s deployment with ${deployment.factory} ${ libraries ? `and libraries ${libraries}` : `` }` ); @@ -83,7 +83,7 @@ async function main() { // Only try to verify contractName if it matches the deploymentName try { await verifyContract( - parsed.data.name, + parsed.data, chainFolder, etherscanApiKey, verifierUrl, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 3f1d87f5..98d8ae35 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -53,3 +53,5 @@ export const EXTRA_BINDINGS_PATH = process.env.EXTRA_BINDINGS_PATH; // The path where we access external artifacts for already deployed contracts export const EXTRA_ARTIFACTS_PATH = process.env.EXTRA_ARTIFACTS_PATH; +// Path where we can load an existing contract registry from +export const EXTERNAL_CONTRACTS_PATH= process.env.EXTERNAL_CONTRACTS_PATH; diff --git a/src/utils/io.ts b/src/utils/io.ts index 2b641f2f..99912a6e 100644 --- a/src/utils/io.ts +++ b/src/utils/io.ts @@ -21,6 +21,7 @@ import { UPDATE_SPECS_PATH, EXTRA_BINDINGS_PATH, EXTRA_ARTIFACTS_PATH, + EXTERNAL_CONTRACTS_PATH, } from './constants'; import yargs from 'yargs/yargs'; import { hideBin } from 'yargs/helpers'; @@ -51,6 +52,7 @@ export type DeployedContractObject = { metadata?: string; name: string; libraries: LibraryMetadata[]; + solcVersion: string | undefined; }; // readYamlFile reads a yaml file and returns the parsed object. @@ -137,10 +139,10 @@ export function parseZodSchema( `parsing ${className} failed. ${zErr.issues .map((i) => i.path) .join(', ')}: ${zErr.message}\nconfig obj:\n${JSON.stringify( - config, - null, - 2 - )}` + config, + null, + 2 + )}` ); } else { throw e; @@ -179,38 +181,49 @@ export function toEnvVarName(e: string) { } /** Reads a foundry build file given */ -export async function readArtifactFile(artifactName: string) { +export async function readArtifactFile( + artifactName: string, + solcVersion: string | undefined +) { const basePaths = [ARTIFACTS_PATH]; if (EXTRA_ARTIFACTS_PATH) { basePaths.push(EXTRA_ARTIFACTS_PATH); } - - const paths = basePaths.map((basePath) => - path.join(basePath, `${artifactName}.sol`, `${artifactName}.json`) - ); + const paths = basePaths.map((basePath) => { + return path.join( + basePath, + `${artifactName}.sol`, + `${artifactName}${solcVersion ? `.${solcVersion}` : ''}.json` + ); + }); for (const path of paths) { try { return await fsAsync.readFile(path, 'utf8'); } catch (e) { - console.error(`error reading from file in extra file ${path}: \n`, e); + console.log(`no file found in ${path}: \n`, e); } } + + console.error(`error reading from file in extra file ${path}: \n`, ); return ''; } - /** Reads a deployment metadata rom a foundry build file. used to generate bytecode for deployment files*/ -export async function readMetadata(factoryName: string) { - const data = await readArtifactFile(factoryName); +export async function readMetadata( + factoryName: string, + solcVersion: string | undefined +) { + const data = await readArtifactFile(factoryName, solcVersion); return JSON.stringify(JSON.parse(data).metadata); } - -export async function readFactoryAbi(factoryName: string, contractFactories: Record) { +export async function readFactoryAbi( + factoryName: string, + contractFactories: Record +) { const contractFactoryConstructor = - contractFactories[`${factoryName}__factory`] + contractFactories[`${factoryName}__factory`]; - // const data = await readArtifactFile(factoryName); return contractFactoryConstructor.abi; } @@ -237,7 +250,10 @@ export async function writeDeployedContractToFile( ensureDir(deploymentFolder); // get metadata from contract from forge build output - const metadata = await readMetadata(deployedContract.factory); + const metadata = await readMetadata( + deployedContract.factory, + deployedContract.solcVersion + ); const outData = JSON.stringify({ ...deployedContract, metadata, @@ -381,6 +397,8 @@ export async function parseArgsFromCLI() { (argv1.EXTRA_BINDINGS_PATH as string) || EXTRA_BINDINGS_PATH; // Any directory of extra typechain bindings. const extraArtifactsPath = (argv1.EXTRA_ARTIFACTS_PATH as string) || EXTRA_ARTIFACTS_PATH; // Any directory of extra foundry artifacts + const externalContractsPath = + (argv1.EXTERNAL_CONTRACTS_PATH as string) || EXTERNAL_CONTRACTS_PATH; const chainParse = ChainConfigSchema.safeParse({ rpc: rpcUrl, @@ -410,6 +428,7 @@ export async function parseArgsFromCLI() { anvilPort, extraBindingsPath, extraArtifactsPath, + externalContractsPath, }; }