Skip to content

Commit

Permalink
Include main build info dir in dictionary
Browse files Browse the repository at this point in the history
  • Loading branch information
ericglau committed Aug 16, 2024
1 parent 10a2ea8 commit a6f1f80
Show file tree
Hide file tree
Showing 11 changed files with 75 additions and 78 deletions.
4 changes: 2 additions & 2 deletions packages/core/src/cli/cli.test.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Generated by [AVA](https://avajs.dev).
--contract <CONTRACT> The name or fully qualified name of the contract to validate. If not specified, all upgradeable contracts in the build info directory will be validated.␊
--reference <REFERENCE_CONTRACT> Can only be used when the --contract option is also provided. The name or fully qualified name of the reference contract to use for storage layout comparisons. If not specified, uses the @custom:oz-upgrades-from annotation if it is defined in the contract that is being validated.␊
--requireReference Can only be used when the --contract option is also provided. Not compatible with --unsafeSkipStorageCheck. If specified, requires either the --reference option to be provided or the contract to have a @custom:oz-upgrades-from annotation.␊
--referenceBuildInfoDirs Optional paths of additional build info directories from previous versions of the project to use for storage layout comparisons. When using this option, refer to one of these directories using prefix '<dirName>:' before the contract name or fully qualified name in the --reference option or @custom:oz-upgrades-from annotation, where <dirName> is the directory short name. Each directory short name must be unique.␊
--referenceBuildInfoDirs Optional paths of additional build info directories from previous versions of the project to use for storage layout comparisons. When using this option, refer to one of these directories using prefix '<dirName>:' before the contract name or fully qualified name in the --reference option or @custom:oz-upgrades-from annotation, where <dirName> is the directory short name. Each directory short name must be unique, including compared to the main build info directory.␊
--unsafeAllow "<VALIDATION_ERRORS>" Selectively disable one or more validation errors. Comma-separated list with one or more of the following: state-variable-assignment, state-variable-immutable, external-library-linking, struct-definition, enum-definition, constructor, delegatecall, selfdestruct, missing-public-upgradeto, internal-function-storage␊
--unsafeAllowRenames Configure storage layout check to allow variable renaming.␊
--unsafeSkipStorageCheck Skips checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort.␊
Expand All @@ -40,7 +40,7 @@ Generated by [AVA](https://avajs.dev).
--contract <CONTRACT> The name or fully qualified name of the contract to validate. If not specified, all upgradeable contracts in the build info directory will be validated.␊
--reference <REFERENCE_CONTRACT> Can only be used when the --contract option is also provided. The name or fully qualified name of the reference contract to use for storage layout comparisons. If not specified, uses the @custom:oz-upgrades-from annotation if it is defined in the contract that is being validated.␊
--requireReference Can only be used when the --contract option is also provided. Not compatible with --unsafeSkipStorageCheck. If specified, requires either the --reference option to be provided or the contract to have a @custom:oz-upgrades-from annotation.␊
--referenceBuildInfoDirs Optional paths of additional build info directories from previous versions of the project to use for storage layout comparisons. When using this option, refer to one of these directories using prefix '<dirName>:' before the contract name or fully qualified name in the --reference option or @custom:oz-upgrades-from annotation, where <dirName> is the directory short name. Each directory short name must be unique.␊
--referenceBuildInfoDirs Optional paths of additional build info directories from previous versions of the project to use for storage layout comparisons. When using this option, refer to one of these directories using prefix '<dirName>:' before the contract name or fully qualified name in the --reference option or @custom:oz-upgrades-from annotation, where <dirName> is the directory short name. Each directory short name must be unique, including compared to the main build info directory.␊
--unsafeAllow "<VALIDATION_ERRORS>" Selectively disable one or more validation errors. Comma-separated list with one or more of the following: state-variable-assignment, state-variable-immutable, external-library-linking, struct-definition, enum-definition, constructor, delegatecall, selfdestruct, missing-public-upgradeto, internal-function-storage␊
--unsafeAllowRenames Configure storage layout check to allow variable renaming.␊
--unsafeSkipStorageCheck Skips checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort.␊
Expand Down
Binary file modified packages/core/src/cli/cli.test.ts.snap
Binary file not shown.
2 changes: 1 addition & 1 deletion packages/core/src/cli/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Options:
--contract <CONTRACT> The name or fully qualified name of the contract to validate. If not specified, all upgradeable contracts in the build info directory will be validated.
--reference <REFERENCE_CONTRACT> Can only be used when the --contract option is also provided. The name or fully qualified name of the reference contract to use for storage layout comparisons. If not specified, uses the @custom:oz-upgrades-from annotation if it is defined in the contract that is being validated.
--requireReference Can only be used when the --contract option is also provided. Not compatible with --unsafeSkipStorageCheck. If specified, requires either the --reference option to be provided or the contract to have a @custom:oz-upgrades-from annotation.
--referenceBuildInfoDirs Optional paths of additional build info directories from previous versions of the project to use for storage layout comparisons. When using this option, refer to one of these directories using prefix '<dirName>:' before the contract name or fully qualified name in the --reference option or @custom:oz-upgrades-from annotation, where <dirName> is the directory short name. Each directory short name must be unique.
--referenceBuildInfoDirs Optional paths of additional build info directories from previous versions of the project to use for storage layout comparisons. When using this option, refer to one of these directories using prefix '<dirName>:' before the contract name or fully qualified name in the --reference option or @custom:oz-upgrades-from annotation, where <dirName> is the directory short name. Each directory short name must be unique, including compared to the main build info directory.
--unsafeAllow "<VALIDATION_ERRORS>" Selectively disable one or more validation errors. Comma-separated list with one or more of the following: ${errorKinds.join(
', ',
)}
Expand Down
40 changes: 20 additions & 20 deletions packages/core/src/cli/validate/build-info-file.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
import { rimraf } from 'rimraf';
import path from 'path';
import os from 'os';
import { BuildInfoFile, getBuildInfoFiles } from './build-info-file';
import { BuildInfoFile, getBuildInfoDirWithFiles } from './build-info-file';

test.beforeEach(async t => {
process.chdir(await fs.mkdtemp(path.join(os.tmpdir(), `upgrades-core-test-${t.title.replace(/\s/g, '-')}-`)));
Expand Down Expand Up @@ -296,9 +296,9 @@ test.serial('get build info files - default hardhat', async t => {
await fs.writeFile('artifacts/build-info/build-info.json', JSON.stringify(BUILD_INFO));
await fs.writeFile('artifacts/build-info/build-info-2.json', JSON.stringify(BUILD_INFO_2));

const buildInfoFiles = await getBuildInfoFiles();
const buildInfoFiles = await getBuildInfoDirWithFiles();

assertBuildInfoFiles(t, buildInfoFiles);
assertBuildInfoFiles(t, buildInfoFiles.files);
});

test.serial('get build info files - default foundry', async t => {
Expand All @@ -308,9 +308,9 @@ test.serial('get build info files - default foundry', async t => {
await fs.writeFile('out/build-info/build-info.json', JSON.stringify(BUILD_INFO));
await fs.writeFile('out/build-info/build-info-2.json', JSON.stringify(BUILD_INFO_2));

const buildInfoFiles = await getBuildInfoFiles();
const buildInfoFiles = await getBuildInfoDirWithFiles();

assertBuildInfoFiles(t, buildInfoFiles);
assertBuildInfoFiles(t, buildInfoFiles.files);
});

test.serial('get build info files - both hardhat and foundry dirs exist', async t => {
Expand All @@ -320,12 +320,12 @@ test.serial('get build info files - both hardhat and foundry dirs exist', async
await fs.mkdir('out/build-info', { recursive: true });
await fs.writeFile('out/build-info/build-info-2.json', JSON.stringify(BUILD_INFO_2));

const error = await t.throwsAsync(getBuildInfoFiles());
const error = await t.throwsAsync(getBuildInfoDirWithFiles());
t.true(error?.message.includes('Found both Hardhat and Foundry build info directories'));
});

test.serial('get build info files - no default dirs exist', async t => {
const error = await t.throwsAsync(getBuildInfoFiles());
const error = await t.throwsAsync(getBuildInfoDirWithFiles());
t.true(error?.message.includes('Could not find the default Hardhat or Foundry build info directory'));
});

Expand All @@ -338,9 +338,9 @@ test.serial('get build info files - override with custom relative path', async t
await fs.writeFile('custom/build-info/build-info.json', JSON.stringify(BUILD_INFO));
await fs.writeFile('custom/build-info/build-info-2.json', JSON.stringify(BUILD_INFO_2));

const buildInfoFiles = await getBuildInfoFiles('custom/build-info');
const buildInfoFiles = await getBuildInfoDirWithFiles('custom/build-info');

assertBuildInfoFiles(t, buildInfoFiles);
assertBuildInfoFiles(t, buildInfoFiles.files);
});

test.serial('get build info files - override with custom absolute path', async t => {
Expand All @@ -352,21 +352,21 @@ test.serial('get build info files - override with custom absolute path', async t
await fs.writeFile('custom/build-info/build-info.json', JSON.stringify(BUILD_INFO));
await fs.writeFile('custom/build-info/build-info-2.json', JSON.stringify(BUILD_INFO_2));

const buildInfoFiles = await getBuildInfoFiles(path.join(process.cwd(), 'custom/build-info'));
const buildInfoFiles = await getBuildInfoDirWithFiles(path.join(process.cwd(), 'custom/build-info'));

assertBuildInfoFiles(t, buildInfoFiles);
assertBuildInfoFiles(t, buildInfoFiles.files);
});

test.serial('invalid build info file', async t => {
await fs.mkdir('invalid-build-info', { recursive: true });

await fs.writeFile('invalid-build-info/invalid.json', JSON.stringify({ output: {} }));
const error = await t.throwsAsync(getBuildInfoFiles('invalid-build-info'));
const error = await t.throwsAsync(getBuildInfoDirWithFiles('invalid-build-info'));
t.true(error?.message.includes('must contain Solidity compiler input, output, and solcVersion'));
});

test.serial('dir does not exist', async t => {
const error = await t.throwsAsync(getBuildInfoFiles('invalid-dir'));
const error = await t.throwsAsync(getBuildInfoDirWithFiles('invalid-dir'));
t.true(error?.message.includes('does not exist'));
});

Expand All @@ -376,7 +376,7 @@ test.serial('no build info files', async t => {
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(`${dir}/notjson.txt`, 'abc');

const error = await t.throwsAsync(getBuildInfoFiles(dir));
const error = await t.throwsAsync(getBuildInfoDirWithFiles(dir));
t.true(error?.message.includes('does not contain any build info files'));
});

Expand All @@ -386,7 +386,7 @@ test.serial('no storage layout', async t => {
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(`${dir}/build-info.json`, JSON.stringify(BUILD_INFO_NO_LAYOUT));

const error = await t.throwsAsync(getBuildInfoFiles(dir));
const error = await t.throwsAsync(getBuildInfoDirWithFiles(dir));
t.true(error?.message.includes('does not contain storage layout'));
});

Expand All @@ -396,7 +396,7 @@ test.serial('individual output selections - no layout', async t => {
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(`${dir}/build-info.json`, JSON.stringify(BUILD_INFO_INDIVIDUAL_NO_LAYOUT));

const error = await t.throwsAsync(getBuildInfoFiles(dir));
const error = await t.throwsAsync(getBuildInfoDirWithFiles(dir));
t.true(error?.message.includes('does not contain storage layout'));
});

Expand All @@ -406,7 +406,7 @@ test.serial('individual output selections - has layout', async t => {
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(`${dir}/build-info.json`, JSON.stringify(BUILD_INFO_INDIVIDUAL_HAS_LAYOUT));

t.assert((await getBuildInfoFiles(dir)).length === 1);
t.assert(((await getBuildInfoDirWithFiles(dir)).files).length === 1);
});

test.serial('individual output selections - partial layout', async t => {
Expand All @@ -415,7 +415,7 @@ test.serial('individual output selections - partial layout', async t => {
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(`${dir}/build-info.json`, JSON.stringify(BUILD_INFO_PARTIAL_LAYOUT));

const error = await t.throwsAsync(getBuildInfoFiles(dir));
const error = await t.throwsAsync(getBuildInfoDirWithFiles(dir));
t.true(error?.message.includes('does not contain storage layout'));
});

Expand All @@ -425,7 +425,7 @@ test.serial('individual output selections - partial compile', async t => {
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(`${dir}/build-info.json`, JSON.stringify(BUILD_INFO_PARTIAL_COMPILE));

const error = await t.throwsAsync(getBuildInfoFiles(dir));
const error = await t.throwsAsync(getBuildInfoDirWithFiles(dir));
t.true(error?.message.includes('is not from a full compilation'));
});

Expand All @@ -435,7 +435,7 @@ test.serial('no output selection', async t => {
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(`${dir}/build-info.json`, JSON.stringify(BUILD_INFO_NO_OUTPUT_SELECTION));

const error = await t.throwsAsync(getBuildInfoFiles(dir));
const error = await t.throwsAsync(getBuildInfoDirWithFiles(dir));
t.true(error?.message.includes('is not from a full compilation'));
});

Expand Down
18 changes: 8 additions & 10 deletions packages/core/src/cli/validate/build-info-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ export interface BuildInfoFile {
* The Solidity compiler output JSON object.
*/
output: SolcOutput;
}

/**
* The short name of the directory containing the build info file.
*/
export interface BuildInfoDirWithFiles {
dirShortName: string;
files: BuildInfoFile[];
}

/**
Expand All @@ -62,16 +62,15 @@ export interface BuildInfoFile {
* @param buildInfoDir Build info directory, or undefined to use the default Hardhat or Foundry build-info dir.
* @returns The build info files with Solidity compiler input and output.
*/
export async function getBuildInfoFiles(buildInfoDir?: string) {
export async function getBuildInfoDirWithFiles(buildInfoDir?: string): Promise<BuildInfoDirWithFiles> {
const dir = await findDir(buildInfoDir);

const shortName = path.basename(dir);
const jsonFiles = await getJsonFiles(dir);
const dirName = path.basename(dir);

return await readBuildInfo(jsonFiles, dirName);
return { dirShortName: shortName, files: await readBuildInfo(jsonFiles) };
}

async function findDir(buildInfoDir: string | undefined) {
async function findDir(buildInfoDir?: string): Promise<string> {
if (buildInfoDir !== undefined && !(await hasJsonFiles(buildInfoDir))) {
throw new ValidateCommandError(
`The directory '${buildInfoDir}' does not exist or does not contain any build info files.`,
Expand Down Expand Up @@ -131,7 +130,7 @@ async function getJsonFiles(dir: string): Promise<string[]> {
return jsonFiles.map(file => path.join(dir, file));
}

async function readBuildInfo(buildInfoFilePaths: string[], dirName: string) {
async function readBuildInfo(buildInfoFilePaths: string[]) {
const buildInfoFiles: BuildInfoFile[] = [];

for (const buildInfoFilePath of buildInfoFilePaths) {
Expand All @@ -151,7 +150,6 @@ async function readBuildInfo(buildInfoFilePaths: string[], dirName: string) {
input: buildInfoJson.input,
output: buildInfoJson.output,
solcVersion: buildInfoJson.solcVersion,
dirShortName: dirName,
});
}
}
Expand Down
16 changes: 7 additions & 9 deletions packages/core/src/cli/validate/contract-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { getUpgradeabilityAssessment } from './upgradeability-assessment';
import { SourceContract } from './validations';
import { LayoutCompatibilityReport } from '../../storage/report';
import { indent } from '../../utils/indent';
import { ReferenceBuildInfoDictionary, SpecifiedContracts } from './validate-upgrade-safety';
import { BuildInfoDictionary, SpecifiedContracts } from './validate-upgrade-safety';

/**
* Report for an upgradeable contract.
Expand Down Expand Up @@ -58,32 +58,30 @@ export class UpgradeableContractReport implements Report {
}

/**
* Gets upgradeble contract reports for the upgradeable contracts in the given set of source contracts.
* Gets upgradeble contract reports for the upgradeable contracts in the set of source contracts at dictionary key ''.
* Reference contracts can come from source contracts at the corresponding dictionary key.
* Only contracts that are detected as upgradeable will be included in the reports.
* Reports include upgradeable contracts regardless of whether they pass or fail upgrade safety checks.
*
* @param sourceContracts The source contracts to check, which must include all contracts that are referenced by the given contracts. Can also include non-upgradeable contracts, which will be ignored.
* @param referenceDictionary Dictionary of reference build info directories and the contracts they contain.
* @param buildInfoDictionary Dictionary of build info directories and the source contracts they contain.
* @param opts The validation options.
* @param specifiedContracts If provided, only the specified contract (upgrading from its reference contract) will be reported.
* @returns The upgradeable contract reports.
*/
export function getContractReports(
sourceContracts: SourceContract[],
referenceDictionary: ReferenceBuildInfoDictionary,
buildInfoDictionary: BuildInfoDictionary,
opts: Required<ValidateUpgradeSafetyOptions>,
specifiedContracts?: SpecifiedContracts,
) {
const upgradeableContractReports: UpgradeableContractReport[] = [];

const contractsToReport: SourceContract[] =
specifiedContracts !== undefined ? [specifiedContracts.contract] : sourceContracts;
specifiedContracts !== undefined ? [specifiedContracts.contract] : buildInfoDictionary[''];

for (const sourceContract of contractsToReport) {
const upgradeabilityAssessment = getUpgradeabilityAssessment(
sourceContract,
sourceContracts,
referenceDictionary,
buildInfoDictionary,
specifiedContracts?.reference,
);
if (opts.requireReference && upgradeabilityAssessment.referenceContract === undefined) {
Expand Down
Loading

0 comments on commit a6f1f80

Please sign in to comment.