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

Bug fix: Repair compiler output from Solidity <0.4.20 #4363

Merged
merged 2 commits into from
Oct 12, 2021
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
44 changes: 42 additions & 2 deletions packages/compile-solidity/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,9 +335,15 @@ function processContracts({
originalSourcePaths,
solcVersion
}) {
if (!compilerOutput.contracts) return [];
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(compilerOutput.contracts)
Object.entries(contracts)
// map to [[{ source, contractName, contract }]]
.map(([sourcePath, sourceContracts]) =>
Object.entries(sourceContracts).map(([contractName, contract]) => ({
Expand Down Expand Up @@ -418,6 +424,40 @@ function processContracts({
);
}

function repairOldContracts(contracts) {
const contractNames = [].concat(
...Object.values(contracts).map(source => Object.keys(source))
);
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;
}
}

function formatLinkReferences(linkReferences) {
if (!linkReferences) {
return [];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//SPDX-License-Identifier: MIT
//I've put a colon in the filename to be extra-sure
//that this is indeed testing what it's supposed to
//(that colons in file paths don't screw things up;
//"project:/" should suffice to trigger the issue but
//I want to be extra sure
pragma solidity >=0.4.9 <0.4.20;

contract SimpleContract {
uint x;

function SimpleContract() public {
x = 7;
}
}
77 changes: 77 additions & 0 deletions packages/compile-solidity/test/test_oldversions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const debug = require("debug")("compile:test:test_oldversions");
const fs = require("fs");
const path = require("path");
const { Compile } = require("@truffle/compile-solidity");
const CompilerSupplier = require("../compilerSupplier");
const assert = require("assert");
const { findOne } = require("./helpers");
const workingDirectory = "/home/fakename/truffleproject";
const compileOptions = {
working_directory: workingDirectory,
compilers: {
solc: {
version: "0.4.11",
settings: {
optimizer: {
enabled: false,
runs: 200
}
}
}
},
quiet: true
};
const supplierOptions = {
solcConfig: compileOptions.compilers.solc,
events: {
emit: () => {}
}
};

describe("Compile - solidity <0.4.20", function () {
this.timeout(5000); // solc
let source = null;
let solc = null; // gets loaded via supplier

before("get solc", async function () {
this.timeout(40000);

const supplier = new CompilerSupplier(supplierOptions);
({ solc } = await supplier.load());
});

describe("Output repair", function () {
before("get code", function () {
source = fs.readFileSync(
path.join(__dirname, "./sources/v0.4.11/FilenameWith:Colon.sol"),
"utf-8"
);
});

it("produces contract output correctly", async function () {
const sourcePath = `${workingDirectory}/contracts/FilenameWith:Colon.sol`;
const sources = { [sourcePath]: source };

const { compilations } = await Compile.sources({
sources,
options: compileOptions
});
//there should be one compilation
assert.equal(compilations.length, 1);
const compilation = compilations[0];
//it should have contracts
assert.ok(compilation.contracts);
//there should be one
assert.equal(compilation.contracts.length, 1);
const contract = compilation.contracts[0];
//it should have contract name & source set correctly;
//it should have various other properties set at all
assert.equal(contract.contractName, "SimpleContract");
assert.equal(contract.sourcePath, sourcePath);
assert.equal(contract.source, source);
assert.ok(contract.bytecode);
assert.ok(contract.abi);
assert.ok(contract.legacyAST);
});
});
});