Skip to content

Commit 9d3112a

Browse files
ericglauOmarTawfik
andauthored
Upgrade slang to 0.18.3 (#1102)
Co-authored-by: OmarTawfik <[email protected]>
1 parent b41336b commit 9d3112a

File tree

11 files changed

+100
-182
lines changed

11 files changed

+100
-182
lines changed

packages/core/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 1.41.0 (2024-11-25)
4+
5+
- Update Slang dependency to 0.18.3. ([#1102](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/1102))
6+
- Improves reliability of Hardhat compilation step for namespaced storage layout validations when using Solidity versions 0.8.27 and 0.8.28.
7+
38
## 1.40.0 (2024-10-10)
49

510
- Fix Hardhat compile error when overriding interface functions with public constant variables. ([#1091](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/1091))

packages/core/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@openzeppelin/upgrades-core",
3-
"version": "1.40.0",
3+
"version": "1.41.0",
44
"description": "",
55
"repository": "https://github.com/OpenZeppelin/openzeppelin-upgrades/tree/master/packages/core",
66
"license": "MIT",
@@ -61,7 +61,7 @@
6161
"typescript": "^5.0.0"
6262
},
6363
"dependencies": {
64-
"@nomicfoundation/slang": "^0.17.0",
64+
"@nomicfoundation/slang": "^0.18.3",
6565
"cbor": "^9.0.0",
6666
"chalk": "^4.1.0",
6767
"compare-versions": "^6.0.0",

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export {
6060
export { ValidateUpgradeSafetyOptions, validateUpgradeSafety, ProjectReport, ReferenceContractNotFound } from './cli';
6161

6262
export { getUpgradeInterfaceVersion } from './upgrade-interface-version';
63-
export { makeNamespacedInput } from './utils/make-namespaced';
63+
export { makeNamespacedInput, trySanitizeNatSpec } from './utils/make-namespaced';
6464
export { isNamespaceSupported } from './storage/namespace';
6565
export { inferProxyAdmin } from './infer-proxy-admin';
6666
export { assertUnreachable } from './utils/assert';

packages/core/src/utils/make-namespaced.test.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
TASK_COMPILE_SOLIDITY_RUN_SOLCJS,
77
} from 'hardhat/builtin-tasks/task-names';
88

9-
import { makeNamespacedInput } from './make-namespaced';
9+
import { makeNamespacedInput, trySanitizeNatSpec } from './make-namespaced';
1010
import { SolcBuild } from 'hardhat/types/builtin-tasks';
1111
import { SolcInput, SolcOutput } from '../solc-api';
1212
import { BuildInfo } from 'hardhat/types';
@@ -40,11 +40,10 @@ async function testMakeNamespaced(
4040
// Inefficient, but we want to test that we don't actually modify the original input object
4141
const origInput = JSON.parse(JSON.stringify(origBuildInfo.input));
4242

43-
const modifiedInput = makeNamespacedInput(
44-
origBuildInfo.input,
45-
origBuildInfo.output,
46-
keepAllNatSpec ? undefined : origBuildInfo.solcVersion,
47-
);
43+
let modifiedInput = makeNamespacedInput(origBuildInfo.input, origBuildInfo.output, origBuildInfo.solcVersion);
44+
if (!keepAllNatSpec) {
45+
modifiedInput = await trySanitizeNatSpec(modifiedInput, origBuildInfo.solcVersion);
46+
}
4847

4948
// Run hardhat compile on the modified input and make sure it has no errors
5049
const modifiedOutput = await hardhatCompile(modifiedInput, solcVersion);

packages/core/src/utils/make-namespaced.ts

Lines changed: 54 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ const OUTPUT_SELECTION = {
3434
*
3535
* @param input The original solc input.
3636
* @param output The original solc output.
37-
* @param solcVersion The version of the solc compiler that was originally used to compile the input.
37+
* @param _solcVersion The version of the solc compiler that was originally used to compile the input. This argument is no longer used and is kept for backwards compatibility.
3838
* @returns The modified solc input with storage layout that includes namespaced type information.
3939
*/
40-
export function makeNamespacedInput(input: SolcInput, output: SolcOutput, solcVersion?: string): SolcInput {
40+
export function makeNamespacedInput(input: SolcInput, output: SolcOutput, _solcVersion?: string): SolcInput {
4141
const modifiedSources: Record<string, { content?: string }> = {};
4242

4343
for (const [sourcePath] of Object.entries(input.sources)) {
@@ -163,74 +163,77 @@ export function makeNamespacedInput(input: SolcInput, output: SolcOutput, solcVe
163163
}
164164
}
165165

166-
const modifiedSource = tryRemoveNonStructNatSpec(getModifiedSource(orig, modifications), solcVersion);
167-
168-
modifiedSources[sourcePath] = { ...source, content: modifiedSource };
166+
modifiedSources[sourcePath] = { ...source, content: getModifiedSource(orig, modifications) };
169167
}
170168

171169
return { ...input, sources: modifiedSources, settings: { ...input.settings, outputSelection: OUTPUT_SELECTION } };
172170
}
173171

174172
/**
175-
* If we have the compiler version available and Slang is supported for the current platform and compiler version,
176-
* use Slang to parse and remove all NatSpec comments that do not precede a struct definition and return the modified content.
173+
* Attempts to remove all NatSpec comments that do not precede a struct definition from the input source contents.
174+
* Directly modifies the input source contents, and also returns the modified input.
175+
*
176+
* If the solc version is not supported by the parser, the original content is kept.
177+
*
178+
* @param solcInput Solc input.
179+
* @param solcVersion The version of the solc compiler that was originally used to compile the input.
180+
* @returns The modified solc input with NatSpec comments removed where they do not precede a struct definition.
181+
*/
182+
export async function trySanitizeNatSpec(solcInput: SolcInput, solcVersion: string): Promise<SolcInput> {
183+
for (const [sourcePath, source] of Object.entries(solcInput.sources)) {
184+
if (source.content !== undefined) {
185+
solcInput.sources[sourcePath].content = await tryRemoveNonStructNatSpec(source.content, solcVersion);
186+
}
187+
}
188+
return solcInput;
189+
}
190+
191+
/**
192+
* If Slang is supported for the current compiler version, use Slang to parse and remove all NatSpec comments
193+
* that do not precede a struct definition and return the modified content.
177194
*
178195
* Otherwise, return the original content.
179196
*/
180-
function tryRemoveNonStructNatSpec(origContent: string, solcVersion: string | undefined): string {
181-
const natSpecRemovals: Modification[] = [];
197+
async function tryRemoveNonStructNatSpec(origContent: string, solcVersion: string): Promise<string> {
198+
if (solcVersion === undefined) {
199+
return origContent;
200+
}
182201

183-
if (solcVersion !== undefined && tryRequire('@nomicfoundation/slang') && slangSupportsVersion(solcVersion)) {
184-
/* eslint-disable @typescript-eslint/no-var-requires */
185-
const { Language } = require('@nomicfoundation/slang/language');
186-
const { NonterminalKind, TerminalKind } = require('@nomicfoundation/slang/kinds');
187-
const { TerminalNode } = require('@nomicfoundation/slang/cst');
188-
const { isTrivia } = require('./slang/trivia');
189-
/* eslint-enable @typescript-eslint/no-var-requires */
202+
const { Parser } = await import('@nomicfoundation/slang/parser');
203+
if (!Parser.supportedVersions().includes(solcVersion)) {
204+
return origContent;
205+
}
206+
207+
const { TerminalKind, TerminalKindExtensions } = await import('@nomicfoundation/slang/cst');
190208

191-
const language = new Language(solcVersion);
192-
const parseOutput = language.parse(NonterminalKind.SourceUnit, origContent);
193-
const cursor = parseOutput.createTreeCursor();
209+
const parser = Parser.create(solcVersion);
210+
const parseOutput = parser.parse(Parser.rootKind(), origContent);
211+
const cursor = parseOutput.createTreeCursor();
212+
213+
const natSpecRemovals: Modification[] = [];
194214

215+
while (
216+
cursor.goToNextTerminalWithKinds([TerminalKind.MultiLineNatSpecComment, TerminalKind.SingleLineNatSpecComment])
217+
) {
218+
// Lookahead to determine if the next non-trivia node is the struct keyword.
219+
// If so, this NatSpec is part of the struct definition and should not be removed.
220+
const lookaheadCursor = cursor.clone();
195221
while (
196-
cursor.goToNextTerminalWithKinds([TerminalKind.MultiLineNatSpecComment, TerminalKind.SingleLineNatSpecComment])
222+
lookaheadCursor.goToNextTerminal() &&
223+
lookaheadCursor.node.isTerminalNode() &&
224+
TerminalKindExtensions.isTrivia(lookaheadCursor.node.kind)
197225
) {
198-
// Lookahead to determine if the next non-trivia node is the struct keyword.
199-
// If so, this NatSpec is part of the struct definition and should not be removed.
200-
const lookaheadCursor = cursor.clone();
201-
while (lookaheadCursor.goToNextTerminal() && isTrivia(lookaheadCursor.node())) {
202-
// skip over trivia nodes
203-
}
204-
if (lookaheadCursor.node().kind === TerminalKind.StructKeyword) {
205-
continue;
206-
}
226+
// skip over trivia nodes
227+
}
207228

208-
const triviaNode = cursor.node();
209-
assert(triviaNode instanceof TerminalNode);
210-
natSpecRemovals.push(makeDeleteRange(cursor.textRange.start.utf8, cursor.textRange.end.utf8));
229+
if (lookaheadCursor.node.kind === TerminalKind.StructKeyword) {
230+
continue;
211231
}
212-
return getModifiedSource(Buffer.from(origContent, 'utf8'), natSpecRemovals);
213-
} else {
214-
return origContent;
215-
}
216-
}
217232

218-
function tryRequire(id: string) {
219-
try {
220-
require(id);
221-
return true;
222-
} catch (e: any) {
223-
// do nothing
233+
natSpecRemovals.push(makeDeleteRange(cursor.textRange.start.utf8, cursor.textRange.end.utf8));
224234
}
225-
return false;
226-
}
227-
228-
function slangSupportsVersion(solcVersion: string): boolean {
229-
/* eslint-disable @typescript-eslint/no-var-requires */
230-
const { Language } = require('@nomicfoundation/slang/language');
231-
/* eslint-enable @typescript-eslint/no-var-requires */
232235

233-
return Language.supportedVersions().includes(solcVersion);
236+
return getModifiedSource(Buffer.from(origContent, 'utf8'), natSpecRemovals);
234237
}
235238

236239
interface Modification {

packages/core/src/utils/slang/trivia.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

packages/plugin-hardhat/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 3.6.0 (2024-11-25)
4+
5+
- Update Slang dependency to 0.18.3. ([#1102](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/1102))
6+
- Improves reliability of Hardhat compilation step for namespaced storage layout validations when using Solidity versions 0.8.27 and 0.8.28.
7+
38
## 3.5.0 (2024-10-10)
49

510
- Support ignoring Hardhat compile errors when extracting detailed namespaced storage layouts for validations. ([#1090](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/1090))

packages/plugin-hardhat/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@openzeppelin/hardhat-upgrades",
3-
"version": "3.5.0",
3+
"version": "3.6.0",
44
"description": "",
55
"repository": "https://github.com/OpenZeppelin/openzeppelin-upgrades/tree/master/packages/plugin-hardhat",
66
"license": "MIT",
@@ -38,7 +38,7 @@
3838
"@openzeppelin/defender-sdk-base-client": "^1.14.4",
3939
"@openzeppelin/defender-sdk-deploy-client": "^1.14.4",
4040
"@openzeppelin/defender-sdk-network-client": "^1.14.4",
41-
"@openzeppelin/upgrades-core": "^1.40.0",
41+
"@openzeppelin/upgrades-core": "^1.41.0",
4242
"chalk": "^4.1.0",
4343
"debug": "^4.1.1",
4444
"ethereumjs-util": "^7.1.5",

packages/plugin-hardhat/src/index.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ interface RunCompilerArgs {
6969
}
7070

7171
subtask(TASK_COMPILE_SOLIDITY, async (args: { force: boolean }, hre, runSuper) => {
72-
const { readValidations, ValidationsCacheOutdated, ValidationsCacheNotFound } = await import('./utils/validations');
72+
const { readValidations, ValidationsCacheOutdated, ValidationsCacheNotFound } = await import(
73+
'./utils/validations.js'
74+
);
7375

7476
try {
7577
await readValidations(hre);
@@ -85,21 +87,21 @@ subtask(TASK_COMPILE_SOLIDITY, async (args: { force: boolean }, hre, runSuper) =
8587
});
8688

8789
subtask(TASK_COMPILE_SOLIDITY_COMPILE, async (args: RunCompilerArgs, hre, runSuper) => {
88-
const { isNamespaceSupported, validate, solcInputOutputDecoder, makeNamespacedInput } = await import(
89-
'@openzeppelin/upgrades-core'
90-
);
91-
const { writeValidations } = await import('./utils/validations');
90+
const { isNamespaceSupported, validate, solcInputOutputDecoder, makeNamespacedInput, trySanitizeNatSpec } =
91+
await import('@openzeppelin/upgrades-core');
92+
const { writeValidations } = await import('./utils/validations.js');
9293

9394
// TODO: patch input
9495
const { output, solcBuild } = await runSuper();
9596

96-
const { isFullSolcOutput } = await import('./utils/is-full-solc-output');
97+
const { isFullSolcOutput } = await import('./utils/is-full-solc-output.js');
9798
if (isFullSolcOutput(output)) {
9899
const decodeSrc = solcInputOutputDecoder(args.input, output);
99100

100101
let namespacedOutput = undefined;
101102
if (isNamespaceSupported(args.solcVersion)) {
102-
const namespacedInput = makeNamespacedInput(args.input, output, args.solcVersion);
103+
let namespacedInput = makeNamespacedInput(args.input, output, args.solcVersion);
104+
namespacedInput = await trySanitizeNatSpec(namespacedInput, args.solcVersion);
103105
namespacedOutput = (await runSuper({ ...args, quiet: true, input: namespacedInput })).output;
104106

105107
const namespacedCompileErrors = getNamespacedCompileErrors(namespacedOutput);
@@ -210,7 +212,7 @@ extendConfig((config: HardhatConfig) => {
210212

211213
if (tryRequire('@nomicfoundation/hardhat-verify')) {
212214
subtask('verify:etherscan').setAction(async (args, hre, runSuper) => {
213-
const { verify } = await import('./verify-proxy');
215+
const { verify } = await import('./verify-proxy.js');
214216
return await verify(args, hre, runSuper);
215217
});
216218
}

tsconfig.base.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
"compilerOptions": {
33
"incremental": true,
44
"target": "es2020",
5-
"module": "commonjs",
5+
"module": "nodenext",
66
"strict": true,
7-
"moduleResolution": "node",
7+
"moduleResolution": "nodenext",
88
"esModuleInterop": true,
99
"sourceMap": true,
1010
"declaration": true,

0 commit comments

Comments
 (0)