@openzeppelin/upgrades-core 1.44.2 → 1.45.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/artifacts/@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json +2 -2
- package/artifacts/@openzeppelin/contracts-v5/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json +2 -2
- package/artifacts/@openzeppelin/contracts-v5/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json +2 -2
- package/artifacts/build-info-v5.json +22 -22
- package/dist/cli/validate/build-info-file.js +114 -20
- package/dist/cli/validate/build-info-file.js.map +1 -1
- package/dist/cli/validate/build-info-file.test.js +40 -1
- package/dist/cli/validate/build-info-file.test.js.map +1 -1
- package/dist/impl-address.d.ts +2 -1
- package/dist/impl-address.d.ts.map +1 -1
- package/dist/impl-address.js +8 -2
- package/dist/impl-address.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/validate/build-info-file.test.ts +56 -1
- package/src/cli/validate/build-info-file.ts +157 -23
- package/src/impl-address.ts +7 -2
|
@@ -129,7 +129,7 @@ async function exists(dir: string) {
|
|
|
129
129
|
|
|
130
130
|
async function getJsonFiles(dir: string): Promise<string[]> {
|
|
131
131
|
const files = await fs.readdir(dir);
|
|
132
|
-
const jsonFiles = files.filter(file => file.endsWith('.json'));
|
|
132
|
+
const jsonFiles = files.filter(file => file.endsWith('.json') && !file.endsWith('.output.json'));
|
|
133
133
|
return jsonFiles.map(file => path.join(dir, file));
|
|
134
134
|
}
|
|
135
135
|
|
|
@@ -137,44 +137,169 @@ async function readBuildInfo(buildInfoFilePaths: string[], dirShortName: string)
|
|
|
137
137
|
const buildInfoFiles: BuildInfoFile[] = [];
|
|
138
138
|
|
|
139
139
|
for (const buildInfoFilePath of buildInfoFilePaths) {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
140
|
+
const buildInfo = await loadBuildInfo(buildInfoFilePath);
|
|
141
|
+
|
|
142
|
+
checkOutputSelection(buildInfo.input, buildInfoFilePath);
|
|
143
|
+
|
|
144
|
+
buildInfoFiles.push({
|
|
145
|
+
input: buildInfo.input,
|
|
146
|
+
output: buildInfo.output,
|
|
147
|
+
solcVersion: buildInfo.solcVersion,
|
|
148
|
+
dirShortName,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return buildInfoFiles;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const FOUNDRY_BUILD_INFO_HELP = `\
|
|
155
|
+
Foundry build-info files must contain Solidity compiler input, output, and solcVersion.
|
|
156
|
+
Ensure foundry.toml has:
|
|
157
|
+
[profile.default]
|
|
158
|
+
build_info = true
|
|
159
|
+
extra_output = ["storageLayout"]
|
|
160
|
+
Then run: ${FOUNDRY_COMPILE_COMMAND}`;
|
|
161
|
+
|
|
162
|
+
const HH3_BUILD_INFO_HELP = `\
|
|
163
|
+
Hardhat 3's build-info uses a main .json file (input, solcVersion) and a separate .output.json (output).
|
|
164
|
+
Ensure you compile with Hardhat 3 so that artifacts/build-info/ contains both the main files and the .output.json files.
|
|
165
|
+
Run: ${HARDHAT_COMPILE_COMMAND}`;
|
|
166
|
+
|
|
167
|
+
const GENERIC_BUILD_INFO_HELP = `\
|
|
168
|
+
If using Foundry, ensure foundry.toml has build_info = true and extra_output = ["storageLayout"], then compile with '${FOUNDRY_COMPILE_COMMAND}'.
|
|
169
|
+
If using Hardhat, compile with '${HARDHAT_COMPILE_COMMAND}'.`;
|
|
170
|
+
|
|
171
|
+
async function loadBuildInfo(buildInfoFilePath: string): Promise<{
|
|
172
|
+
input: SolcInput;
|
|
173
|
+
output: SolcOutput;
|
|
174
|
+
solcVersion: string;
|
|
175
|
+
}> {
|
|
176
|
+
const buildInfoJson = await readJSON(buildInfoFilePath);
|
|
177
|
+
|
|
178
|
+
if (
|
|
179
|
+
buildInfoJson.input !== undefined &&
|
|
180
|
+
buildInfoJson.output !== undefined &&
|
|
181
|
+
buildInfoJson.solcVersion !== undefined
|
|
182
|
+
) {
|
|
183
|
+
return {
|
|
184
|
+
input: buildInfoJson.input,
|
|
185
|
+
output: buildInfoJson.output,
|
|
186
|
+
solcVersion: buildInfoJson.solcVersion,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const format = buildInfoJson._format as string | undefined;
|
|
191
|
+
|
|
192
|
+
// Foundry (ethers-rs-sol-build-info-1) normally has input, output, solcVersion at top level.
|
|
193
|
+
// If we reach here with Foundry format, a field is missing — suggest recompiling.
|
|
194
|
+
if (typeof format === 'string' && format.startsWith('ethers-rs-sol-build-info')) {
|
|
195
|
+
throw new ValidateCommandError(
|
|
196
|
+
`Build info file ${buildInfoFilePath} must contain Solidity compiler input, output, and solcVersion. Got format: ${format}.`,
|
|
197
|
+
() => FOUNDRY_BUILD_INFO_HELP,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (typeof format === 'string' && format.startsWith('hh3-sol-build-info')) {
|
|
202
|
+
if (buildInfoJson.input === undefined || buildInfoJson.solcVersion === undefined) {
|
|
146
203
|
throw new ValidateCommandError(
|
|
147
|
-
`Build info file ${buildInfoFilePath} must contain
|
|
204
|
+
`Build info file ${buildInfoFilePath} (Hardhat 3 format) must contain input and solcVersion. Got format: ${format}.`,
|
|
205
|
+
() => HH3_BUILD_INFO_HELP,
|
|
148
206
|
);
|
|
149
|
-
} else {
|
|
150
|
-
checkOutputSelection(buildInfoJson, buildInfoFilePath);
|
|
151
|
-
|
|
152
|
-
buildInfoFiles.push({
|
|
153
|
-
input: buildInfoJson.input,
|
|
154
|
-
output: buildInfoJson.output,
|
|
155
|
-
solcVersion: buildInfoJson.solcVersion,
|
|
156
|
-
dirShortName: dirShortName,
|
|
157
|
-
});
|
|
158
207
|
}
|
|
208
|
+
let inputData: SolcInput = buildInfoJson.input;
|
|
209
|
+
let outputData: SolcOutput | undefined = buildInfoJson.output;
|
|
210
|
+
|
|
211
|
+
if (outputData === undefined) {
|
|
212
|
+
const { dir, name } = path.parse(buildInfoFilePath);
|
|
213
|
+
const outputFilePath = path.join(dir, `${name}.output.json`);
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const outputJson = await readJSON(outputFilePath);
|
|
217
|
+
outputData = outputJson.output ?? outputJson;
|
|
218
|
+
} catch (error) {
|
|
219
|
+
throw new ValidateCommandError(
|
|
220
|
+
`Build info file ${buildInfoFilePath} does not contain output, and output file ${outputFilePath} could not be read.`,
|
|
221
|
+
() => HH3_BUILD_INFO_HELP,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (outputData === undefined) {
|
|
226
|
+
throw new ValidateCommandError(
|
|
227
|
+
`Build info file ${buildInfoFilePath} does not contain output, and output file ${outputFilePath} is missing Solidity compiler output.`,
|
|
228
|
+
() => HH3_BUILD_INFO_HELP,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const userSourceNameMap: Record<string, string> | undefined = buildInfoJson.userSourceNameMap;
|
|
234
|
+
if (userSourceNameMap !== undefined) {
|
|
235
|
+
const canonicalToUser: Record<string, string> = {};
|
|
236
|
+
for (const [userSource, canonicalSource] of Object.entries(userSourceNameMap)) {
|
|
237
|
+
canonicalToUser[canonicalSource] = userSource;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (inputData.sources !== undefined) {
|
|
241
|
+
inputData = {
|
|
242
|
+
...inputData,
|
|
243
|
+
sources: remapKeys(inputData.sources, canonicalToUser),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (outputData.sources !== undefined) {
|
|
248
|
+
outputData = {
|
|
249
|
+
...outputData,
|
|
250
|
+
sources: remapKeys(outputData.sources, canonicalToUser),
|
|
251
|
+
contracts:
|
|
252
|
+
outputData.contracts !== undefined
|
|
253
|
+
? remapKeys(outputData.contracts, canonicalToUser)
|
|
254
|
+
: outputData.contracts,
|
|
255
|
+
};
|
|
256
|
+
} else if (outputData.contracts !== undefined) {
|
|
257
|
+
outputData = {
|
|
258
|
+
...outputData,
|
|
259
|
+
contracts: remapKeys(outputData.contracts, canonicalToUser),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
input: inputData,
|
|
266
|
+
output: outputData,
|
|
267
|
+
solcVersion: buildInfoJson.solcVersion,
|
|
268
|
+
};
|
|
159
269
|
}
|
|
160
|
-
|
|
270
|
+
|
|
271
|
+
throw new ValidateCommandError(
|
|
272
|
+
`Build info from ${buildInfoFilePath} must include Solidity compiler input, output, and solcVersion. Got format: ${format ?? 'unknown'}.`,
|
|
273
|
+
() => GENERIC_BUILD_INFO_HELP,
|
|
274
|
+
);
|
|
161
275
|
}
|
|
162
276
|
|
|
163
277
|
/**
|
|
164
278
|
* Gives an error if there is empty output selection for any contract, or a contract does not have storage layout.
|
|
165
279
|
*/
|
|
166
|
-
function checkOutputSelection(
|
|
167
|
-
const
|
|
280
|
+
function checkOutputSelection(input: SolcInput, buildInfoFilePath: string) {
|
|
281
|
+
const outputSelection = input.settings?.outputSelection;
|
|
282
|
+
|
|
283
|
+
if (outputSelection === undefined) {
|
|
284
|
+
throw new ValidateCommandError(
|
|
285
|
+
`Build info file ${buildInfoFilePath} does not define compiler outputSelection.`,
|
|
286
|
+
() => STORAGE_LAYOUT_HELP,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
Object.keys(outputSelection).forEach(item => {
|
|
291
|
+
const selection = outputSelection[item] ?? {};
|
|
292
|
+
const unnamed = Array.isArray(selection['']) ? selection[''] : [];
|
|
293
|
+
const wildcard = Array.isArray(selection['*']) ? selection['*'] : [];
|
|
168
294
|
|
|
169
|
-
|
|
170
|
-
if ((o[item][''] === undefined || o[item][''].length === 0) && o[item]['*'].length === 0) {
|
|
295
|
+
if (unnamed.length === 0 && wildcard.length === 0) {
|
|
171
296
|
// No outputs at all for this contract e.g. if there were no changes since the last compile in Foundry.
|
|
172
297
|
// This is not supported for now, since it leads to AST nodes that reference node ids in other build-info files.
|
|
173
298
|
throw new ValidateCommandError(
|
|
174
299
|
`Build info file ${buildInfoFilePath} is not from a full compilation.`,
|
|
175
300
|
() => PARTIAL_COMPILE_HELP,
|
|
176
301
|
);
|
|
177
|
-
} else if (!
|
|
302
|
+
} else if (!wildcard.includes('storageLayout')) {
|
|
178
303
|
throw new ValidateCommandError(
|
|
179
304
|
`Build info file ${buildInfoFilePath} does not contain storage layout for all contracts.`,
|
|
180
305
|
() => STORAGE_LAYOUT_HELP,
|
|
@@ -186,3 +311,12 @@ function checkOutputSelection(buildInfoJson: any, buildInfoFilePath: string) {
|
|
|
186
311
|
async function readJSON(path: string) {
|
|
187
312
|
return JSON.parse(await fs.readFile(path, 'utf8'));
|
|
188
313
|
}
|
|
314
|
+
|
|
315
|
+
function remapKeys<T>(original: Record<string, T>, canonicalToUser: Record<string, string>): Record<string, T> {
|
|
316
|
+
const remapped: Record<string, T> = {};
|
|
317
|
+
for (const [key, value] of Object.entries(original)) {
|
|
318
|
+
const mappedKey = canonicalToUser[key] ?? key;
|
|
319
|
+
remapped[mappedKey] = value;
|
|
320
|
+
}
|
|
321
|
+
return remapped;
|
|
322
|
+
}
|
package/src/impl-address.ts
CHANGED
|
@@ -20,13 +20,18 @@ export class InvalidBeacon extends UpgradesError {}
|
|
|
20
20
|
* @throws {InvalidBeacon} If the implementation() function could not be called or does not return an address.
|
|
21
21
|
*/
|
|
22
22
|
export async function getImplementationAddressFromBeacon(
|
|
23
|
-
provider: EthereumProvider,
|
|
23
|
+
provider: EthereumProvider, // v2 may differ from v3
|
|
24
24
|
beaconAddress: string,
|
|
25
25
|
): Promise<string> {
|
|
26
26
|
const impl = await callOptionalSignature(provider, beaconAddress, 'implementation()');
|
|
27
|
+
|
|
27
28
|
let parsedImplAddress;
|
|
28
29
|
if (impl !== undefined) {
|
|
29
|
-
|
|
30
|
+
try {
|
|
31
|
+
parsedImplAddress = parseAddress(impl);
|
|
32
|
+
} catch (parseErr) {
|
|
33
|
+
throw new InvalidBeacon(`Contract at ${beaconAddress} doesn't look like a beacon`);
|
|
34
|
+
}
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
if (parsedImplAddress === undefined) {
|