-
Notifications
You must be signed in to change notification settings - Fork 478
/
linker.ts
181 lines (153 loc) · 6.9 KB
/
linker.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import assert from 'assert';
import { keccak256 } from 'js-sha3';
import { isNil, isObject } from './common/helpers';
import { LibraryAddresses, LinkReferences } from './common/types';
/**
* 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 `$${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: string, libraries: LibraryAddresses): string {
assert(typeof bytecode === 'string');
assert(typeof libraries === 'object');
// NOTE: for backwards compatibility support old compiler which didn't use file names
const librariesComplete: { [fullyQualifiedLibraryName: string]: string } = {};
for (const [fullyQualifiedLibraryName, libraryObjectOrAddress] of Object.entries(libraries)) {
if (isNil(libraryObjectOrAddress)) {
throw new Error(`No address provided for library ${fullyQualifiedLibraryName}`);
}
// API compatible with the standard JSON i/o
// {"lib.sol": {"L": "0x..."}}
if (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 as string;
if (!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: string): LinkReferences {
assert(typeof bytecode === 'string');
// find 40 bytes in the pattern of __...<36 digits>...__
// e.g. __Lib.sol:L_____________________________
const linkReferences: 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;
}
export = {
linkBytecode,
findLinkReferences
};