Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Experimental feature: Compilation with pragma analysis #3704

Merged
merged 45 commits into from
Feb 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f466348
WIP - Add method to profiler for getting imports for a single file so…
eggplantzzz Oct 1, 2020
56847e5
WIP - First draft of analyzeImports code
eggplantzzz Oct 1, 2020
57dae0f
WIP
eggplantzzz Oct 1, 2020
163dabf
Create analyzeImports utility for compile-solidity
eggplantzzz Oct 2, 2020
c2a735d
Update some method and file names
eggplantzzz Oct 2, 2020
1c63d5a
Remove a method and simplify a bit
eggplantzzz Oct 2, 2020
da25a45
Improve messaging for when no version of the Solidity compiler could …
eggplantzzz Oct 6, 2020
8f931bf
Make compilation slightly more efficient by collecting all sources ba…
eggplantzzz Oct 6, 2020
2a22fdf
Add a couple of tests for the pragma analysis feature
eggplantzzz Oct 14, 2020
3f51c2c
Increase test timeout
eggplantzzz Oct 14, 2020
9096540
Correct typo
eggplantzzz Oct 14, 2020
bafeae4
Increase test timeout globally for compile-solidity
eggplantzzz Oct 14, 2020
0c05bcf
Once again increase test timeouts
eggplantzzz Oct 15, 2020
51368f6
Add SPDX header to test contracts
cds-amal Oct 14, 2020
32cf3df
Mock request to get solc versions
eggplantzzz Oct 16, 2020
5dec52f
Use spread operator instead of Object.assign
eggplantzzz Oct 16, 2020
bddada2
Increase the timeout for tests once more
eggplantzzz Oct 16, 2020
7336ae1
Increase test timeout
eggplantzzz Oct 16, 2020
0310442
Remove some unnecessary awaits and a couple of comments
eggplantzzz Oct 27, 2020
296f890
Type profiler method input
eggplantzzz Oct 27, 2020
a57ca33
Get rid of call to normalizeOptions
eggplantzzz Oct 27, 2020
b63fcc6
Remove unnecessary setting of variable in test
eggplantzzz Oct 27, 2020
13e2d0b
Ensure solc version list is in descending order in compiler supplier
eggplantzzz Oct 27, 2020
5edad34
Add semver validation layer and a couple of tests
eggplantzzz Oct 28, 2020
599d24b
Change setting name to 'pragma'
eggplantzzz Oct 29, 2020
fc4a7be
Remove unnecessary await
eggplantzzz Oct 30, 2020
07f65e0
Add one more test
eggplantzzz Oct 30, 2020
828178a
Stub the request for getting the releases at a higher level in test
eggplantzzz Nov 10, 2020
7684d41
Merge in develop branch
eggplantzzz Dec 9, 2020
788b58d
Merge in analyzeImports branch
eggplantzzz Jan 13, 2021
cc9144b
Strip off .json and .abi.json from source file extensions when creati…
eggplantzzz Jan 12, 2021
2f06528
Add lodash.clonedeep to compile-solidity and use it to clone the comp…
eggplantzzz Jan 15, 2021
7101f5d
Omit rewriting the pragma expression of DeployedAddresses
eggplantzzz Jan 15, 2021
fa165a2
Correct require statement for lodash.clonedeep
eggplantzzz Jan 15, 2021
3ecd11e
Merge branch 'develop' of github.com:trufflesuite/truffle into pragma…
eggplantzzz Jan 19, 2021
13b6cc4
Merge branch 'develop' of github.com:trufflesuite/truffle into pragma…
eggplantzzz Jan 22, 2021
36558e5
Upate Deployed pragma expression
eggplantzzz Jan 29, 2021
0099e47
Filter out sol and json files in compileWithPragmaAnalysis
eggplantzzz Jan 29, 2021
0f6cd57
Remove unnecessary conditional and make slight edit to loop
eggplantzzz Feb 3, 2021
4856c59
Fix variable error and stop compilation early if there is nothing to …
eggplantzzz Feb 3, 2021
1f3ec9c
Remove unnecessary Object.assign
eggplantzzz Feb 3, 2021
96ab0fc
Allow for imports to not contain pragma expressions: throw when a use…
eggplantzzz Feb 3, 2021
1b1e87e
Merge in develop branch
eggplantzzz Feb 3, 2021
d4dfdff
Update expected message in test
eggplantzzz Feb 4, 2021
5fd2047
Merge in develop branch
eggplantzzz Feb 24, 2021
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
1 change: 1 addition & 0 deletions packages/compile-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@truffle/contract-schema": "^3.3.4",
"@types/fs-extra": "^8.1.0",
"@types/mocha": "^5.2.7",
"@truffle/config": "^1.2.32",
"typescript": "3.9.8"
},
"dependencies": {
Expand Down
21 changes: 21 additions & 0 deletions packages/compile-common/src/profiler/profiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const debug = debugModule("compile-common:profiler");
const findContracts = require("@truffle/contract-sources");
const expect = require("@truffle/expect");

import type TruffleConfig from "@truffle/config";
import { updated } from "./updated";
import { UnresolvedSource } from "./resolveAllSources";
import { requiredSources, RequiredSourcesOptions } from "./requiredSources";
Expand Down Expand Up @@ -89,4 +90,24 @@ export class Profiler {
allPaths
});
}

async requiredSourcesForSingleFile(options: TruffleConfig) {
expect.options(options, ["path", "base_path", "resolver"]);

const { resolver, path, base_path: basePath } = options;

const resolve = ({ filePath, importedFrom }: UnresolvedSource) =>
resolver.resolve(filePath, importedFrom);

const allPaths = convertToAbsolutePaths([path], basePath);
const updatedPaths = allPaths;

return await requiredSources({
resolve,
parseImports: this.config.parseImports,
shouldIncludePath: this.config.shouldIncludePath,
updatedPaths,
allPaths
});
}
}
142 changes: 142 additions & 0 deletions packages/compile-solidity/compileWithPragmaAnalysis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
const CompilerSupplier = require("./compilerSupplier");
const Config = require("@truffle/config");
const semver = require("semver");
const Profiler = require("./profiler");
const fse = require("fs-extra");
const { run } = require("./run");
const OS = require("os");
const cloneDeep = require("lodash.clonedeep");

const getSemverExpression = source => {
return source.match(/pragma solidity(.*);/)[1] ?
source.match(/pragma solidity(.*);/)[1].trim() :
undefined;
};

const getSemverExpressions = sources => {
return sources.map(source => getSemverExpression(source)).filter(expression => expression);
};

const validateSemverExpressions = semverExpressions => {
const { validRange } = semver;
for (const expression of semverExpressions) {
if (semver.validRange(expression) === null) {
const message = `Invalid semver expression (${expression}) found in` +
`one of your contract's imports.`;
throw new Error(message);
}
}
};

// takes an array of versions and an array of semver expressions
// returns a version of the compiler or undefined if none can be found
const findNewestSatisfyingVersion = ({ solcReleases, semverExpressions }) => {
// releases are ordered from newest to oldest
return solcReleases.find(version => {
return semverExpressions.every(expression =>
semver.satisfies(version, expression)
);
});
};

const throwCompilerVersionNotFound = ({ path, semverExpressions }) => {
const message =
`Could not find a single version of the Solidity compiler that ` +
`satisfies the following semver expressions obtained from your source ` +
`files' pragma statements: ${semverExpressions.join(" - ")}. ` +
`${OS.EOL}Please check the pragma statements for ${path} and its imports.`;
throw new Error(message);
};

const compileWithPragmaAnalysis = async ({ paths, options }) => {
const filteredPaths = paths.filter(
path => path.endsWith(".sol") || path.endsWith(".json")
);
if (filteredPaths.length === 0) {
return { compilations: [] };
}
const supplierOptions = {
events: options.events,
solcConfig: options.compilers.solc
};
const compilerSupplier = new CompilerSupplier(supplierOptions);
const { releases } = await compilerSupplier.getReleases();

// collect sources by the version of the Solidity compiler that they require
const versionsAndSources = {};
for (const path of filteredPaths) {
const source = (await options.resolver.resolve(path)).body;

const parserVersion = findNewestSatisfyingVersion({
solcReleases: releases,
semverExpressions: [getSemverExpression(source)]
});
if (!parserVersion) {
const m = `Could not find a pragma expression in ${path}. To use the ` +
`"pragma" compiler setting your contracts must contain a pragma ` +
`expression.`;
throw new Error(m);
}

// allSources is of the format { [filename]: string }
const { allSources } = await Profiler.requiredSourcesForSingleFile(
options.with({
path,
base_path: options.contracts_directory,
resolver: options.resolver,
compilers: {
solc: {
version: parserVersion
}
}
})
);

// get an array of all the semver expressions in the sources
const semverExpressions = await getSemverExpressions(
Object.values(allSources)
);

// this really just validates the expressions from the contracts' imports
// as it has already determined the parser version for each contract
validateSemverExpressions(semverExpressions);

const newestSatisfyingVersion = findNewestSatisfyingVersion({
solcReleases: releases,
semverExpressions
});
if (!newestSatisfyingVersion) {
throwCompilerVersionNotFound({
path,
semverExpressions
});
}

versionsAndSources[newestSatisfyingVersion] = {
...versionsAndSources[newestSatisfyingVersion],
...allSources
};
}

const compilations = [];
for (const compilerVersion in versionsAndSources) {
const compilationOptions = {
compilers: cloneDeep(options.compilers)
};
compilationOptions.compilers.solc.version = compilerVersion;

const config = Config.default().with(compilationOptions);
const compilation = await run(
versionsAndSources[compilerVersion],
config
);
if (compilation.contracts.length > 0) {
compilations.push(compilation);
}
}
return { compilations };
};

module.exports = {
compileWithPragmaAnalysis
};
4 changes: 3 additions & 1 deletion packages/compile-solidity/compilerSupplier/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ class CompilerSupplier {
.filter(build => build["prerelease"])
.map(build => build["longVersion"]);

const releases = Object.keys(list.releases);
const { rsort } = semver;
// ensure releases are listed in descending order
const releases = rsort(Object.keys(list.releases));

return {
prereleases: prereleases,
Expand Down
44 changes: 11 additions & 33 deletions packages/compile-solidity/index.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,13 @@
const debug = require("debug")("compile");
const path = require("path");
const expect = require("@truffle/expect");
const findContracts = require("@truffle/contract-sources");
const Config = require("@truffle/config");
const Profiler = require("./profiler");
const CompilerSupplier = require("./compilerSupplier");
const { run } = require("./run");

const normalizeOptions = options => {
if (options.logger === undefined) options.logger = console;

expect.options(options, ["contracts_directory", "compilers"]);
expect.options(options.compilers, ["solc"]);

options.compilers.solc.settings.evmVersion =
options.compilers.solc.settings.evmVersion ||
options.compilers.solc.evmVersion;
options.compilers.solc.settings.optimizer =
options.compilers.solc.settings.optimizer ||
options.compilers.solc.optimizer ||
{};

// Grandfather in old solc config
if (options.solc) {
options.compilers.solc.settings.evmVersion = options.solc.evmVersion;
options.compilers.solc.settings.optimizer = options.solc.optimizer;
}

// Certain situations result in `{}` as a value for compilationTargets
// Previous implementations treated any value lacking `.length` as equivalent
// to `[]`
// (This also happens when run() is called from sources(), so
// compilationTargets is not passed)
if (!options.compilationTargets || !options.compilationTargets.length) {
options.compilationTargets = [];
}

return options;
};
const { normalizeOptions } = require("./normalizeOptions");
const { compileWithPragmaAnalysis } = require("./compileWithPragmaAnalysis");
const expect = require("@truffle/expect");

const Compile = {
// this takes an object with keys being the name and values being source
Expand Down Expand Up @@ -77,6 +47,10 @@ const Compile = {

// this takes an array of paths and options
async sourcesWithDependencies({ paths, options }) {
if (options.compilers.solc.version === "pragma") {
return this.sourcesWithPragmaAnalysis({ paths, options });
}

options.logger = options.logger || console;
options.contracts_directory = options.contracts_directory || process.cwd();

Expand Down Expand Up @@ -139,6 +113,10 @@ const Compile = {
: { compilations: [] };
},

async sourcesWithPragmaAnalysis({ paths, options }) {
return compileWithPragmaAnalysis({ paths, options });
},

display(paths, options) {
if (options.quiet !== true) {
if (!Array.isArray(paths)) {
Expand Down
37 changes: 37 additions & 0 deletions packages/compile-solidity/normalizeOptions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const expect = require("@truffle/expect");

const normalizeOptions = options => {
if (options.logger === undefined) options.logger = console;

expect.options(options, ["contracts_directory", "compilers"]);
expect.options(options.compilers, ["solc"]);

options.compilers.solc.settings.evmVersion =
options.compilers.solc.settings.evmVersion ||
options.compilers.solc.evmVersion;
options.compilers.solc.settings.optimizer =
options.compilers.solc.settings.optimizer ||
options.compilers.solc.optimizer ||
{};

// Grandfather in old solc config
if (options.solc) {
options.compilers.solc.settings.evmVersion = options.solc.evmVersion;
options.compilers.solc.settings.optimizer = options.solc.optimizer;
}

// Certain situations result in `{}` as a value for compilationTargets
// Previous implementations treated any value lacking `.length` as equivalent
// to `[]`
// (This also happens when run() is called from sources(), so
// compilationTargets is not passed)
if (!options.compilationTargets || !options.compilationTargets.length) {
options.compilationTargets = [];
}

return options;
};

module.exports = {
normalizeOptions
};
1 change: 1 addition & 0 deletions packages/compile-solidity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@truffle/config": "^1.2.35",
"@truffle/contract-sources": "^0.1.11",
"@truffle/expect": "^0.0.15",
"lodash.clonedeep": "^4.5.0",
"debug": "^4.3.1",
"fs-extra": "^9.1.0",
"ora": "^3.4.0",
Expand Down
11 changes: 11 additions & 0 deletions packages/compile-solidity/profiler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,16 @@ module.exports = {

// invoke profiler
return await profiler.requiredSources(options);
},

requiredSourcesForSingleFile: async options => {
const parseImports = await loadParser(options);

const profiler = new Common.Profiler({
parseImports,
shouldIncludePath
});

return profiler.requiredSourcesForSingleFile(options);
}
};
4 changes: 2 additions & 2 deletions packages/compile-solidity/scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
set -o errexit

if [ "$CI" = true ]; then
mocha ./test/** ./test/**/* --timeout 10000 $@
mocha ./test/** ./test/**/* --timeout 50000 $@
else
rm -rf ./node_modules/.cache/truffle
mocha ./test/** ./test/**/* --invert --grep native --timeout 10000 $@
mocha ./test/** ./test/**/* --invert --grep native --timeout 50000 $@
fi
Loading