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

Commit

Permalink
Expose @truffle/compile-solidity's bytecode shims
Browse files Browse the repository at this point in the history
  • Loading branch information
gnidan committed Aug 10, 2022
1 parent d288a03 commit 2f9715c
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 87 deletions.
4 changes: 3 additions & 1 deletion packages/compile-solidity/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { CompilerSupplier } = require("./compilerSupplier");
const { run } = require("./run");
const { normalizeOptions } = require("./normalizeOptions");
const { compileWithPragmaAnalysis } = require("./compileWithPragmaAnalysis");
const Shims = require("./shims");
const { reportSources } = require("./reportSources");
const { Compilations } = require("@truffle/compile-common");
const expect = require("@truffle/expect");
Expand Down Expand Up @@ -192,5 +193,6 @@ const Compile = {

module.exports = {
Compile,
CompilerSupplier
CompilerSupplier,
Shims
};
134 changes: 48 additions & 86 deletions packages/compile-solidity/src/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const OS = require("os");
const semver = require("semver");
const Common = require("@truffle/compile-common");
const { CompilerSupplier } = require("./compilerSupplier");
const { zeroLinkReferences, formatLinkReferences } = require("./shims");

// this function returns a Compilation - legacy/index.js and ./index.js
// both check to make sure rawSources exist before calling this method
Expand All @@ -15,23 +16,20 @@ async function run(rawSources, options, internalOptions = {}) {
const {
language = "Solidity", // could also be "Yul"
noTransform = false, // turns off project root transform
solc, // passing this skips compilerSupplier.load()
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:/"
);
const { sources, targets, originalSourcePaths } =
Common.Sources.collectSources(
rawSources,
options.compilationTargets,
noTransform ? "" : options.working_directory,
noTransform ? "" : "project:/"
);

// construct solc compiler input
const compilerInput = prepareCompilerInput({
Expand Down Expand Up @@ -254,27 +252,32 @@ function detectErrors({
}) {
outputErrors = outputErrors || [];
const rawErrors = outputErrors.filter(
({ severity }) => options.strict
? severity !== "info" //strict mode: warnings are errors too
: severity === "error" //nonstrict mode: only errors are errors
({ 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
);
: 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
""
let errors = rawErrors
.map(({ formattedMessage }) =>
formattedMessage.replace(
/: File import callback not supported/g, //remove this confusing message suffix
""
)
)
).join();
.join();
const warnings = rawWarnings.map(({ formattedMessage }) => formattedMessage);
const infos = rawInfos.map(({ formattedMessage }) => formattedMessage);

Expand Down Expand Up @@ -306,17 +309,24 @@ function detectErrors({
* aggregate source information based on compiled output;
* this can include sources that do not define any contracts
*/
function processAllSources({ sources, compilerOutput, originalSourcePaths, language }) {
function processAllSources({
sources,
compilerOutput,
originalSourcePaths,
language
}) {
if (!compilerOutput.sources) {
const entries = Object.entries(sources);
if (entries.length === 1) {
//special case for handling Yul
const [sourcePath, contents] = entries[0];
return [{
sourcePath: originalSourcePaths[sourcePath],
contents,
language
}]
return [
{
sourcePath: originalSourcePaths[sourcePath],
contents,
language
}
];
} else {
return [];
}
Expand Down Expand Up @@ -363,7 +373,8 @@ function processContracts({
source: {
//some versions of Yul don't have sources in output
ast: ((compilerOutput.sources || {})[sourcePath] || {}).ast,
legacyAST: ((compilerOutput.sources || {})[sourcePath] || {}).legacyAST,
legacyAST: ((compilerOutput.sources || {})[sourcePath] || {})
.legacyAST,
contents: sources[sourcePath],
sourcePath
}
Expand Down Expand Up @@ -419,13 +430,16 @@ function processContracts({
}),
deployedBytecode: zeroLinkReferences({
bytes: (deployedBytecodeInfo || {}).object,
linkReferences: formatLinkReferences((deployedBytecodeInfo || {}).linkReferences)
linkReferences: 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,
deployedGeneratedSources: (deployedBytecodeInfo || {})
.generatedSources,
compiler: {
name: "solc",
version: solcVersion
Expand All @@ -448,7 +462,8 @@ function repairOldContracts(contracts) {
for (const [mixedPath, contract] of Object.entries(sourceContracts)) {
let sourcePath, contractName;
const lastColonIndex = mixedPath.lastIndexOf(":");
if (lastColonIndex === -1) { //if there is none
if (lastColonIndex === -1) {
//if there is none
sourcePath = sourcePrefix;
contractName = mixedPath;
} else {
Expand All @@ -469,57 +484,4 @@ function repairOldContracts(contracts) {
}
}

function formatLinkReferences(linkReferences) {
if (!linkReferences) {
return [];
}

// convert to flat list
const libraryLinkReferences = Object.values(linkReferences)
.map(fileLinks =>
Object.entries(fileLinks).map(([name, links]) => ({
name,
links
}))
)
.reduce((a, b) => [...a, ...b], []);

// convert to { offsets, length, name } format
return libraryLinkReferences.map(({ name, links }) => ({
offsets: links.map(({ start }) => start),
length: links[0].length, // HACK just assume they're going to be the same
name
}));
}

// takes linkReferences in output format (not Solidity's format)
function zeroLinkReferences({ bytes, linkReferences }) {
if (bytes === undefined) {
return undefined;
}
// inline link references - start by flattening the offsets
const flattenedLinkReferences = linkReferences
// map each link ref to array of link refs with only one offset
.map(({ offsets, length, name }) =>
offsets.map(offset => ({ offset, length, name }))
)
// flatten
.reduce((a, b) => [...a, ...b], []);

// then overwite bytes with zeroes
bytes = flattenedLinkReferences.reduce((bytes, { offset, length }) => {
// length is a byte offset
const characterLength = length * 2;
const start = offset * 2;

const zeroes = "0".repeat(characterLength);

return `${bytes.substring(0, start)}${zeroes}${bytes.substring(
start + characterLength
)}`;
}, bytes);

return { bytes, linkReferences };
}

module.exports = { run };
97 changes: 97 additions & 0 deletions packages/compile-solidity/src/shims.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import type * as Common from "@truffle/compile-common";

/**
* Converts solc's link references format into the @truffle/compile-common
* link references format.
*/
export const formatLinkReferences = (
/**
* @dev type matches solc's Compiler output JSON
*/
linkReferences: {
[sourcePath: string]: {
[libraryName: string]: Array<{ start: 0; length: 20 }>;
};
}
): Common.LinkReference[] => {
if (!linkReferences) {
return [];
}

// convert to flat list
const libraryLinkReferences = Object.values(linkReferences)
.map(fileLinks =>
Object.entries(fileLinks).map(([name, links]) => ({
name,
links
}))
)
.reduce((a, b) => [...a, ...b], []);

// convert to { offsets, length, name } format
return libraryLinkReferences.map(({ name, links }) => ({
offsets: links.map(({ start }) => start),
length: links[0].length, // HACK just assume they're going to be the same
name
}));
};

/**
* This function converts contract bytecodes' bytes strings from solc's native
* format into the @truffle/compile-common internal format.
*
* solc produces bytecodes where the bytes corresponding to link references are
* not necessarily zero, but Truffle's format requires that these bytes MUST be
* zero.
*
* To be forgiving to the full range of possible input, this function accepts
* `undefined` as value for `bytes`, e.g., for `abstract contract`s.
*
* This function produces a spec-compliant Common.Bytecode object or undefined.
*/
export const zeroLinkReferences = (options: {
/**
* Link references in compile-common format (not Solidity's format), for
* which `zeroLinkReferences()` will convert the corresponding bytes to zero.
*/
linkReferences: Common.LinkReference[];

/**
* Hexadecimal string with NO prefix, straight from the Solidity output.
* For abstract contracts, this might be undefined
*/
bytes: string | undefined;
}): Common.Bytecode | undefined => {
const { linkReferences, bytes: inputBytes } = options;

if (inputBytes === undefined) {
return undefined;
}

// inline link references - start by flattening the offsets
const flattenedLinkReferences = linkReferences
// map each link ref to array of link refs with only one offset
.map(({ offsets, length, name }) =>
offsets.map(offset => ({ offset, length, name }))
)
// flatten
.reduce((a, b) => [...a, ...b], []);

// then overwite bytes with zeroes
const outputBytes = flattenedLinkReferences.reduce(
(bytes, { offset, length }) => {
// length is a byte offset
const characterLength = length * 2;
const start = offset * 2;

const zeroes = "0".repeat(characterLength);

return `${bytes.substring(0, start)}${zeroes}${bytes.substring(
start + characterLength
)}`;
},
inputBytes
);

return { linkReferences, bytes: outputBytes };
};

0 comments on commit 2f9715c

Please sign in to comment.