@openzeppelin/upgrades-core 1.20.6 → 1.22.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/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.dbg.json +1 -1
- package/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.dbg.json +1 -1
- package/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.dbg.json +1 -1
- package/artifacts/build-info.json +117 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +4 -0
- package/dist/provider.js.map +1 -1
- package/dist/src-decoder.js +1 -1
- package/dist/src-decoder.js.map +1 -1
- package/dist/validate/query.d.ts.map +1 -1
- package/dist/validate/query.js +12 -8
- package/dist/validate/query.js.map +1 -1
- package/dist/validate/run.d.ts +5 -0
- package/dist/validate/run.d.ts.map +1 -1
- package/dist/validate/run.js +140 -26
- package/dist/validate/run.js.map +1 -1
- package/dist/validate/run.test.d.ts +2 -0
- package/dist/validate/run.test.d.ts.map +1 -0
- package/dist/validate/run.test.js +43 -0
- package/dist/validate/run.test.js.map +1 -0
- package/dist/validate-ignore-errors.test.d.ts +2 -0
- package/dist/validate-ignore-errors.test.d.ts.map +1 -0
- package/dist/validate-ignore-errors.test.js +123 -0
- package/dist/validate-ignore-errors.test.js.map +1 -0
- package/package.json +2 -2
- package/src/provider.ts +4 -0
- package/src/src-decoder.ts +1 -1
- package/src/validate/query.ts +15 -11
- package/src/validate/run.test.ts +49 -0
- package/src/validate/run.ts +220 -31
- package/src/validate-ignore-errors.test.ts +137 -0
package/src/validate/run.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Node } from 'solidity-ast/node';
|
|
2
|
-
import { isNodeType, findAll } from 'solidity-ast/utils';
|
|
3
|
-
import type { ContractDefinition } from 'solidity-ast';
|
|
2
|
+
import { isNodeType, findAll, ASTDereferencer } from 'solidity-ast/utils';
|
|
3
|
+
import type { ContractDefinition, FunctionDefinition } from 'solidity-ast';
|
|
4
4
|
|
|
5
5
|
import { SolcOutput, SolcBytecode } from '../solc-api';
|
|
6
6
|
import { SrcDecoder } from '../src-decoder';
|
|
@@ -68,6 +68,26 @@ interface ValidationErrorOpcode extends ValidationErrorBase {
|
|
|
68
68
|
kind: 'delegatecall' | 'selfdestruct';
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
interface OpcodePattern {
|
|
72
|
+
kind: 'delegatecall' | 'selfdestruct';
|
|
73
|
+
pattern: RegExp;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const OPCODES = {
|
|
77
|
+
delegatecall: {
|
|
78
|
+
kind: 'delegatecall',
|
|
79
|
+
pattern: /^t_function_baredelegatecall_/,
|
|
80
|
+
},
|
|
81
|
+
selfdestruct: {
|
|
82
|
+
kind: 'selfdestruct',
|
|
83
|
+
pattern: /^t_function_selfdestruct_/,
|
|
84
|
+
},
|
|
85
|
+
} as const;
|
|
86
|
+
|
|
87
|
+
export function isOpcodeError(error: ValidationErrorBase): error is ValidationErrorOpcode {
|
|
88
|
+
return error.kind === 'delegatecall' || error.kind === 'selfdestruct';
|
|
89
|
+
}
|
|
90
|
+
|
|
71
91
|
interface ValidationErrorUpgradeability extends ValidationErrorBase {
|
|
72
92
|
kind: 'missing-public-upgradeto';
|
|
73
93
|
}
|
|
@@ -84,34 +104,49 @@ function* execall(re: RegExp, text: string) {
|
|
|
84
104
|
}
|
|
85
105
|
}
|
|
86
106
|
|
|
87
|
-
function getAllowed(node: Node): string[] {
|
|
107
|
+
function getAllowed(node: Node, reachable: boolean): string[] {
|
|
88
108
|
if ('documentation' in node) {
|
|
109
|
+
const tag = `oz-upgrades-unsafe-allow${reachable ? '-reachable' : ''}`;
|
|
89
110
|
const doc = typeof node.documentation === 'string' ? node.documentation : node.documentation?.text ?? '';
|
|
111
|
+
return getAnnotationArgs(doc, tag);
|
|
112
|
+
} else {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
90
116
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Get args from the doc string matching the given tag
|
|
119
|
+
*/
|
|
120
|
+
export function getAnnotationArgs(doc: string, tag: string) {
|
|
121
|
+
const result: string[] = [];
|
|
122
|
+
for (const { groups } of execall(
|
|
123
|
+
/^\s*(?:@(?<title>\w+)(?::(?<tag>[a-z][a-z-]*))? )?(?<args>(?:(?!^\s*@\w+)[^])*)/m,
|
|
124
|
+
doc,
|
|
125
|
+
)) {
|
|
126
|
+
if (groups && groups.title === 'custom' && groups.tag === tag) {
|
|
127
|
+
const trimmedArgs = groups.args.trim();
|
|
128
|
+
if (trimmedArgs.length > 0) {
|
|
129
|
+
result.push(...trimmedArgs.split(/\s+/));
|
|
98
130
|
}
|
|
99
131
|
}
|
|
132
|
+
}
|
|
100
133
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
134
|
+
result.forEach(arg => {
|
|
135
|
+
if (!(errorKinds as readonly string[]).includes(arg)) {
|
|
136
|
+
throw new Error(`NatSpec: ${tag} argument not recognized: ${arg}`);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
106
139
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function skipCheckReachable(error: string, node: Node): boolean {
|
|
144
|
+
return getAllowed(node, true).includes(error);
|
|
111
145
|
}
|
|
112
146
|
|
|
113
147
|
function skipCheck(error: string, node: Node): boolean {
|
|
114
|
-
|
|
148
|
+
// skip both allow and allow-reachable errors in the lexical scope
|
|
149
|
+
return getAllowed(node, false).includes(error) || getAllowed(node, true).includes(error);
|
|
115
150
|
}
|
|
116
151
|
|
|
117
152
|
function getFullyQualifiedName(source: string, contractName: string) {
|
|
@@ -126,6 +161,9 @@ export function validate(solcOutput: SolcOutput, decodeSrc: SrcDecoder, solcVers
|
|
|
126
161
|
|
|
127
162
|
const deref = astDereferencer(solcOutput);
|
|
128
163
|
|
|
164
|
+
const delegateCallCache = initOpcodeCache();
|
|
165
|
+
const selfDestructCache = initOpcodeCache();
|
|
166
|
+
|
|
129
167
|
for (const source in solcOutput.contracts) {
|
|
130
168
|
for (const contractName in solcOutput.contracts[source]) {
|
|
131
169
|
const bytecode = solcOutput.contracts[source][contractName].evm.bytecode;
|
|
@@ -163,7 +201,7 @@ export function validate(solcOutput: SolcOutput, decodeSrc: SrcDecoder, solcVers
|
|
|
163
201
|
validation[key].src = decodeSrc(contractDef);
|
|
164
202
|
validation[key].errors = [
|
|
165
203
|
...getConstructorErrors(contractDef, decodeSrc),
|
|
166
|
-
...getOpcodeErrors(contractDef, decodeSrc),
|
|
204
|
+
...getOpcodeErrors(contractDef, deref, decodeSrc, delegateCallCache, selfDestructCache),
|
|
167
205
|
...getStateVariableErrors(contractDef, decodeSrc),
|
|
168
206
|
// TODO: add linked libraries support
|
|
169
207
|
// https://github.com/OpenZeppelin/openzeppelin-upgrades/issues/52
|
|
@@ -206,27 +244,178 @@ function* getConstructorErrors(contractDef: ContractDefinition, decodeSrc: SrcDe
|
|
|
206
244
|
}
|
|
207
245
|
}
|
|
208
246
|
|
|
209
|
-
function
|
|
210
|
-
|
|
247
|
+
function initOpcodeCache(): OpcodeCache {
|
|
248
|
+
return {
|
|
249
|
+
mainContractErrors: new Map(),
|
|
250
|
+
inheritedContractErrors: new Map(),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
interface OpcodeCache {
|
|
255
|
+
mainContractErrors: Map<number, ValidationErrorOpcode[]>;
|
|
256
|
+
inheritedContractErrors: Map<number, ValidationErrorOpcode[]>;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function* getOpcodeErrors(
|
|
260
|
+
contractDef: ContractDefinition,
|
|
261
|
+
deref: ASTDereferencer,
|
|
262
|
+
decodeSrc: SrcDecoder,
|
|
263
|
+
delegateCallCache: OpcodeCache,
|
|
264
|
+
selfDestructCache: OpcodeCache,
|
|
265
|
+
): Generator<ValidationErrorOpcode, void, undefined> {
|
|
266
|
+
yield* getContractOpcodeErrors(contractDef, deref, decodeSrc, OPCODES.delegatecall, 'main', delegateCallCache);
|
|
267
|
+
yield* getContractOpcodeErrors(contractDef, deref, decodeSrc, OPCODES.selfdestruct, 'main', selfDestructCache);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Whether this node is being visited as part of a main contract, or as an inherited contract or function
|
|
272
|
+
*/
|
|
273
|
+
type Scope = 'main' | 'inherited';
|
|
274
|
+
|
|
275
|
+
function* getContractOpcodeErrors(
|
|
276
|
+
contractDef: ContractDefinition,
|
|
277
|
+
deref: ASTDereferencer,
|
|
278
|
+
decodeSrc: SrcDecoder,
|
|
279
|
+
opcode: OpcodePattern,
|
|
280
|
+
scope: Scope,
|
|
281
|
+
cache: OpcodeCache,
|
|
282
|
+
): Generator<ValidationErrorOpcode, void, undefined> {
|
|
283
|
+
const cached = getCachedOpcodes(contractDef.id, scope, cache);
|
|
284
|
+
if (cached !== undefined) {
|
|
285
|
+
yield* cached;
|
|
286
|
+
} else {
|
|
287
|
+
const errors: ValidationErrorOpcode[] = [];
|
|
288
|
+
setCachedOpcodes(contractDef.id, scope, cache, errors);
|
|
289
|
+
errors.push(
|
|
290
|
+
...getFunctionOpcodeErrors(contractDef, deref, decodeSrc, opcode, scope, cache),
|
|
291
|
+
...getInheritedContractOpcodeErrors(contractDef, deref, decodeSrc, opcode, cache),
|
|
292
|
+
);
|
|
293
|
+
yield* errors;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function getCachedOpcodes(key: number, scope: string, cache: OpcodeCache) {
|
|
298
|
+
return scope === 'main' ? cache.mainContractErrors.get(key) : cache.inheritedContractErrors.get(key);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function* getFunctionOpcodeErrors(
|
|
302
|
+
contractOrFunctionDef: ContractDefinition | FunctionDefinition,
|
|
303
|
+
deref: ASTDereferencer,
|
|
304
|
+
decodeSrc: SrcDecoder,
|
|
305
|
+
opcode: OpcodePattern,
|
|
306
|
+
scope: Scope,
|
|
307
|
+
cache: OpcodeCache,
|
|
308
|
+
): Generator<ValidationErrorOpcode, void, undefined> {
|
|
309
|
+
const parentContractDef = getParentDefinition(deref, contractOrFunctionDef);
|
|
310
|
+
if (parentContractDef === undefined || !skipCheck(opcode.kind, parentContractDef)) {
|
|
311
|
+
yield* getDirectFunctionOpcodeErrors(contractOrFunctionDef, decodeSrc, opcode, scope);
|
|
312
|
+
}
|
|
313
|
+
if (parentContractDef === undefined || !skipCheckReachable(opcode.kind, parentContractDef)) {
|
|
314
|
+
yield* getReferencedFunctionOpcodeErrors(contractOrFunctionDef, deref, decodeSrc, opcode, scope, cache);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function* getDirectFunctionOpcodeErrors(
|
|
319
|
+
contractOrFunctionDef: ContractDefinition | FunctionDefinition,
|
|
320
|
+
decodeSrc: SrcDecoder,
|
|
321
|
+
opcode: OpcodePattern,
|
|
322
|
+
scope: Scope,
|
|
323
|
+
) {
|
|
324
|
+
for (const fnCall of findAll(
|
|
325
|
+
'FunctionCall',
|
|
326
|
+
contractOrFunctionDef,
|
|
327
|
+
node => skipCheck(opcode.kind, node) || (scope === 'inherited' && isInternalFunction(node)),
|
|
328
|
+
)) {
|
|
211
329
|
const fn = fnCall.expression;
|
|
212
|
-
if (fn.typeDescriptions.typeIdentifier?.match(
|
|
330
|
+
if (fn.typeDescriptions.typeIdentifier?.match(opcode.pattern)) {
|
|
213
331
|
yield {
|
|
214
|
-
kind:
|
|
332
|
+
kind: opcode.kind,
|
|
215
333
|
src: decodeSrc(fnCall),
|
|
216
334
|
};
|
|
217
335
|
}
|
|
218
336
|
}
|
|
219
|
-
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function* getReferencedFunctionOpcodeErrors(
|
|
340
|
+
contractOrFunctionDef: ContractDefinition | FunctionDefinition,
|
|
341
|
+
deref: ASTDereferencer,
|
|
342
|
+
decodeSrc: SrcDecoder,
|
|
343
|
+
opcode: OpcodePattern,
|
|
344
|
+
scope: Scope,
|
|
345
|
+
cache: OpcodeCache,
|
|
346
|
+
) {
|
|
347
|
+
for (const fnCall of findAll(
|
|
348
|
+
'FunctionCall',
|
|
349
|
+
contractOrFunctionDef,
|
|
350
|
+
node => skipCheckReachable(opcode.kind, node) || (scope === 'inherited' && isInternalFunction(node)),
|
|
351
|
+
)) {
|
|
220
352
|
const fn = fnCall.expression;
|
|
221
|
-
if (fn.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
353
|
+
if ('referencedDeclaration' in fn && fn.referencedDeclaration && fn.referencedDeclaration > 0) {
|
|
354
|
+
// non-positive references refer to built-in functions
|
|
355
|
+
const referencedNode = tryDerefFunction(deref, fn.referencedDeclaration);
|
|
356
|
+
if (referencedNode !== undefined) {
|
|
357
|
+
const cached = getCachedOpcodes(referencedNode.id, scope, cache);
|
|
358
|
+
if (cached !== undefined) {
|
|
359
|
+
yield* cached;
|
|
360
|
+
} else {
|
|
361
|
+
const errors: ValidationErrorOpcode[] = [];
|
|
362
|
+
setCachedOpcodes(referencedNode.id, scope, cache, errors);
|
|
363
|
+
errors.push(...getFunctionOpcodeErrors(referencedNode, deref, decodeSrc, opcode, scope, cache));
|
|
364
|
+
yield* errors;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function setCachedOpcodes(key: number, scope: string, cache: OpcodeCache, errors: ValidationErrorOpcode[]) {
|
|
372
|
+
if (scope === 'main') {
|
|
373
|
+
cache.mainContractErrors.set(key, errors);
|
|
374
|
+
} else {
|
|
375
|
+
cache.inheritedContractErrors.set(key, errors);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function tryDerefFunction(deref: ASTDereferencer, referencedDeclaration: number): FunctionDefinition | undefined {
|
|
380
|
+
try {
|
|
381
|
+
return deref(['FunctionDefinition'], referencedDeclaration);
|
|
382
|
+
} catch (e: any) {
|
|
383
|
+
if (!e.message.includes('No node with id')) {
|
|
384
|
+
throw e;
|
|
226
385
|
}
|
|
227
386
|
}
|
|
228
387
|
}
|
|
229
388
|
|
|
389
|
+
function* getInheritedContractOpcodeErrors(
|
|
390
|
+
contractDef: ContractDefinition,
|
|
391
|
+
deref: ASTDereferencer,
|
|
392
|
+
decodeSrc: SrcDecoder,
|
|
393
|
+
opcode: OpcodePattern,
|
|
394
|
+
cache: OpcodeCache,
|
|
395
|
+
) {
|
|
396
|
+
if (!skipCheckReachable(opcode.kind, contractDef)) {
|
|
397
|
+
for (const base of contractDef.baseContracts) {
|
|
398
|
+
const referencedContract = deref('ContractDefinition', base.baseName.referencedDeclaration);
|
|
399
|
+
yield* getContractOpcodeErrors(referencedContract, deref, decodeSrc, opcode, 'inherited', cache);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function getParentDefinition(deref: ASTDereferencer, contractOrFunctionDef: ContractDefinition | FunctionDefinition) {
|
|
405
|
+
const parentNode = deref(['ContractDefinition', 'SourceUnit'], contractOrFunctionDef.scope);
|
|
406
|
+
if (parentNode.nodeType === 'ContractDefinition') {
|
|
407
|
+
return parentNode;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function isInternalFunction(node: Node) {
|
|
412
|
+
return (
|
|
413
|
+
node.nodeType === 'FunctionDefinition' &&
|
|
414
|
+
node.kind !== 'constructor' && // do not consider constructors as internal, because they are always called by children contracts' constructors
|
|
415
|
+
(node.visibility === 'internal' || node.visibility === 'private')
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
230
419
|
function* getStateVariableErrors(
|
|
231
420
|
contractDef: ContractDefinition,
|
|
232
421
|
decodeSrc: SrcDecoder,
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import _test, { TestFn } from 'ava';
|
|
2
|
+
import { artifacts } from 'hardhat';
|
|
3
|
+
|
|
4
|
+
import { validate, getContractVersion, assertUpgradeSafe, ValidationOptions, RunValidation } from './validate';
|
|
5
|
+
import { solcInputOutputDecoder } from './src-decoder';
|
|
6
|
+
|
|
7
|
+
interface Context {
|
|
8
|
+
validation: RunValidation;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const test = _test as TestFn<Context>;
|
|
12
|
+
|
|
13
|
+
test.before(async t => {
|
|
14
|
+
const contracts = [
|
|
15
|
+
'contracts/test/ignore-errors/SafeContract.sol:SafeContract',
|
|
16
|
+
'contracts/test/ignore-errors/SafeContractWithFreeFunctionCall.sol:SafeContractWithFreeFunctionCall',
|
|
17
|
+
'contracts/test/ignore-errors/SafeContractWithLibraryCall.sol:SafeContractWithLibraryCall',
|
|
18
|
+
'contracts/test/ignore-errors/SafeContractWithLibraryImport.sol:SafeContractWithLibraryImport',
|
|
19
|
+
'contracts/test/ignore-errors/SafeContractWithLibraryUsingFor.sol:SafeContractWithLibraryUsingFor',
|
|
20
|
+
'contracts/test/ignore-errors/SafeContractWithTransitiveLibraryCall.sol:SafeContractWithTransitiveLibraryCall',
|
|
21
|
+
'contracts/test/ignore-errors/SafeContractWithParentCall.sol:SafeContractWithParentCall',
|
|
22
|
+
'contracts/test/ignore-errors/RiskyParentContract.sol:RiskyParentContract',
|
|
23
|
+
'contracts/test/ignore-errors/UnsafeContract.sol:UnsafeContract',
|
|
24
|
+
'contracts/test/ignore-errors/UnsafeContractWithFreeFunctionCall.sol:UnsafeContractWithFreeFunctionCall',
|
|
25
|
+
'contracts/test/ignore-errors/UnsafeContractWithLibraryCall.sol:UnsafeContractWithLibraryCall',
|
|
26
|
+
'contracts/test/ignore-errors/UnsafeContractWithLibraryUsingFor.sol:UnsafeContractWithLibraryUsingFor',
|
|
27
|
+
'contracts/test/ignore-errors/UnsafeContractWithTransitiveLibraryCall.sol:UnsafeContractWithTransitiveLibraryCall',
|
|
28
|
+
'contracts/test/ignore-errors/UnsafeContractWithParentCall.sol:UnsafeContractWithParentCall',
|
|
29
|
+
'contracts/test/ignore-errors/UnsafeContractWithInheritedParent.sol:UnsafeContractWithInheritedParent',
|
|
30
|
+
'contracts/test/ignore-errors/UnsafeContractWithInheritedTransitiveParent.sol:UnsafeContractWithInheritedTransitiveParent',
|
|
31
|
+
'contracts/test/ignore-errors/UnsafeAllow.sol:UnsafeAllow',
|
|
32
|
+
'contracts/test/ignore-errors/AllowReachable.sol:AllowReachable',
|
|
33
|
+
'contracts/test/ignore-errors/UnsafeAllowReachableDifferentOpcode.sol:UnsafeAllowReachableDifferentOpcode',
|
|
34
|
+
'contracts/test/ignore-errors/UnsafeAllowParent.sol:UnsafeAllowParent',
|
|
35
|
+
'contracts/test/ignore-errors/AllowReachableParent.sol:AllowReachableParent',
|
|
36
|
+
'contracts/test/ignore-errors/UnsafeAllowReachableParentDifferentOpcode.sol:UnsafeAllowReachableParentDifferentOpcode',
|
|
37
|
+
'contracts/test/ignore-errors/AllowChildInheritedTransitive.sol:AllowChildInheritedTransitive',
|
|
38
|
+
'contracts/test/ignore-errors/AllowChild.sol:AllowChild',
|
|
39
|
+
'contracts/test/ignore-errors/AllowChildCallTransitive.sol:AllowChildCallTransitive',
|
|
40
|
+
'contracts/test/ignore-errors/AllowChildSelfReachable.sol:AllowChildSelfReachable',
|
|
41
|
+
'contracts/test/ignore-errors/SafeRecursion.sol:SafeRecursion',
|
|
42
|
+
'contracts/test/ignore-errors/UnsafeRecursion.sol:UnsafeRecursion',
|
|
43
|
+
'contracts/test/ignore-errors/Constructors.sol:UnsafeChild1',
|
|
44
|
+
'contracts/test/ignore-errors/Constructors.sol:UnsafeChild2',
|
|
45
|
+
'contracts/test/ignore-errors/Constructors.sol:UnsafeChild3',
|
|
46
|
+
'contracts/test/ignore-errors/Constructors.sol:UnsafeChild4',
|
|
47
|
+
'contracts/test/ignore-errors/Constructors.sol:AllowChild5',
|
|
48
|
+
'contracts/test/ignore-errors/Constructors.sol:AllowChild6',
|
|
49
|
+
'contracts/test/ignore-errors/Constructors.sol:UnsafeAllowChild7',
|
|
50
|
+
'contracts/test/ignore-errors/Constructors.sol:AllowReachableChild8',
|
|
51
|
+
'contracts/test/ignore-errors/Constructors.sol:UnsafeChild9',
|
|
52
|
+
// 'contracts/test/ignore-errors/Modifiers.sol:ModifierNotUsed',
|
|
53
|
+
'contracts/test/ignore-errors/Modifiers.sol:ModifierUsed',
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
t.context.validation = {} as RunValidation;
|
|
57
|
+
for (const contract of contracts) {
|
|
58
|
+
const buildInfo = await artifacts.getBuildInfo(contract);
|
|
59
|
+
if (buildInfo === undefined) {
|
|
60
|
+
throw new Error(`Build info not found for contract ${contract}`);
|
|
61
|
+
}
|
|
62
|
+
const solcOutput = buildInfo.output;
|
|
63
|
+
const solcInput = buildInfo.input;
|
|
64
|
+
const decodeSrc = solcInputOutputDecoder(solcInput, solcOutput);
|
|
65
|
+
Object.assign(t.context.validation, validate(solcOutput, decodeSrc));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
function testValid(name: string, kind: ValidationOptions['kind'], valid: boolean) {
|
|
70
|
+
testOverride(name, kind, {}, valid);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function testOverride(name: string, kind: ValidationOptions['kind'], opts: ValidationOptions, valid: boolean) {
|
|
74
|
+
const optKeys = Object.keys(opts);
|
|
75
|
+
const describeOpts = optKeys.length > 0 ? '(' + optKeys.join(', ') + ')' : '';
|
|
76
|
+
const testName = [valid ? 'accepts' : 'rejects', kind, name, describeOpts].join(' ');
|
|
77
|
+
test(testName, t => {
|
|
78
|
+
const version = getContractVersion(t.context.validation, name);
|
|
79
|
+
const assertUpgSafe = () => assertUpgradeSafe([t.context.validation], version, { kind, ...opts });
|
|
80
|
+
if (valid) {
|
|
81
|
+
t.notThrows(assertUpgSafe);
|
|
82
|
+
} else {
|
|
83
|
+
t.throws(assertUpgSafe);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
testValid('SafeContract', 'transparent', true);
|
|
89
|
+
testValid('SafeContractWithFreeFunctionCall', 'transparent', true);
|
|
90
|
+
testValid('SafeContractWithLibraryCall', 'transparent', true);
|
|
91
|
+
testValid('SafeContractWithLibraryImport', 'transparent', true);
|
|
92
|
+
testValid('SafeContractWithLibraryUsingFor', 'transparent', true);
|
|
93
|
+
testValid('SafeContractWithTransitiveLibraryCall', 'transparent', true);
|
|
94
|
+
testValid('SafeContractWithParentCall', 'transparent', true);
|
|
95
|
+
|
|
96
|
+
testValid('RiskyParentContract', 'transparent', false);
|
|
97
|
+
|
|
98
|
+
testValid('UnsafeContract', 'transparent', false);
|
|
99
|
+
testValid('UnsafeContractWithFreeFunctionCall', 'transparent', false);
|
|
100
|
+
testValid('UnsafeContractWithLibraryCall', 'transparent', false);
|
|
101
|
+
testValid('UnsafeContractWithLibraryUsingFor', 'transparent', false);
|
|
102
|
+
testValid('UnsafeContractWithTransitiveLibraryCall', 'transparent', false);
|
|
103
|
+
testValid('UnsafeContractWithParentCall', 'transparent', false);
|
|
104
|
+
testValid('UnsafeContractWithInheritedParent', 'transparent', false);
|
|
105
|
+
testValid('UnsafeContractWithInheritedTransitiveParent', 'transparent', false);
|
|
106
|
+
|
|
107
|
+
testValid('UnsafeAllow', 'transparent', false);
|
|
108
|
+
testValid('AllowReachable', 'transparent', true);
|
|
109
|
+
testValid('UnsafeAllowReachableDifferentOpcode', 'transparent', false);
|
|
110
|
+
|
|
111
|
+
testValid('UnsafeAllowParent', 'transparent', false);
|
|
112
|
+
testValid('AllowReachableParent', 'transparent', true);
|
|
113
|
+
testValid('UnsafeAllowReachableParentDifferentOpcode', 'transparent', false);
|
|
114
|
+
|
|
115
|
+
testValid('AllowChildInheritedTransitive', 'transparent', true);
|
|
116
|
+
testValid('AllowChild', 'transparent', true);
|
|
117
|
+
testValid('AllowChildCallTransitive', 'transparent', true);
|
|
118
|
+
|
|
119
|
+
testValid('AllowChildSelfReachable', 'transparent', true);
|
|
120
|
+
|
|
121
|
+
testValid('SafeRecursion', 'transparent', true);
|
|
122
|
+
testValid('UnsafeRecursion', 'transparent', false);
|
|
123
|
+
|
|
124
|
+
testValid('UnsafeChild1', 'transparent', false);
|
|
125
|
+
testValid('UnsafeChild2', 'transparent', false);
|
|
126
|
+
testValid('UnsafeChild3', 'transparent', false);
|
|
127
|
+
testValid('UnsafeChild4', 'transparent', false);
|
|
128
|
+
testValid('AllowChild5', 'transparent', true);
|
|
129
|
+
testValid('AllowChild6', 'transparent', true);
|
|
130
|
+
testValid('UnsafeAllowChild7', 'transparent', false);
|
|
131
|
+
testValid('AllowReachableChild8', 'transparent', true);
|
|
132
|
+
testValid('UnsafeChild9', 'transparent', false);
|
|
133
|
+
|
|
134
|
+
// TODO: do not throw an error in this case
|
|
135
|
+
// testValid('ModifierNotUsed', 'transparent', true);
|
|
136
|
+
|
|
137
|
+
testValid('ModifierUsed', 'transparent', false);
|