UNPKG

@truffle/compile-solidity

Version:
419 lines 17.6 kB
"use strict"; 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