solc
Version:
Solidity compiler
157 lines (156 loc) • 7.39 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const assert_1 = __importDefault(require("assert"));
const js_sha3_1 = require("js-sha3");
const helpers_1 = require("./common/helpers");
/**
* Generates a new-style library placeholder from a fully-qualified library name.
*
* Newer versions of the compiler use hashed names instead of just truncating the name
* before putting it in a placeholder.
*
* @param fullyQualifiedLibraryName Fully qualified library name.
*/
function libraryHashPlaceholder(fullyQualifiedLibraryName) {
return `$${(0, js_sha3_1.keccak256)(fullyQualifiedLibraryName).slice(0, 34)}$`;
}
/**
* Finds all placeholders corresponding to the specified library label and replaces them
* with a concrete address. Works with both hex-encoded and binary bytecode as long as
* the address is in the same format.
*
* @param bytecode Bytecode string.
*
* @param label Library label, either old- or new-style. Must exactly match the part between `__` markers in the
* placeholders. Will be padded with `_` characters if too short or truncated if too long.
*
* @param address Address to replace placeholders with. Must be the right length.
* It will **not** be padded with zeros if too short.
*/
function replacePlaceholder(bytecode, label, address) {
// truncate to 36 characters
const truncatedName = label.slice(0, 36);
const libLabel = `__${truncatedName.padEnd(36, '_')}__`;
while (bytecode.indexOf(libLabel) >= 0) {
bytecode = bytecode.replace(libLabel, address);
}
return bytecode;
}
/**
* Finds and all library placeholders in the provided bytecode and replaces them with actual addresses.
* Supports both old- and new-style placeholders (even both in the same file).
* See [Library Linking](https://docs.soliditylang.org/en/latest/using-the-compiler.html#library-linking)
* for a full explanation of the linking process.
*
* Example of a legacy placeholder: `__lib.sol:L_____________________________`
* Example of a new-style placeholder: `__$cb901161e812ceb78cfe30ca65050c4337$__`
*
* @param bytecode Hex-encoded bytecode string. All 40-byte substrings starting and ending with
* `__` will be interpreted as placeholders.
*
* @param libraries Mapping between fully qualified library names and the hex-encoded
* addresses they should be replaced with. Addresses shorter than 40 characters are automatically padded with zeros.
*
* @returns bytecode Hex-encoded bytecode string with placeholders replaced with addresses.
* Note that some placeholders may remain in the bytecode if `libraries` does not provide addresses for all of them.
*/
function linkBytecode(bytecode, libraries) {
(0, assert_1.default)(typeof bytecode === 'string');
(0, assert_1.default)(typeof libraries === 'object');
// NOTE: for backwards compatibility support old compiler which didn't use file names
const librariesComplete = {};
for (const [fullyQualifiedLibraryName, libraryObjectOrAddress] of Object.entries(libraries)) {
if ((0, helpers_1.isNil)(libraryObjectOrAddress)) {
throw new Error(`No address provided for library ${fullyQualifiedLibraryName}`);
}
// API compatible with the standard JSON i/o
// {"lib.sol": {"L": "0x..."}}
if ((0, helpers_1.isObject)(libraryObjectOrAddress)) {
for (const [unqualifiedLibraryName, address] of Object.entries(libraryObjectOrAddress)) {
librariesComplete[unqualifiedLibraryName] = address;
librariesComplete[`${fullyQualifiedLibraryName}:${unqualifiedLibraryName}`] = address;
}
continue;
}
// backwards compatible API for early solc-js versions
const parsed = fullyQualifiedLibraryName.match(/^(?<sourceUnitName>[^:]+):(?<unqualifiedLibraryName>.+)$/);
const libraryAddress = libraryObjectOrAddress;
if (!(0, helpers_1.isNil)(parsed)) {
const { unqualifiedLibraryName } = parsed.groups;
librariesComplete[unqualifiedLibraryName] = libraryAddress;
}
librariesComplete[fullyQualifiedLibraryName] = libraryAddress;
}
for (const libraryName in librariesComplete) {
let hexAddress = librariesComplete[libraryName];
if (!hexAddress.startsWith('0x') || hexAddress.length > 42) {
throw new Error(`Invalid address specified for ${libraryName}`);
}
// remove 0x prefix
hexAddress = hexAddress.slice(2).padStart(40, '0');
bytecode = replacePlaceholder(bytecode, libraryName, hexAddress);
bytecode = replacePlaceholder(bytecode, libraryHashPlaceholder(libraryName), hexAddress);
}
return bytecode;
}
/**
* Finds locations of all library address placeholders in the hex-encoded bytecode.
* Returns information in a format matching `evm.bytecode.linkReferences` output
* in Standard JSON.
*
* See [Library Linking](https://docs.soliditylang.org/en/latest/using-the-compiler.html#library-linking)
* for a full explanation of library placeholders and linking process.
*
* WARNING: The output matches `evm.bytecode.linkReferences` exactly only in
* case of old-style placeholders created from fully qualified library names
* of no more than 36 characters, and even then only if the name does not start
* or end with an underscore. This is different from
* `evm.bytecode.linkReferences`, which uses fully qualified library names.
* This is a limitation of the placeholder format - the fully qualified names
* are not preserved in the compiled bytecode and cannot be reconstructed
* without external information.
*
* @param bytecode Hex-encoded bytecode string.
*
* @returns linkReferences A mapping between library labels and their locations
* in the bytecode. In case of old-style placeholders the label is a fully
* qualified library name truncated to 36 characters. For new-style placeholders
* it's the first 34 characters of the hex-encoded hash of the fully qualified
* library name, with a leading and trailing $ character added. Note that the
* offsets and lengths refer to the *binary* (not hex-encoded) bytecode, just
* like in `evm.bytecode.linkReferences`.
*/
function findLinkReferences(bytecode) {
(0, assert_1.default)(typeof bytecode === 'string');
// find 40 bytes in the pattern of __...<36 digits>...__
// e.g. __Lib.sol:L_____________________________
const linkReferences = {};
let offset = 0;
while (true) {
const found = bytecode.match(/__(.{36})__/);
if (!found) {
break;
}
const start = found.index;
// trim trailing underscores
// NOTE: this has no way of knowing if the trailing underscore was part of the name
const libraryName = found[1].replace(/_+$/gm, '');
if (!linkReferences[libraryName]) {
linkReferences[libraryName] = [];
}
// offsets are in bytes in binary representation (and not hex)
linkReferences[libraryName].push({
start: (offset + start) / 2,
length: 20
});
offset += start + 20;
bytecode = bytecode.slice(start + 20);
}
return linkReferences;
}
module.exports = {
linkBytecode,
findLinkReferences
};
;