@truffle/compile-solidity
Version:
Compiler helper and artifact manager for Solidity files
419 lines • 17.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.run = void 0;
const shims_1 = require("./shims");
const debug_1 = __importDefault(require("debug"));
const debug = (0, debug_1.default)("compile:run");
const OS = require("os");
const semver_1 = __importDefault(require("semver"));
const compilerSupplier_1 = require("./compilerSupplier");
const Common = __importStar(require("@truffle/compile-common"));
// this function returns a Compilation - legacy/index.js and ./index.js
// both check to make sure rawSources exist before calling this method
// however, there is a check here that returns null if no sources exist
function run(rawSources, options, internalOptions = {}) {
return __awaiter(this, void 0, void 0, function* () {
if (Object.keys(rawSources).length === 0) {
return null;
}
const { language = "Solidity", // could also be "Yul"
noTransform = false, // turns off project root transform
solc // passing this skips compilerSupplier.load()
} = internalOptions;
// Ensure sources have operating system independent paths
// i.e., convert backslashes to forward slashes; things like C: are left intact.
// we also strip the project root (to avoid it appearing in metadata)
// and replace it with "project:/" (unless noTransform is set)
const { sources, targets, originalSourcePaths } = Common.Sources.collectSources(rawSources, options.compilationTargets, noTransform ? "" : options.working_directory, noTransform ? "" : "project:/");
// construct solc compiler input
const compilerInput = prepareCompilerInput({
sources,
targets,
language,
settings: options.compilers.solc.settings,
modelCheckerSettings: options.compilers.solc.modelCheckerSettings
});
// perform compilation
const { compilerOutput, solcVersion } = yield invokeCompiler({
compilerInput,
solc,
options
});
debug("compilerOutput: %O", compilerOutput);
// handle warnings as errors if options.strict
// log if not options.quiet
const { infos, warnings, errors } = detectErrors({
compilerOutput,
options,
solcVersion
});
if (infos.length > 0) {
options.events.emit("compile:infos", { infos });
}
if (warnings.length > 0) {
options.events.emit("compile:warnings", { warnings });
}
if (errors.length > 0) {
if (!options.quiet) {
options.logger.log("");
}
throw new Common.Errors.CompileError(errors);
}
const outputSources = processAllSources({
sources,
compilerOutput,
originalSourcePaths,
language
});
const sourceIndexes = outputSources
? outputSources.map(source => source.sourcePath)
: [];
return {
sourceIndexes,
contracts: processContracts({
sources,
compilerOutput,
solcVersion,
originalSourcePaths
}),
sources: outputSources,
compiler: {
name: "solc",
version: solcVersion
}
};
});
}
exports.run = run;
function orderABI({ abi, contractName, ast }) {
if (!abi) {
return []; //Yul doesn't return ABIs, but we require something
}
if (!ast || !ast.nodes) {
return abi;
}
// AST can have multiple contract definitions, make sure we have the
// one that matches our contract
const contractDefinition = ast.nodes.find(({ nodeType, name }) => nodeType === "ContractDefinition" && name === contractName);
if (!contractDefinition || !contractDefinition.nodes) {
return abi;
}
// Find all function definitions
const orderedFunctionNames = contractDefinition.nodes
.filter(({ nodeType }) => nodeType === "FunctionDefinition")
.map(({ name: functionName }) => functionName);
// Put function names in a hash with their order, lowest first, for speed.
const functionIndexes = orderedFunctionNames
.map((functionName, index) => ({ [functionName]: index }))
.reduce((a, b) => Object.assign({}, a, b), {});
// Construct new ABI with functions at the end in source order
return [
...abi.filter(({ name }) => functionIndexes[name] === undefined),
// followed by the functions in the source order
...abi
.filter(({ name }) => functionIndexes[name] !== undefined)
.sort(({ name: a }, { name: b }) => functionIndexes[a] - functionIndexes[b])
];
}
function prepareCompilerInput({ sources, targets, language, settings, modelCheckerSettings }) {
return {
language,
sources: prepareSources({ sources }),
settings: Object.assign(Object.assign({}, settings), {
// Specify compilation targets. Each target uses defaultSelectors,
// defaulting to single target `*` if targets are unspecified
outputSelection: prepareOutputSelection({ targets }) }),
modelCheckerSettings
};
}
/**
* Convert sources into solc compiler input format
* @param sources - { [sourcePath]: string }
* @return { [sourcePath]: { content: string } }
*/
function prepareSources({ sources }) {
return Object.entries(sources)
.map(([sourcePath, content]) => ({ [sourcePath]: { content } }))
.reduce((a, b) => Object.assign({}, a, b), {});
}
/**
* If targets are specified, specify output selectors for each individually.
* Otherwise, just use "*" selector
* @param targets - sourcePath[] | undefined
*/
function prepareOutputSelection({ targets = [] }) {
const defaultSelectors = {
"": ["legacyAST", "ast"],
"*": [
"abi",
"ast",
"metadata",
"evm.bytecode.object",
"evm.bytecode.linkReferences",
"evm.bytecode.sourceMap",
"evm.bytecode.generatedSources",
"evm.deployedBytecode.object",
"evm.deployedBytecode.linkReferences",
"evm.deployedBytecode.sourceMap",
"evm.deployedBytecode.immutableReferences",
"evm.deployedBytecode.generatedSources",
"userdoc",
"devdoc"
]
};
if (!targets.length) {
return {
"*": defaultSelectors
};
}
return targets
.map(target => ({ [target]: defaultSelectors }))
.reduce((a, b) => Object.assign({}, a, b), {});
}
/**
* Load solc and perform compilation
*/
function invokeCompiler({ compilerInput, options, solc }) {
return __awaiter(this, void 0, void 0, function* () {
const supplierOptions = {
parser: options.parser,
events: options.events,
solcConfig: options.compilers.solc
};
if (!solc) {
const supplier = new compilerSupplier_1.CompilerSupplier(supplierOptions);
({ solc } = yield supplier.load());
}
const solcVersion = solc.version();
// perform compilation
const inputString = JSON.stringify(compilerInput);
const outputString = solc.compile(inputString);
const compilerOutput = JSON.parse(outputString);
return {
compilerOutput,
solcVersion
};
});
}
function detectErrors({ compilerOutput, options, solcVersion }) {
const outputErrors = compilerOutput.errors || [];
const rawErrors = outputErrors.filter(({ severity }) => options.strict
? severity !== "info" //strict mode: warnings are errors too
: severity === "error" //nonstrict mode: only errors are errors
);
const rawWarnings = options.strict
? [] // in strict mode these get classified as errors, not warnings
: outputErrors.filter(({ severity, message }) => severity === "warning" &&
message !==
"Yul is still experimental. Please use the output with care." //filter out Yul warning
);
const rawInfos = outputErrors.filter(({ severity }) => severity === "info");
// extract messages
let errors = rawErrors
.map(({ formattedMessage }) => formattedMessage.replace(/: File import callback not supported/g, //remove this confusing message suffix
""))
.join();
const warnings = rawWarnings.map(({ formattedMessage }) => formattedMessage);
const infos = rawInfos.map(({ formattedMessage }) => formattedMessage);
if (errors.includes("requires different compiler version")) {
const contractSolcVer = errors.match(/pragma solidity[^;]*/gm)[0];
const configSolcVer = options.compilers.solc.version || semver_1.default.valid(solcVersion);
errors = errors.concat([
OS.EOL,
`Error: Truffle is currently using solc ${configSolcVer}, `,
`but one or more of your contracts specify "${contractSolcVer}".`,
OS.EOL,
`Please update your truffle config or pragma statement(s).`,
OS.EOL,
`(See https://trufflesuite.com/docs/truffle/reference/configuration#compiler-configuration `,
`for information on`,
OS.EOL,
`configuring Truffle to use a specific solc compiler version.)`
].join(""));
}
return { warnings, errors, infos };
}
/**
* aggregate source information based on compiled output;
* this can include sources that do not define any contracts
*/
function processAllSources({ sources, compilerOutput, originalSourcePaths, language }) {
if (!compilerOutput.sources) {
const entries = Object.entries(sources);
if (entries.length === 1) {
//special case for handling old Yul versions
const [sourcePath, contents] = entries[0];
return [
{
sourcePath: originalSourcePaths[sourcePath],
contents,
language
}
];
}
else {
return [];
}
}
let outputSources = [];
for (const [sourcePath, { id, ast, legacyAST }] of Object.entries(compilerOutput.sources)) {
outputSources[id] = {
sourcePath: originalSourcePaths[sourcePath],
contents: sources[sourcePath],
ast,
legacyAST,
language
};
}
//HACK: special case for handling a Yul compilation bug that causes
//the ID to be returned as 1 rather than 0
if (language === "Yul" &&
outputSources.length === 2 &&
outputSources[0] === undefined) {
return [outputSources[1]];
}
return outputSources;
}
/**
* Converts compiler-output contracts into @truffle/compile-solidity's return format
* Uses compiler contract output plus other information.
*/
function processContracts({ compilerOutput, sources, originalSourcePaths, solcVersion }) {
let { contracts } = compilerOutput;
if (!contracts)
return [];
//HACK: versions of Solidity prior to 0.4.20 are confused by our "project:/"
//prefix (or, more generally, by paths containing colons)
//and put contracts in a weird form as a result. we detect
//this case and repair it.
contracts = repairOldContracts(contracts);
return (Object.entries(contracts)
// map to [[{ source, contractName, contract }]]
.map(([sourcePath, sourceContracts]) => {
return Object.entries(sourceContracts).map(([contractName, contract]) => ({
contractName,
contract,
source: {
//some versions of Yul don't have sources in output
ast: ((compilerOutput.sources || {})[sourcePath] || {}).ast,
legacyAST: ((compilerOutput.sources || {})[sourcePath] || {})
.legacyAST,
contents: sources[sourcePath],
sourcePath
}
}));
})
// and flatten
.reduce((a, b) => [...a, ...b], [])
// All source will have a key, but only the compiled source will have
// the evm output.
.filter(({ contract: { evm } }) => Object.keys(evm).length > 0)
// convert to output format
.map(({ contractName, contract: { evm: { bytecode: { sourceMap, linkReferences, generatedSources, object: bytecode }, deployedBytecode: deployedBytecodeInfo //destructured below
}, abi, metadata, devdoc, userdoc }, source: { ast, legacyAST, sourcePath: transformedSourcePath, contents: source } }) => {
return {
contractName,
abi: orderABI({ abi, contractName, ast }),
metadata,
devdoc,
userdoc,
sourcePath: originalSourcePaths[transformedSourcePath],
source,
sourceMap,
deployedSourceMap: (deployedBytecodeInfo || {}).sourceMap,
ast,
legacyAST,
bytecode: (0, shims_1.zeroLinkReferences)({
bytes: bytecode,
linkReferences: (0, shims_1.formatLinkReferences)(linkReferences)
}),
deployedBytecode: (0, shims_1.zeroLinkReferences)({
bytes: (deployedBytecodeInfo || {}).object,
linkReferences: (0, shims_1.formatLinkReferences)((deployedBytecodeInfo || {}).linkReferences)
}),
immutableReferences: (deployedBytecodeInfo || {})
.immutableReferences,
//ideally immutable references would be part of the deployedBytecode object,
//but compatibility makes that impossible
generatedSources,
deployedGeneratedSources: (deployedBytecodeInfo || {})
.generatedSources,
compiler: {
name: "solc",
version: solcVersion
}
};
}));
}
function repairOldContracts(contracts) {
const contractNames = Object.values(contracts)
.map(source => Object.keys(source))
.flat();
if (contractNames.some(name => name.includes(":"))) {
//if any of the "contract names" contains a colon... hack invoked!
//(notionally we could always apply this hack but let's skip it most of the
//time please :P )
let repairedContracts = {};
for (const [sourcePrefix, sourceContracts] of Object.entries(contracts)) {
for (const [mixedPath, contract] of Object.entries(sourceContracts)) {
let sourcePath, contractName;
const lastColonIndex = mixedPath.lastIndexOf(":");
if (lastColonIndex === -1) {
//if there is none
sourcePath = sourcePrefix;
contractName = mixedPath;
}
else {
contractName = mixedPath.slice(lastColonIndex + 1); //take the part after the final colon
sourcePath = sourcePrefix + ":" + mixedPath.slice(0, lastColonIndex); //the part before the final colon
}
if (!repairedContracts[sourcePath]) {
repairedContracts[sourcePath] = {};
}
repairedContracts[sourcePath][contractName] = contract;
}
}
debug("repaired contracts: %O", repairedContracts);
return repairedContracts;
}
else {
//otherwise just return contracts as-is rather than processing
return contracts;
}
}
//# sourceMappingURL=run.js.map