Skip to main content

Invoking the compiler

In being able to test a pointer dereference implementation, it is necessary to pair each tested pointer with associated EVM code that makes the pointer meaningful. To avoid solutions such as pre-compiling Solidity or handwriting EVM bytecode, the @ethdebug/pointers reference implementation's integration tests are written so that each test case is described in terms of Solidity code that the testing infrastructure compiles when executing the test.

The strategy taken by these tests is to use Solidity's constructor mechanism for allowing tests to specify variable assignment and mutation logic without needing to manage deployed contract instances. All these integration test cases thus observe pointers only via the trace of a contract creation transaction.

Integration logic

This testing infrastructure includes the compileCreateBytecode() function, which accepts input resembling Solidity's compiler input data as argument (i.e., the collection of source contents by path and additional contract target information) and asynchronously returns Data with the create (deployment) bytecode for the target contract.

/**
* Compile a collection of sources and return the create (deployment) bytecode
* for a particular target contract
*/
export async function compileCreateBytecode({
sources,
target
}: CompileOptions): Promise<Data> {
if (!solc) {
throw new Error("Unable to load solc");
}

const input = {
language: "Solidity",
sources,
settings: {
outputSelection: {
"*": {
"*": ["ir", "*"],
"": ["*"]
}
},
viaIR: true,
optimizer: {
enabled: true
}
}
};

const output = JSON.parse(
solc.compile(
JSON.stringify(input),
)
);

const { errors = [] } = output;
if (errors.length > 0) {
throw new Error(`Compilation error: ${JSON.stringify(errors, undefined, 2)}`);
}

const {
evm: {
bytecode: createBytecode
}
} = output.contracts[target.path][target.contractName];

return Data.fromHex(`0x${createBytecode.object}`);
}

The CompileOptions interface

/**
* Organizes the sources being compiled by their path identifier, as well
* as includes information about which contract's bytecode is desired
*/
export interface CompileOptions {
sources: {
[path: string]: {
content: string
}
};

target: {
path: string;
contractName: string;
};
}

"Syntactic sugar"-like helper function

To avoid test cases' needing to describe their associated code samples in terms of source content by path, test cases that require only a single source file can use the singleSourceCompilation() helper function that provides a more succinct method for generating CompileOptions objects:

/**
* "Syntactic sugar"-like helper function to initialize CompileOptions for
* compiling only a single source file.
*/
export function singleSourceCompilation(options: {
path: string;
contractName: string;
content: string;
}): CompileOptions {
const { path, contractName, content: contentWithoutHeader } = options;

const spdxLicenseIdentifier = "// SPDX-License-Identifier: UNLICENSED";
const pragma = "pragma solidity ^0.8.25;";
const header = `${spdxLicenseIdentifier}\n${pragma}\n`;

return {
sources: {
[path]: {
content: `${header}\n${contentWithoutHeader}\n`
}
},

target: {
path,
contractName
}
};
}