@openzeppelin/upgrades-core 1.45.0 → 1.47.0
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/dist/cli/validate/validate-upgrade-safety.test.js +18 -0
- package/dist/cli/validate/validate-upgrade-safety.test.js.map +1 -1
- package/dist/storage/compare.d.ts.map +1 -1
- package/dist/storage/compare.js +3 -3
- package/dist/storage/compare.js.map +1 -1
- package/dist/storage/extract.d.ts +15 -3
- package/dist/storage/extract.d.ts.map +1 -1
- package/dist/storage/extract.js +68 -22
- package/dist/storage/extract.js.map +1 -1
- package/dist/storage/namespace.d.ts.map +1 -1
- package/dist/storage/namespace.js +26 -13
- package/dist/storage/namespace.js.map +1 -1
- package/dist/storage/report-rename-retype.test.js +30 -0
- package/dist/storage/report-rename-retype.test.js.map +1 -1
- package/dist/storage/report.js +9 -1
- package/dist/storage/report.js.map +1 -1
- package/dist/storage-erc7201-builtin.test.d.ts +2 -0
- package/dist/storage-erc7201-builtin.test.d.ts.map +1 -0
- package/dist/storage-erc7201-builtin.test.js +105 -0
- package/dist/storage-erc7201-builtin.test.js.map +1 -0
- package/dist/storage-namespaced-renamed-retyped.test.d.ts +2 -0
- package/dist/storage-namespaced-renamed-retyped.test.d.ts.map +1 -0
- package/dist/storage-namespaced-renamed-retyped.test.js +278 -0
- package/dist/storage-namespaced-renamed-retyped.test.js.map +1 -0
- package/dist/storage-struct-member-renamed-retyped.test.d.ts +2 -0
- package/dist/storage-struct-member-renamed-retyped.test.d.ts.map +1 -0
- package/dist/storage-struct-member-renamed-retyped.test.js +256 -0
- package/dist/storage-struct-member-renamed-retyped.test.js.map +1 -0
- package/dist/utils/annotations.d.ts +1 -0
- package/dist/utils/annotations.d.ts.map +1 -1
- package/dist/utils/annotations.js +4 -1
- package/dist/utils/annotations.js.map +1 -1
- package/dist/utils/erc7201-builtin.d.ts +24 -0
- package/dist/utils/erc7201-builtin.d.ts.map +1 -0
- package/dist/utils/erc7201-builtin.js +83 -0
- package/dist/utils/erc7201-builtin.js.map +1 -0
- package/dist/utils/erc7201.d.ts.map +1 -1
- package/dist/utils/erc7201.js +5 -3
- package/dist/utils/erc7201.js.map +1 -1
- package/dist/utils/erc7201.test.js +10 -0
- package/dist/utils/erc7201.test.js.map +1 -1
- package/dist/utils/integer-literals.d.ts +19 -6
- package/dist/utils/integer-literals.d.ts.map +1 -1
- package/dist/utils/integer-literals.js +28 -10
- package/dist/utils/integer-literals.js.map +1 -1
- package/dist/utils/integer-literals.test.js +12 -0
- package/dist/utils/integer-literals.test.js.map +1 -1
- package/dist/utils/struct-member-docs.d.ts +52 -0
- package/dist/utils/struct-member-docs.d.ts.map +1 -0
- package/dist/utils/struct-member-docs.js +264 -0
- package/dist/utils/struct-member-docs.js.map +1 -0
- package/dist/utils/struct-member-docs.test.d.ts +2 -0
- package/dist/utils/struct-member-docs.test.d.ts.map +1 -0
- package/dist/utils/struct-member-docs.test.js +320 -0
- package/dist/utils/struct-member-docs.test.js.map +1 -0
- package/dist/validate/data.d.ts +1 -1
- package/dist/validate/data.d.ts.map +1 -1
- package/dist/validate/data.js +1 -1
- package/dist/validate/run.d.ts +1 -1
- package/dist/validate/run.d.ts.map +1 -1
- package/dist/validate/run.js +22 -2
- package/dist/validate/run.js.map +1 -1
- package/package.json +5 -5
- package/src/cli/validate/validate-upgrade-safety.test.ts +35 -0
- package/src/cli/validate/validate-upgrade-safety.test.ts.md +10 -0
- package/src/cli/validate/validate-upgrade-safety.test.ts.snap +0 -0
- package/src/storage/compare.ts +3 -4
- package/src/storage/extract.ts +83 -30
- package/src/storage/namespace.ts +31 -14
- package/src/storage/report-rename-retype.test.ts +41 -0
- package/src/storage/report.ts +9 -1
- package/src/storage-erc7201-builtin.test.ts +127 -0
- package/src/storage-namespaced-renamed-retyped.test.ts +338 -0
- package/src/storage-namespaced-renamed-retyped.test.ts.md +183 -0
- package/src/storage-namespaced-renamed-retyped.test.ts.snap +0 -0
- package/src/storage-struct-member-renamed-retyped.test.ts +299 -0
- package/src/storage-struct-member-renamed-retyped.test.ts.md +260 -0
- package/src/storage-struct-member-renamed-retyped.test.ts.snap +0 -0
- package/src/utils/annotations.ts +4 -4
- package/src/utils/erc7201-builtin.ts +91 -0
- package/src/utils/erc7201.test.ts +12 -0
- package/src/utils/erc7201.ts +6 -4
- package/src/utils/integer-literals.test.ts +15 -1
- package/src/utils/integer-literals.ts +29 -10
- package/src/utils/struct-member-docs.test.ts +351 -0
- package/src/utils/struct-member-docs.ts +293 -0
- package/src/validate/data.ts +2 -2
- package/src/validate/run.ts +23 -1
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { StructDefinition } from 'solidity-ast';
|
|
2
|
+
import { Node } from 'solidity-ast/node';
|
|
3
|
+
import { findAll } from 'solidity-ast/utils';
|
|
4
|
+
|
|
5
|
+
import { SolcInput, SolcOutput } from '../solc-api';
|
|
6
|
+
import { SrcDecoder } from '../src-decoder';
|
|
7
|
+
import { UpgradesError } from '../error';
|
|
8
|
+
import debug from './debug';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolves the NatSpec documentation of the members of a struct, keyed by member name. Members without documentation
|
|
12
|
+
* are not included. Returns undefined if the documentation of the struct's members is not available.
|
|
13
|
+
*/
|
|
14
|
+
export type StructDocsResolver = (structDef: StructDefinition) => Map<string, string> | undefined;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Thrown when the source text of a struct does not have the shape implied by its AST, so that the documentation of its
|
|
18
|
+
* members cannot be located. This happens with syntax that the scanner does not know about.
|
|
19
|
+
*/
|
|
20
|
+
export class UnexpectedStructSourceError extends Error {
|
|
21
|
+
constructor(
|
|
22
|
+
message: string,
|
|
23
|
+
readonly position: number,
|
|
24
|
+
) {
|
|
25
|
+
super(`${message} at byte ${position}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Builds a resolver for the member documentation of the structs in a compilation.
|
|
31
|
+
*
|
|
32
|
+
* Solidity does not include the NatSpec of struct members in its AST, so it is read from the source text in the
|
|
33
|
+
* compiler input, between the source offsets of the members. The struct given to the resolver may come from the
|
|
34
|
+
* namespaced compilation of the same sources, whose source text and AST ids differ from the original, so it is first
|
|
35
|
+
* mapped to the original definition by source id and canonical name, which are the same in both compilations.
|
|
36
|
+
*
|
|
37
|
+
* @param solcInput The compiler input, or undefined if it is not available, in which case no documentation is resolved.
|
|
38
|
+
* @param solcOutput The compiler output of the original compilation.
|
|
39
|
+
* @param decodeSrc Source decoder for the original source code.
|
|
40
|
+
* @returns The resolver, which throws an UpgradesError if the member documentation of a struct that contains rename or
|
|
41
|
+
* retype annotations cannot be read, and an Error if the struct is not part of the original compilation.
|
|
42
|
+
*/
|
|
43
|
+
export function makeStructDocsResolver(
|
|
44
|
+
solcInput: SolcInput | undefined,
|
|
45
|
+
solcOutput: SolcOutput,
|
|
46
|
+
decodeSrc: SrcDecoder,
|
|
47
|
+
): StructDocsResolver {
|
|
48
|
+
const sourcePaths = new Map<number, string>();
|
|
49
|
+
const originalStructs = new Map<string, StructDefinition>();
|
|
50
|
+
for (const [sourcePath, { ast, id }] of Object.entries(solcOutput.sources)) {
|
|
51
|
+
sourcePaths.set(id, sourcePath);
|
|
52
|
+
for (const structDef of findAll('StructDefinition', ast)) {
|
|
53
|
+
originalStructs.set(getStructKey(structDef), structDef);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const sources = new Map<number, Buffer | undefined>();
|
|
58
|
+
const docs = new Map<string, Map<string, string> | undefined>();
|
|
59
|
+
|
|
60
|
+
function getSource(sourceId: number): Buffer | undefined {
|
|
61
|
+
if (!sources.has(sourceId)) {
|
|
62
|
+
const sourcePath = sourcePaths.get(sourceId);
|
|
63
|
+
const content = sourcePath === undefined ? undefined : solcInput?.sources[sourcePath]?.content;
|
|
64
|
+
if (content === undefined) {
|
|
65
|
+
debug(`Source content of ${sourcePath} is not available, annotations on its struct members are not read`);
|
|
66
|
+
}
|
|
67
|
+
sources.set(sourceId, content === undefined ? undefined : Buffer.from(content, 'utf8'));
|
|
68
|
+
}
|
|
69
|
+
return sources.get(sourceId);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getDocs(structDef: StructDefinition): Map<string, string> | undefined {
|
|
73
|
+
const source = getSource(getSourceId(structDef));
|
|
74
|
+
if (source === undefined) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
return getStructMemberDocumentation(structDef, source);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
if (!(e instanceof UnexpectedStructSourceError)) {
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
const { start, end } = getPositions(structDef);
|
|
84
|
+
if (/@custom:oz-(renamed|retyped)-from/.test(source.toString('utf8', start, end))) {
|
|
85
|
+
throw new UpgradesError(
|
|
86
|
+
`${decodeSrc(structDef)}: Could not read storage annotations on members of struct \`${structDef.canonicalName}\``,
|
|
87
|
+
() =>
|
|
88
|
+
'Please report this at https://zpl.in/upgrades/report and include the struct definition from your contract.',
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
debug(
|
|
92
|
+
`${decodeSrc(structDef)}: Could not read member documentation of struct ${structDef.canonicalName}: ${e.message}`,
|
|
93
|
+
);
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return structDef => {
|
|
99
|
+
const key = getStructKey(structDef);
|
|
100
|
+
const original = originalStructs.get(key);
|
|
101
|
+
if (original === undefined) {
|
|
102
|
+
throw new Error(`Broken invariant: struct ${structDef.canonicalName} not found in the original compilation`);
|
|
103
|
+
}
|
|
104
|
+
if (!docs.has(key)) {
|
|
105
|
+
docs.set(key, getDocs(original));
|
|
106
|
+
}
|
|
107
|
+
return docs.get(key);
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getStructKey(structDef: StructDefinition): string {
|
|
112
|
+
return `${getSourceId(structDef)}:${structDef.canonicalName}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getSourceId(node: Node): number {
|
|
116
|
+
return Number(node.src.split(':')[2]);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getPositions(node: Node) {
|
|
120
|
+
const [start, length] = node.src.split(':').map(Number);
|
|
121
|
+
return { start, end: start + length };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Byte values of the ASCII characters that delimit whitespace and comments. Any other byte, including the bytes of
|
|
125
|
+
// multi-byte UTF-8 sequences, is only accepted inside a comment, where it is kept as it is.
|
|
126
|
+
const TAB = 0x09;
|
|
127
|
+
const LF = 0x0a;
|
|
128
|
+
const CR = 0x0d;
|
|
129
|
+
const SPACE = 0x20;
|
|
130
|
+
const STAR = 0x2a; // *
|
|
131
|
+
const SLASH = 0x2f; // /
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Reads the NatSpec documentation of the members of a struct from the source text of its definition, located with the
|
|
135
|
+
* source offsets in the AST, which are byte offsets into the UTF-8 encoded source. The result is keyed by member
|
|
136
|
+
* name, and members without documentation are not included.
|
|
137
|
+
*
|
|
138
|
+
* A member is documented like a state variable preceded by the same comments would be: its documentation is the last
|
|
139
|
+
* `/**` block or run of consecutive `///` lines between the end of the previous member and the start of its
|
|
140
|
+
* declaration, with the comment markers removed as in `getDocumentationBefore`. Comments in the struct header, inside
|
|
141
|
+
* a member's declaration, between a member's name and its `;`, or after the last member document nothing.
|
|
142
|
+
*
|
|
143
|
+
* @throws UnexpectedStructSourceError if the source text of the struct does not have the shape implied by its AST.
|
|
144
|
+
*/
|
|
145
|
+
export function getStructMemberDocumentation(structDef: StructDefinition, source: Buffer): Map<string, string> {
|
|
146
|
+
const docs = new Map<string, string>();
|
|
147
|
+
const { start, end } = getPositions(structDef);
|
|
148
|
+
let pos = expectText(source, start, end, 'struct');
|
|
149
|
+
pos = expectText(source, skipTrivia(source, pos, end), end, structDef.name);
|
|
150
|
+
pos = expectText(source, skipTrivia(source, pos, end), end, '{');
|
|
151
|
+
for (const member of structDef.members) {
|
|
152
|
+
const memberPos = getPositions(member);
|
|
153
|
+
if (memberPos.start < pos || memberPos.end > end) {
|
|
154
|
+
throw new UnexpectedStructSourceError(`Member ${member.name} is outside the expected range`, memberPos.start);
|
|
155
|
+
}
|
|
156
|
+
const doc = getDocumentationBefore(source, pos, memberPos.start);
|
|
157
|
+
if (doc !== undefined) {
|
|
158
|
+
docs.set(member.name, doc);
|
|
159
|
+
}
|
|
160
|
+
// The source range of a member ends at its name and does not include the `;`
|
|
161
|
+
pos = expectText(source, skipTrivia(source, memberPos.end, end), end, ';');
|
|
162
|
+
}
|
|
163
|
+
pos = expectText(source, skipTrivia(source, pos, end), end, '}');
|
|
164
|
+
if (pos !== end) {
|
|
165
|
+
throw new UnexpectedStructSourceError('Expected the end of the struct', pos);
|
|
166
|
+
}
|
|
167
|
+
return docs;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Returns the documentation that solc attaches to a declaration starting at byte `end` of the source when the previous
|
|
172
|
+
* token ends at byte `start`, or undefined if there is none. The bytes in between may only be whitespace and comments.
|
|
173
|
+
*
|
|
174
|
+
* @throws UnexpectedStructSourceError if there is anything else between `start` and `end`.
|
|
175
|
+
*/
|
|
176
|
+
export function getDocumentationBefore(source: Buffer, start: number, end: number): string | undefined {
|
|
177
|
+
const { pos, doc } = scanTrivia(source, start, end);
|
|
178
|
+
if (pos !== end) {
|
|
179
|
+
throw new UnexpectedStructSourceError('Unexpected text', pos);
|
|
180
|
+
}
|
|
181
|
+
return doc;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function skipTrivia(source: Buffer, start: number, end: number): number {
|
|
185
|
+
return scanTrivia(source, start, end).pos;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function expectText(source: Buffer, pos: number, end: number, text: string): number {
|
|
189
|
+
const next = pos + text.length;
|
|
190
|
+
if (next > end || !source.subarray(pos, next).equals(Buffer.from(text))) {
|
|
191
|
+
throw new UnexpectedStructSourceError(`Expected \`${text}\``, pos);
|
|
192
|
+
}
|
|
193
|
+
return next;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* A comment that can be documentation, as its lines without the comment markers: a run of `///` lines, or a `/**`
|
|
198
|
+
* block split at its line breaks.
|
|
199
|
+
*/
|
|
200
|
+
interface DocComment {
|
|
201
|
+
block: boolean;
|
|
202
|
+
lines: string[];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Scans the whitespace and comments starting at byte `start` of the source, stopping at `end` or at the first byte that
|
|
207
|
+
* is neither, and returns that position together with the text of the documentation found: the last `/**` block or
|
|
208
|
+
* run of consecutive `///` lines, or undefined if there is none or its text is empty.
|
|
209
|
+
*
|
|
210
|
+
* Whitespace is space, tab, CR and LF. A line comment is documentation if it starts with exactly three slashes, and a
|
|
211
|
+
* block comment if its `/**` is followed by neither `*` nor `/`. A run of `///` lines continues through the next `///`
|
|
212
|
+
* line only when exactly one line break and otherwise only spaces and tabs separate them: a blank line or any other
|
|
213
|
+
* comment, even on the same line, ends the run.
|
|
214
|
+
*
|
|
215
|
+
* @throws UnexpectedStructSourceError at a block comment that is not closed before `end`.
|
|
216
|
+
*/
|
|
217
|
+
function scanTrivia(source: Buffer, start: number, end: number): { pos: number; doc: string | undefined } {
|
|
218
|
+
const byteAt = (i: number) => (i < end ? source[i] : undefined);
|
|
219
|
+
let doc: DocComment | undefined;
|
|
220
|
+
let runOpen = false;
|
|
221
|
+
let lineBreaks = 0;
|
|
222
|
+
let pos = start;
|
|
223
|
+
while (pos < end) {
|
|
224
|
+
const byte = source[pos];
|
|
225
|
+
if (byte === SPACE || byte === TAB) {
|
|
226
|
+
pos += 1;
|
|
227
|
+
} else if (byte === LF || byte === CR) {
|
|
228
|
+
lineBreaks += 1;
|
|
229
|
+
pos += byte === CR && byteAt(pos + 1) === LF ? 2 : 1;
|
|
230
|
+
} else if (byte === SLASH && byteAt(pos + 1) === SLASH) {
|
|
231
|
+
let lineEnd = pos + 2;
|
|
232
|
+
while (lineEnd < end && source[lineEnd] !== LF && source[lineEnd] !== CR) {
|
|
233
|
+
lineEnd += 1;
|
|
234
|
+
}
|
|
235
|
+
if (byteAt(pos + 2) === SLASH && byteAt(pos + 3) !== SLASH) {
|
|
236
|
+
const line = source.toString('utf8', pos + 3, lineEnd);
|
|
237
|
+
if (doc !== undefined && runOpen && lineBreaks === 1) {
|
|
238
|
+
doc.lines.push(line);
|
|
239
|
+
} else {
|
|
240
|
+
doc = { block: false, lines: [line] };
|
|
241
|
+
}
|
|
242
|
+
runOpen = true;
|
|
243
|
+
} else {
|
|
244
|
+
runOpen = false;
|
|
245
|
+
}
|
|
246
|
+
lineBreaks = 0;
|
|
247
|
+
pos = lineEnd;
|
|
248
|
+
} else if (byte === SLASH && byteAt(pos + 1) === STAR) {
|
|
249
|
+
const close = source.indexOf('*/', pos + 2);
|
|
250
|
+
if (close < 0 || close + 2 > end) {
|
|
251
|
+
throw new UnexpectedStructSourceError('Unterminated block comment', pos);
|
|
252
|
+
}
|
|
253
|
+
if (byteAt(pos + 2) === STAR && byteAt(pos + 3) !== STAR && byteAt(pos + 3) !== SLASH) {
|
|
254
|
+
doc = { block: true, lines: source.toString('utf8', pos + 3, close).split(/\r\n|\r|\n/) };
|
|
255
|
+
}
|
|
256
|
+
runOpen = false;
|
|
257
|
+
lineBreaks = 0;
|
|
258
|
+
pos = close + 2;
|
|
259
|
+
} else {
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return { pos, doc: doc === undefined ? undefined : getDocText(doc) };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Joins the lines of a documentation comment into the text that solc produces for the same comment on a state
|
|
268
|
+
* variable. Leading spaces and tabs are dropped from the first line and from every line of a block, then a single `*`
|
|
269
|
+
* at the start of a block line is dropped too, since it is a decoration. A block line that starts with `**` instead
|
|
270
|
+
* (counting the star that closes the comment for the last line) is kept whole and appended to the previous line
|
|
271
|
+
* without a line break. Lines that are left empty are dropped, and the rest are joined with `\n`.
|
|
272
|
+
*
|
|
273
|
+
* The text of a `///` run whose first line is blank starts with a line break in solc; here that line is dropped like
|
|
274
|
+
* any other blank line, which makes no difference to the annotations parsed from the text.
|
|
275
|
+
*/
|
|
276
|
+
function getDocText({ block, lines }: DocComment): string | undefined {
|
|
277
|
+
let text = '';
|
|
278
|
+
for (const [i, line] of lines.entries()) {
|
|
279
|
+
let content = i === 0 || block ? line.replace(/^[ \t]+/, '') : line;
|
|
280
|
+
let glued = false;
|
|
281
|
+
if (block && i > 0) {
|
|
282
|
+
if (content.startsWith('**') || (i === lines.length - 1 && content === '*')) {
|
|
283
|
+
glued = true;
|
|
284
|
+
} else if (content.startsWith('*')) {
|
|
285
|
+
content = content.slice(1);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (content.length > 0) {
|
|
289
|
+
text += text.length > 0 && !glued ? `\n${content}` : content;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return text.length > 0 ? text : undefined;
|
|
293
|
+
}
|
package/src/validate/data.ts
CHANGED
|
@@ -7,10 +7,10 @@ type ValidationDataV1 = ValidationRunData;
|
|
|
7
7
|
type ValidationDataV2 = ValidationRunData[];
|
|
8
8
|
|
|
9
9
|
const currentMajor = '3';
|
|
10
|
-
const currentVersion = '3.
|
|
10
|
+
const currentVersion = '3.5';
|
|
11
11
|
|
|
12
12
|
interface ValidationDataV3 {
|
|
13
|
-
version: '3' | '3.1' | '3.2' | '3.3' | '3.4';
|
|
13
|
+
version: '3' | '3.1' | '3.2' | '3.3' | '3.4' | '3.5';
|
|
14
14
|
log: ValidationRunData[];
|
|
15
15
|
}
|
|
16
16
|
|
package/src/validate/run.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { StorageLayout } from '../storage/layout';
|
|
|
21
21
|
import { getFullyQualifiedName } from '../utils/contract-name';
|
|
22
22
|
import { getAnnotationArgs as getSupportedAnnotationArgs, getDocumentation } from '../utils/annotations';
|
|
23
23
|
import { getStorageLocationAnnotation, isNamespaceSupported } from '../storage/namespace';
|
|
24
|
+
import { makeStructDocsResolver } from '../utils/struct-member-docs';
|
|
24
25
|
import { UpgradesError } from '../error';
|
|
25
26
|
import { assertUnreachable } from '../utils/assert';
|
|
26
27
|
import { logWarning } from '../utils/log';
|
|
@@ -177,7 +178,7 @@ export function skipCheck(error: ValidationError['kind'], node: Node): boolean {
|
|
|
177
178
|
* @param solcOutput Solc output to validate
|
|
178
179
|
* @param decodeSrc Source decoder for the original source code
|
|
179
180
|
* @param solcVersion The version of solc used to compile the contracts
|
|
180
|
-
* @param solcInput Solc input that the compiler was invoked with
|
|
181
|
+
* @param solcInput Solc input that the compiler was invoked with. If omitted, annotations on struct members are not read.
|
|
181
182
|
* @param namespacedOutput Namespaced solc output to extract storage layout information for namespaced types
|
|
182
183
|
* @returns A record of validation results for each fully qualified contract name
|
|
183
184
|
*/
|
|
@@ -194,6 +195,10 @@ export function validate(
|
|
|
194
195
|
const libraryIds: Record<string, number[]> = {};
|
|
195
196
|
|
|
196
197
|
const deref = astDereferencer(solcOutput);
|
|
198
|
+
if (namespacedOutput !== undefined) {
|
|
199
|
+
checkNamespacedSourceIds(solcOutput, namespacedOutput);
|
|
200
|
+
}
|
|
201
|
+
const structDocs = makeStructDocsResolver(solcInput, solcOutput, decodeSrc);
|
|
197
202
|
|
|
198
203
|
const delegateCallCache = initOpcodeCache();
|
|
199
204
|
const selfDestructCache = initOpcodeCache();
|
|
@@ -253,6 +258,7 @@ export function validate(
|
|
|
253
258
|
deref,
|
|
254
259
|
solcOutput.contracts[source][contractDef.name].storageLayout,
|
|
255
260
|
getNamespacedCompilationContext(source, contractDef, namespacedOutput),
|
|
261
|
+
structDocs,
|
|
256
262
|
);
|
|
257
263
|
|
|
258
264
|
validation[key].methods = [...findAll('FunctionDefinition', contractDef)]
|
|
@@ -335,6 +341,22 @@ function assertNotNamespace(node: StructDefinition, decodeSrc: SrcDecoder, stric
|
|
|
335
341
|
}
|
|
336
342
|
}
|
|
337
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Throws if a source of the solc output is missing from the namespaced solc output or has a different id there.
|
|
346
|
+
* Structs from the namespaced compilation are mapped back to their original definitions by source id.
|
|
347
|
+
*/
|
|
348
|
+
function checkNamespacedSourceIds(solcOutput: SolcOutput, namespacedOutput: SolcOutput) {
|
|
349
|
+
for (const [source, { id }] of Object.entries(solcOutput.sources)) {
|
|
350
|
+
const namespacedSource = namespacedOutput.sources[source];
|
|
351
|
+
if (namespacedSource === undefined) {
|
|
352
|
+
throw new Error(`Source ${source} not found in namespaced solc output`);
|
|
353
|
+
}
|
|
354
|
+
if (namespacedSource.id !== id) {
|
|
355
|
+
throw new Error(`Source ids differ between the solc output and the namespaced solc output for ${source}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
338
360
|
function getNamespacedCompilationContext(
|
|
339
361
|
source: string,
|
|
340
362
|
contractDef: ContractDefinition,
|