Skip to content
This repository has been archived by the owner on Jan 24, 2022. It is now read-only.

Warn when importing vanilla contracts package #1335

Merged
merged 2 commits into from
Dec 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion packages/cli/src/interface/ValidationLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import {
} from '@openzeppelin/upgrades';
import { ContractInterface } from '../models/files/NetworkFile';

const DOCS_HOME = 'https://docs.openzeppelin.com/sdk/2.5';
const DOCS_HOME = 'https://docs.openzeppelin.com/sdk/2.6';
const DANGEROUS_OPERATIONS_LINK = `${DOCS_HOME}/writing-contracts#potentially-unsafe-operations`;
const AVOID_INITIAL_VALUES_LINK = `${DOCS_HOME}/writing-contracts#avoid-initial-values-in-field-declarations`;
const INITIALIZERS_LINK = `${DOCS_HOME}/writing-contracts#initializers`;
const STORAGE_CHECKS_LINK = `${DOCS_HOME}/writing-contracts#modifying-your-contracts`;
const VANILLA_CONTRACTS_LINK = `${DOCS_HOME}/linking#linking-the-contracts-ethereum-package`;

export default class ValidationLogger {
public contract: Contract;
Expand All @@ -39,6 +40,7 @@ export default class ValidationLogger {
uninitializedBaseContracts,
storageDiff,
storageUncheckedVars,
importsVanillaContracts,
} = validations;

this.logHasConstructor(hasConstructor);
Expand All @@ -48,6 +50,19 @@ export default class ValidationLogger {
this.logUncheckedVars(storageUncheckedVars);
this.logUninitializedBaseContracts(uninitializedBaseContracts);
this.logStorageLayoutDiffs(storageDiff, getStorageLayout(this.contract, buildArtifacts));
this.logImportsVanillaContracts(importsVanillaContracts);
}

public logImportsVanillaContracts(vanillaContracts: string[] | null): void {
if (!vanillaContracts) return;
Loggy.noSpin.warn(
__filename,
'logImportsVanillaContracts',
`validation-imports-vanilla-contracts`,
`- Contract ${this.contractName} imports ${vanillaContracts.join(
frangio marked this conversation as resolved.
Show resolved Hide resolved
', ',
)} from @openzeppelin/contracts. Use @openzeppelin/contracts-ethereum-package instead. See ${VANILLA_CONTRACTS_LINK}.`,
);
}

public logHasSelfDestruct(hasSelfDestruct: boolean): void {
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/test/interface/ValidationLogger.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ describe('ValidationLogger', function() {
validationLogger().log({ hasInitialValuesInDeclarations: true });
this.logs.warns[0].should.match(/sets an initial value/);
});

it('logs when importing openzeppelin-contracts', async function() {
validationLogger().log({ importsVanillaContracts: ['Foo.sol', 'Bar.sol'] });
this.logs.warns[0].should.match(/@openzeppelin\/contracts/);
});
});

describe('storage', function() {
Expand Down
4 changes: 4 additions & 0 deletions packages/lib/contracts/mocks/Invalid.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pragma solidity ^0.5.0;

import "mock-dependency/contracts/Greeter.sol";

contract WithConstructor {
uint256 public value;

Expand Down Expand Up @@ -58,3 +60,5 @@ contract WithParentWithDelegateCall is WithDelegateCall {
return "WithParentWithDelegateCall";
}
}

contract WithVanillaBaseContract is Greeter { }
10 changes: 7 additions & 3 deletions packages/lib/src/utils/ContractAST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class MultipleNodesFoundError extends Error {
export default class ContractAST {
private artifacts: BuildArtifacts;
private contract: Contract;
private imports: Set<any>;
private imports: Set<string>;
private nodes: NodeMapping;
private types: TypeInfoMapping;
private nodesFilter: string[];
Expand Down Expand Up @@ -96,6 +96,10 @@ export default class ContractAST {
);
}

public getImports(): Set<string> {
return this.imports;
}

public getMethods(attributes?: string[]): any {
const baseContracts = this.getLinearizedBaseContracts();
return flatten(baseContracts.map(contract => contract.nodes))
Expand Down Expand Up @@ -140,7 +144,7 @@ export default class ContractAST {
} catch (err) {
if (err instanceof NodeNotFoundError) {
throw new Error(
`Cannot find source data for contract ${name} (base contract of ${this.contract.schema.contractName}). This often happens because either:\n- An incremental compilation step went wrong. Clear your build folder and recompile.\n- There is more than one contract named ${name} in your project (including dependencies). Make sure all contracts have a unique name, and that you are not importing dependencies with duplicated contract names (for example, openzeppelin-eth and openzeppelin-solidity).`,
`Cannot find source data for contract ${name} (base contract of ${this.contract.schema.contractName}). This often happens because either:\n- An incremental compilation step went wrong. Clear your build folder and recompile.\n- There is more than one contract named ${name} in your project (including dependencies). Make sure all contracts have a unique name, and that you are not importing dependencies with duplicated contract names (for example, @openzeppelin/contracts-ethereum-package and @openzeppelin/contracts).`,
);
} else {
throw err;
Expand Down Expand Up @@ -174,7 +178,7 @@ export default class ContractAST {
ast.nodes
.filter(node => node.nodeType === 'ImportDirective')
.map(node => node.absolutePath)
.forEach(importPath => {
.forEach((importPath: string) => {
if (this.imports.has(importPath)) return;
this.imports.add(importPath);
this.artifacts.getArtifactsFromSourcePath(importPath).forEach(importedArtifact => {
Expand Down
19 changes: 19 additions & 0 deletions packages/lib/src/validations/VanillaContracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Contract from '../artifacts/Contract';
import ContractAST from '../utils/ContractAST';

let VANILLA_CONTRACTS = '@openzeppelin/contracts/';

export function importsVanillaContracts(contract: Contract, buildArtifacts?: any): string[] | undefined {
const ast = new ContractAST(contract, buildArtifacts, { nodesFilter: [] });
const illegalImports = [...ast.getImports()]
.filter(i => i.startsWith(VANILLA_CONTRACTS))
.map(i => i.slice(VANILLA_CONTRACTS.length))
.map(i => i.replace(/^contracts\//, ''));
frangio marked this conversation as resolved.
Show resolved Hide resolved

return illegalImports.length > 0 ? illegalImports : undefined;
}

// Used for testing purposes;
export function setVanillaContractsPackageName(value: string) {
VANILLA_CONTRACTS = value;
}
10 changes: 9 additions & 1 deletion packages/lib/src/validations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { getUninitializedBaseContracts } from './Initializers';
import { getStorageLayout, getStructsOrEnums } from './Storage';
import { compareStorageLayouts, Operation } from './Layout';
import { hasInitialValuesInDeclarations } from './InitialValues';
import { importsVanillaContracts } from './VanillaContracts';
import Contract from '../artifacts/Contract.js';
import ContractAST, { StorageInfo } from '../utils/ContractAST';

Expand All @@ -24,6 +25,7 @@ export interface ValidationInfo {
uninitializedBaseContracts: any[];
storageUncheckedVars?: StorageInfo[];
storageDiff?: Operation[];
importsVanillaContracts?: string[];
}

export function validate(contract: Contract, existingContractInfo: any = {}, buildArtifacts?: any): any {
Expand All @@ -36,6 +38,7 @@ export function validate(contract: Contract, existingContractInfo: any = {}, bui
hasSelfDestruct: hasSelfDestruct(contract),
hasDelegateCall: hasDelegateCall(contract),
hasInitialValuesInDeclarations: hasInitialValuesInDeclarations(contract),
importsVanillaContracts: importsVanillaContracts(contract, buildArtifacts),
uninitializedBaseContracts,
...storageValidation,
};
Expand All @@ -54,6 +57,10 @@ export function newValidationErrors(validations: any, existingValidations: any =
),
storageUncheckedVars: difference(validations.storageUncheckedVars, existingValidations.storageUncheckedVars),
storageDiff: validations.storageDiff,
importsVanillaContracts: difference(
validations.importsVanillaContracts,
existingValidations.importsVanillaContracts,
),
};
}

Expand All @@ -64,7 +71,8 @@ export function validationPasses(validations: any): boolean {
!validations.hasSelfDestruct &&
!validations.hasDelegateCall &&
!validations.hasInitialValuesInDeclarations &&
isEmpty(validations.uninitializedBaseContracts)
isEmpty(validations.uninitializedBaseContracts) &&
isEmpty(validations.importsVanillaContracts)
);
}

Expand Down
10 changes: 10 additions & 0 deletions packages/lib/test/src/validations/Validations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ require('../../setup');

import { validate as validateContract } from '../../../src/validations';
import Contracts from '../../../src/artifacts/Contracts';
import { setVanillaContractsPackageName } from '../../../src/validations/VanillaContracts';

describe('Validations', function() {
it('should warn when adding a contract with a constructor', async function() {
Expand Down Expand Up @@ -37,6 +38,15 @@ describe('Validations', function() {
validate('WithParentWithInitialValuesInFieldsDeclarations').hasInitialValuesInDeclarations.should.be.true;
});

it('should warn on contract that extends vanilla openzeppelin contracts', async function() {
setVanillaContractsPackageName('mock-dependency/');
validate('WithVanillaBaseContract').importsVanillaContracts.should.deep.eq(['Greeter.sol']);
});

after(function() {
setVanillaContractsPackageName('@openzeppelin/contracts/');
});

describe.skip('uninitialized base contracts', function() {
it('should warn when adding a contract with uninitialized base contracts', async function() {
validate('WithBaseUninitialized').uninitializedBaseContracts.should.deep.eq([
Expand Down