skittles 1.2.4 → 1.2.6
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/compiler/codegen.d.ts +2 -2
- package/dist/compiler/codegen.d.ts.map +1 -1
- package/dist/compiler/codegen.js +119 -8
- package/dist/compiler/codegen.js.map +1 -1
- package/dist/compiler/compiler.d.ts.map +1 -1
- package/dist/compiler/compiler.js +154 -35
- package/dist/compiler/compiler.js.map +1 -1
- package/dist/compiler/parser.d.ts +7 -5
- package/dist/compiler/parser.d.ts.map +1 -1
- package/dist/compiler/parser.js +185 -22
- package/dist/compiler/parser.js.map +1 -1
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import type { SkittlesContract, SkittlesFunction, SkittlesParameter, SkittlesType, Statement, Expression } from "../types/index.ts";
|
|
2
|
+
import type { SkittlesContract, SkittlesFunction, SkittlesParameter, SkittlesType, SkittlesContractInterface, Statement, Expression } from "../types/index.ts";
|
|
3
3
|
/**
|
|
4
|
-
* Pre-scan a source file to collect
|
|
5
|
-
* without parsing any classes.
|
|
6
|
-
* cross-file type references.
|
|
4
|
+
* Pre-scan a source file to collect type aliases (structs), interfaces
|
|
5
|
+
* (contract interfaces), and enums without parsing any classes.
|
|
6
|
+
* Used by the compiler to resolve cross-file type references.
|
|
7
7
|
*/
|
|
8
8
|
export declare function collectTypes(source: string, filePath: string): {
|
|
9
9
|
structs: Map<string, SkittlesParameter[]>;
|
|
10
10
|
enums: Map<string, string[]>;
|
|
11
|
+
contractInterfaces: Map<string, SkittlesContractInterface>;
|
|
11
12
|
};
|
|
12
13
|
export interface ExternalTypes {
|
|
13
14
|
structs?: Map<string, SkittlesParameter[]>;
|
|
14
15
|
enums?: Map<string, string[]>;
|
|
16
|
+
contractInterfaces?: Map<string, SkittlesContractInterface>;
|
|
15
17
|
}
|
|
16
18
|
export interface ExternalFunctions {
|
|
17
19
|
functions?: SkittlesFunction[];
|
|
@@ -29,6 +31,6 @@ export declare function collectFunctions(source: string, filePath: string): {
|
|
|
29
31
|
export declare function parseType(node: ts.TypeNode): SkittlesType;
|
|
30
32
|
export declare function parseExpression(node: ts.Expression): Expression;
|
|
31
33
|
export declare function parseStatement(node: ts.Statement, varTypes: Map<string, SkittlesType>, eventNames?: Set<string>): Statement;
|
|
32
|
-
export declare function inferStateMutability(body: Statement[]): "pure" | "view" | "nonpayable" | "payable";
|
|
34
|
+
export declare function inferStateMutability(body: Statement[], varTypes?: Map<string, SkittlesType>): "pure" | "view" | "nonpayable" | "payable";
|
|
33
35
|
export declare function inferType(expr: Expression, varTypes: Map<string, SkittlesType>): SkittlesType | undefined;
|
|
34
36
|
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/compiler/parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,KAAK,EACV,gBAAgB,EAEhB,gBAAgB,EAGhB,iBAAiB,EACjB,YAAY,
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/compiler/parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,KAAK,EACV,gBAAgB,EAEhB,gBAAgB,EAGhB,iBAAiB,EACjB,YAAY,EAEZ,yBAAyB,EAGzB,SAAS,EACT,UAAU,EAGX,MAAM,mBAAmB,CAAC;AAa3B;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;IAC9D,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC1C,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7B,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;CAC5D,CA2CA;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9B,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;CAC7D;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC/B,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACrC;AAED,wBAAgB,KAAK,CACnB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,aAAa,CAAC,EAAE,aAAa,EAC7B,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,gBAAgB,EAAE,CAmHpB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;IAClE,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACpC,CA4CA;AAspBD,wBAAgB,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,GAAG,YAAY,CAiEzD;AAMD,wBAAgB,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,UAAU,CAqM/D;AAMD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,EAAE,CAAC,SAAS,EAClB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACnC,UAAU,GAAE,GAAG,CAAC,MAAM,CAAa,GAClC,SAAS,CAmOX;AAqPD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,MAAM,GAAG,MAAM,GAAG,YAAY,GAAG,SAAS,CAmDxI;AAMD,wBAAgB,SAAS,CACvB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAClC,YAAY,GAAG,SAAS,CA+D1B"}
|
package/dist/compiler/parser.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
// Module-level registries, populated during parse()
|
|
3
3
|
let _knownStructs = new Map();
|
|
4
|
+
let _knownContractInterfaces = new Set();
|
|
4
5
|
let _knownEnums = new Set();
|
|
5
6
|
let _knownCustomErrors = new Set();
|
|
6
7
|
let _fileConstants = new Map();
|
|
@@ -8,24 +9,32 @@ let _fileConstants = new Map();
|
|
|
8
9
|
// Main entry
|
|
9
10
|
// ============================================================
|
|
10
11
|
/**
|
|
11
|
-
* Pre-scan a source file to collect
|
|
12
|
-
* without parsing any classes.
|
|
13
|
-
* cross-file type references.
|
|
12
|
+
* Pre-scan a source file to collect type aliases (structs), interfaces
|
|
13
|
+
* (contract interfaces), and enums without parsing any classes.
|
|
14
|
+
* Used by the compiler to resolve cross-file type references.
|
|
14
15
|
*/
|
|
15
16
|
export function collectTypes(source, filePath) {
|
|
16
17
|
const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);
|
|
17
18
|
const structs = new Map();
|
|
18
19
|
const enums = new Map();
|
|
20
|
+
const contractInterfaces = new Map();
|
|
19
21
|
// Temporarily set module registries so parseType can resolve references
|
|
20
22
|
const prevStructs = _knownStructs;
|
|
21
23
|
const prevEnums = _knownEnums;
|
|
24
|
+
const prevInterfaces = _knownContractInterfaces;
|
|
22
25
|
_knownStructs = structs;
|
|
23
26
|
_knownEnums = new Set();
|
|
27
|
+
_knownContractInterfaces = new Set();
|
|
24
28
|
ts.forEachChild(sourceFile, (node) => {
|
|
25
|
-
if (ts.
|
|
26
|
-
const fields =
|
|
29
|
+
if (ts.isTypeAliasDeclaration(node) && node.name && ts.isTypeLiteralNode(node.type)) {
|
|
30
|
+
const fields = parseTypeLiteralFields(node.type);
|
|
27
31
|
structs.set(node.name.text, fields);
|
|
28
32
|
}
|
|
33
|
+
if (ts.isInterfaceDeclaration(node) && node.name) {
|
|
34
|
+
const iface = parseInterfaceAsContractInterface(node);
|
|
35
|
+
contractInterfaces.set(node.name.text, iface);
|
|
36
|
+
_knownContractInterfaces.add(node.name.text);
|
|
37
|
+
}
|
|
29
38
|
if (ts.isEnumDeclaration(node) && node.name) {
|
|
30
39
|
const members = node.members.map((m) => ts.isIdentifier(m.name) ? m.name.text : "Unknown");
|
|
31
40
|
enums.set(node.name.text, members);
|
|
@@ -33,12 +42,14 @@ export function collectTypes(source, filePath) {
|
|
|
33
42
|
});
|
|
34
43
|
_knownStructs = prevStructs;
|
|
35
44
|
_knownEnums = prevEnums;
|
|
36
|
-
|
|
45
|
+
_knownContractInterfaces = prevInterfaces;
|
|
46
|
+
return { structs, enums, contractInterfaces };
|
|
37
47
|
}
|
|
38
48
|
export function parse(source, filePath, externalTypes, externalFunctions) {
|
|
39
49
|
const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);
|
|
40
50
|
const structs = new Map();
|
|
41
51
|
const enums = new Map();
|
|
52
|
+
const contractInterfaces = new Map();
|
|
42
53
|
const contracts = [];
|
|
43
54
|
// Seed with externally resolved types (from other files)
|
|
44
55
|
if (externalTypes?.structs) {
|
|
@@ -51,9 +62,15 @@ export function parse(source, filePath, externalTypes, externalFunctions) {
|
|
|
51
62
|
enums.set(name, members);
|
|
52
63
|
}
|
|
53
64
|
}
|
|
65
|
+
if (externalTypes?.contractInterfaces) {
|
|
66
|
+
for (const [name, iface] of externalTypes.contractInterfaces) {
|
|
67
|
+
contractInterfaces.set(name, iface);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// First pass: collect structs and enums so they are available when parsing interfaces
|
|
54
71
|
ts.forEachChild(sourceFile, (node) => {
|
|
55
|
-
if (ts.
|
|
56
|
-
const fields =
|
|
72
|
+
if (ts.isTypeAliasDeclaration(node) && node.name && ts.isTypeLiteralNode(node.type)) {
|
|
73
|
+
const fields = parseTypeLiteralFields(node.type);
|
|
57
74
|
structs.set(node.name.text, fields);
|
|
58
75
|
}
|
|
59
76
|
if (ts.isEnumDeclaration(node) && node.name) {
|
|
@@ -61,9 +78,17 @@ export function parse(source, filePath, externalTypes, externalFunctions) {
|
|
|
61
78
|
enums.set(node.name.text, members);
|
|
62
79
|
}
|
|
63
80
|
});
|
|
64
|
-
const customErrors = new Map();
|
|
65
81
|
_knownStructs = structs;
|
|
66
82
|
_knownEnums = new Set(enums.keys());
|
|
83
|
+
// Second pass: parse interfaces (may reference struct/enum types collected above)
|
|
84
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
85
|
+
if (ts.isInterfaceDeclaration(node) && node.name) {
|
|
86
|
+
const iface = parseInterfaceAsContractInterface(node);
|
|
87
|
+
contractInterfaces.set(node.name.text, iface);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
const customErrors = new Map();
|
|
91
|
+
_knownContractInterfaces = new Set(contractInterfaces.keys());
|
|
67
92
|
_knownCustomErrors = new Set();
|
|
68
93
|
_fileConstants = new Map();
|
|
69
94
|
// Collect file level constants (const declarations outside classes)
|
|
@@ -110,7 +135,7 @@ export function parse(source, filePath, externalTypes, externalFunctions) {
|
|
|
110
135
|
_knownCustomErrors.add(node.name.text);
|
|
111
136
|
}
|
|
112
137
|
else {
|
|
113
|
-
contracts.push(parseClass(node, filePath, structs, enums, customErrors, fileFunctions, fileConstants));
|
|
138
|
+
contracts.push(parseClass(node, filePath, structs, enums, contractInterfaces, customErrors, fileFunctions, fileConstants));
|
|
114
139
|
}
|
|
115
140
|
}
|
|
116
141
|
});
|
|
@@ -126,11 +151,13 @@ export function collectFunctions(source, filePath) {
|
|
|
126
151
|
const constants = new Map();
|
|
127
152
|
const emptyVarTypes = new Map();
|
|
128
153
|
const emptyEventNames = new Set();
|
|
129
|
-
// Need to set up struct/enum registries for type parsing
|
|
154
|
+
// Need to set up struct/enum/interface registries for type parsing
|
|
130
155
|
const prevStructs = _knownStructs;
|
|
131
156
|
const prevEnums = _knownEnums;
|
|
157
|
+
const prevInterfaces = _knownContractInterfaces;
|
|
132
158
|
_knownStructs = new Map();
|
|
133
159
|
_knownEnums = new Set();
|
|
160
|
+
_knownContractInterfaces = new Set();
|
|
134
161
|
ts.forEachChild(sourceFile, (node) => {
|
|
135
162
|
if (ts.isFunctionDeclaration(node) && node.name && node.body) {
|
|
136
163
|
functions.push(parseStandaloneFunction(node, emptyVarTypes, emptyEventNames));
|
|
@@ -150,6 +177,7 @@ export function collectFunctions(source, filePath) {
|
|
|
150
177
|
});
|
|
151
178
|
_knownStructs = prevStructs;
|
|
152
179
|
_knownEnums = prevEnums;
|
|
180
|
+
_knownContractInterfaces = prevInterfaces;
|
|
153
181
|
return { functions, constants };
|
|
154
182
|
}
|
|
155
183
|
function parseArrayDestructuring(pattern, initializer, varTypes) {
|
|
@@ -262,7 +290,7 @@ function parseErrorClass(node) {
|
|
|
262
290
|
}
|
|
263
291
|
return [];
|
|
264
292
|
}
|
|
265
|
-
function
|
|
293
|
+
function parseTypeLiteralFields(node) {
|
|
266
294
|
const fields = [];
|
|
267
295
|
for (const member of node.members) {
|
|
268
296
|
if (ts.isPropertySignature(member) &&
|
|
@@ -277,10 +305,30 @@ function parseInterfaceFields(node) {
|
|
|
277
305
|
}
|
|
278
306
|
return fields;
|
|
279
307
|
}
|
|
308
|
+
function parseInterfaceAsContractInterface(node) {
|
|
309
|
+
const name = node.name.text;
|
|
310
|
+
const functions = [];
|
|
311
|
+
for (const member of node.members) {
|
|
312
|
+
if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {
|
|
313
|
+
const propName = member.name.text;
|
|
314
|
+
const returnType = member.type
|
|
315
|
+
? parseType(member.type)
|
|
316
|
+
: { kind: "uint256" };
|
|
317
|
+
functions.push({ name: propName, parameters: [], returnType, stateMutability: "view" });
|
|
318
|
+
}
|
|
319
|
+
if (ts.isMethodSignature(member) && member.name && ts.isIdentifier(member.name)) {
|
|
320
|
+
const methodName = member.name.text;
|
|
321
|
+
const parameters = member.parameters.map(parseParameter);
|
|
322
|
+
const returnType = member.type ? parseType(member.type) : null;
|
|
323
|
+
functions.push({ name: methodName, parameters, returnType });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return { name, functions };
|
|
327
|
+
}
|
|
280
328
|
// ============================================================
|
|
281
329
|
// Class level parsing
|
|
282
330
|
// ============================================================
|
|
283
|
-
function parseClass(node, filePath, knownStructs = new Map(), knownEnums = new Map(), knownCustomErrors = new Map(), fileFunctions = [], fileConstants = new Map()) {
|
|
331
|
+
function parseClass(node, filePath, knownStructs = new Map(), knownEnums = new Map(), knownContractInterfaces = new Map(), knownCustomErrors = new Map(), fileFunctions = [], fileConstants = new Map()) {
|
|
284
332
|
const name = node.name?.text ?? "Unknown";
|
|
285
333
|
const variables = [];
|
|
286
334
|
const functions = [];
|
|
@@ -296,9 +344,13 @@ function parseClass(node, filePath, knownStructs = new Map(), knownEnums = new M
|
|
|
296
344
|
}
|
|
297
345
|
}
|
|
298
346
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
347
|
+
if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
|
|
348
|
+
for (const type of clause.types) {
|
|
349
|
+
if (ts.isIdentifier(type.expression)) {
|
|
350
|
+
inherits.push(type.expression.text);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
302
354
|
}
|
|
303
355
|
}
|
|
304
356
|
// Collect arrow function properties separately so they can be parsed as methods
|
|
@@ -371,6 +423,19 @@ function parseClass(node, filePath, knownStructs = new Map(), knownEnums = new M
|
|
|
371
423
|
// Third pass: propagate state mutability through call chains.
|
|
372
424
|
// If function A calls this.B(), and B is nonpayable, then A is also nonpayable.
|
|
373
425
|
propagateStateMutability(functions);
|
|
426
|
+
// Build set of interface names this class implements
|
|
427
|
+
const implementedInterfaceNames = new Set();
|
|
428
|
+
if (node.heritageClauses) {
|
|
429
|
+
for (const clause of node.heritageClauses) {
|
|
430
|
+
if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
|
|
431
|
+
for (const type of clause.types) {
|
|
432
|
+
if (ts.isIdentifier(type.expression) && knownContractInterfaces.has(type.expression.text)) {
|
|
433
|
+
implementedInterfaceNames.add(type.expression.text);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
374
439
|
const contractStructs = [];
|
|
375
440
|
for (const [sName, fields] of knownStructs) {
|
|
376
441
|
contractStructs.push({ name: sName, fields });
|
|
@@ -379,6 +444,72 @@ function parseClass(node, filePath, knownStructs = new Map(), knownEnums = new M
|
|
|
379
444
|
for (const [eName, members] of knownEnums) {
|
|
380
445
|
contractEnums.push({ name: eName, members });
|
|
381
446
|
}
|
|
447
|
+
// Determine which interfaces this contract actually references
|
|
448
|
+
const usedIfaceNames = new Set();
|
|
449
|
+
for (const iName of inherits) {
|
|
450
|
+
if (knownContractInterfaces.has(iName))
|
|
451
|
+
usedIfaceNames.add(iName);
|
|
452
|
+
}
|
|
453
|
+
for (const v of variables) {
|
|
454
|
+
collectContractInterfaceTypeRefs(v.type, usedIfaceNames);
|
|
455
|
+
}
|
|
456
|
+
for (const f of functions) {
|
|
457
|
+
for (const p of f.parameters)
|
|
458
|
+
collectContractInterfaceTypeRefs(p.type, usedIfaceNames);
|
|
459
|
+
if (f.returnType)
|
|
460
|
+
collectContractInterfaceTypeRefs(f.returnType, usedIfaceNames);
|
|
461
|
+
}
|
|
462
|
+
if (ctor) {
|
|
463
|
+
for (const p of ctor.parameters)
|
|
464
|
+
collectContractInterfaceTypeRefs(p.type, usedIfaceNames);
|
|
465
|
+
}
|
|
466
|
+
// Deep copy only used interfaces so mutability updates don't leak to shared state
|
|
467
|
+
const contractIfaceList = [];
|
|
468
|
+
for (const ifName of usedIfaceNames) {
|
|
469
|
+
const iface = knownContractInterfaces.get(ifName);
|
|
470
|
+
if (iface) {
|
|
471
|
+
contractIfaceList.push({
|
|
472
|
+
name: iface.name,
|
|
473
|
+
functions: iface.functions.map((fn) => ({ ...fn })),
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Fourth pass: for implemented interfaces, derive method mutabilities
|
|
478
|
+
// from the actual implementation and mark implementing members as override.
|
|
479
|
+
if (implementedInterfaceNames.size > 0) {
|
|
480
|
+
const interfaceFnNames = new Set();
|
|
481
|
+
for (const ifaceName of implementedInterfaceNames) {
|
|
482
|
+
const iface = contractIfaceList.find((i) => i.name === ifaceName);
|
|
483
|
+
if (!iface)
|
|
484
|
+
continue;
|
|
485
|
+
for (const ifn of iface.functions) {
|
|
486
|
+
interfaceFnNames.add(ifn.name);
|
|
487
|
+
if (ifn.stateMutability)
|
|
488
|
+
continue;
|
|
489
|
+
const impl = functions.find((f) => f.name === ifn.name);
|
|
490
|
+
if (impl) {
|
|
491
|
+
ifn.stateMutability = impl.stateMutability;
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
const varImpl = variables.find((v) => v.name === ifn.name && v.visibility === "public");
|
|
495
|
+
if (varImpl) {
|
|
496
|
+
ifn.stateMutability = "view";
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
for (const fn of functions) {
|
|
502
|
+
if (interfaceFnNames.has(fn.name)) {
|
|
503
|
+
fn.isOverride = true;
|
|
504
|
+
fn.isVirtual = false;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
for (const v of variables) {
|
|
508
|
+
if (v.visibility === "public" && interfaceFnNames.has(v.name) && !v.constant && !v.immutable) {
|
|
509
|
+
v.isOverride = true;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
382
513
|
const contractCustomErrors = [];
|
|
383
514
|
for (const [cName, params] of knownCustomErrors) {
|
|
384
515
|
contractCustomErrors.push({ name: cName, parameters: params });
|
|
@@ -395,6 +526,7 @@ function parseClass(node, filePath, knownStructs = new Map(), knownEnums = new M
|
|
|
395
526
|
events,
|
|
396
527
|
structs: contractStructs,
|
|
397
528
|
enums: contractEnums,
|
|
529
|
+
contractInterfaces: contractIfaceList,
|
|
398
530
|
customErrors: contractCustomErrors,
|
|
399
531
|
ctor,
|
|
400
532
|
inherits,
|
|
@@ -507,7 +639,7 @@ function parseMethod(node, varTypes, eventNames) {
|
|
|
507
639
|
: null;
|
|
508
640
|
const visibility = getVisibility(node.modifiers);
|
|
509
641
|
const body = node.body ? parseBlock(node.body, varTypes, eventNames) : [];
|
|
510
|
-
const stateMutability = inferStateMutability(body);
|
|
642
|
+
const stateMutability = inferStateMutability(body, varTypes);
|
|
511
643
|
const isOverride = hasModifier(node.modifiers, ts.SyntaxKind.OverrideKeyword);
|
|
512
644
|
const isVirtual = !isOverride;
|
|
513
645
|
return { name, parameters, returnType, visibility, stateMutability, isVirtual, isOverride, body };
|
|
@@ -520,7 +652,7 @@ function parseGetAccessor(node, varTypes, eventNames) {
|
|
|
520
652
|
: null;
|
|
521
653
|
const visibility = getVisibility(node.modifiers);
|
|
522
654
|
const body = node.body ? parseBlock(node.body, varTypes, eventNames) : [];
|
|
523
|
-
const stateMutability = inferStateMutability(body);
|
|
655
|
+
const stateMutability = inferStateMutability(body, varTypes);
|
|
524
656
|
const isOverride = hasModifier(node.modifiers, ts.SyntaxKind.OverrideKeyword);
|
|
525
657
|
const isVirtual = !isOverride;
|
|
526
658
|
return { name, parameters, returnType, visibility, stateMutability, isVirtual, isOverride, body };
|
|
@@ -531,7 +663,7 @@ function parseSetAccessor(node, varTypes, eventNames) {
|
|
|
531
663
|
const returnType = null; // setters don't return
|
|
532
664
|
const visibility = getVisibility(node.modifiers);
|
|
533
665
|
const body = node.body ? parseBlock(node.body, varTypes, eventNames) : [];
|
|
534
|
-
const stateMutability = inferStateMutability(body);
|
|
666
|
+
const stateMutability = inferStateMutability(body, varTypes);
|
|
535
667
|
const isOverride = hasModifier(node.modifiers, ts.SyntaxKind.OverrideKeyword);
|
|
536
668
|
const isVirtual = !isOverride;
|
|
537
669
|
return { name, parameters, returnType, visibility, stateMutability, isVirtual, isOverride, body };
|
|
@@ -554,7 +686,7 @@ function parseArrowProperty(node, varTypes, eventNames) {
|
|
|
554
686
|
body = [{ kind: "return", value: parseExpression(arrow.body) }];
|
|
555
687
|
}
|
|
556
688
|
}
|
|
557
|
-
const stateMutability = inferStateMutability(body);
|
|
689
|
+
const stateMutability = inferStateMutability(body, varTypes);
|
|
558
690
|
const isOverride = hasModifier(node.modifiers, ts.SyntaxKind.OverrideKeyword);
|
|
559
691
|
const isVirtual = !isOverride;
|
|
560
692
|
return { name, parameters, returnType, visibility, stateMutability, isVirtual, isOverride, body };
|
|
@@ -599,13 +731,19 @@ export function parseType(node) {
|
|
|
599
731
|
structFields: _knownStructs.get(name),
|
|
600
732
|
};
|
|
601
733
|
}
|
|
734
|
+
if (_knownContractInterfaces.has(name)) {
|
|
735
|
+
return {
|
|
736
|
+
kind: "contract-interface",
|
|
737
|
+
structName: name,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
602
740
|
if (_knownEnums.has(name)) {
|
|
603
741
|
return {
|
|
604
742
|
kind: "enum",
|
|
605
743
|
structName: name,
|
|
606
744
|
};
|
|
607
745
|
}
|
|
608
|
-
throw new Error(`Unsupported type reference: "${name}". Skittles supports number, string, boolean, address, bytes, Record<K,V>, T[],
|
|
746
|
+
throw new Error(`Unsupported type reference: "${name}". Skittles supports number, string, boolean, address, bytes, Record<K,V>, T[], type structs, interfaces, and enums.`);
|
|
609
747
|
}
|
|
610
748
|
if (ts.isArrayTypeNode(node)) {
|
|
611
749
|
return {
|
|
@@ -1222,7 +1360,7 @@ function collectThisCalls(stmts) {
|
|
|
1222
1360
|
});
|
|
1223
1361
|
return names;
|
|
1224
1362
|
}
|
|
1225
|
-
export function inferStateMutability(body) {
|
|
1363
|
+
export function inferStateMutability(body, varTypes) {
|
|
1226
1364
|
let readsState = false;
|
|
1227
1365
|
let writesState = false;
|
|
1228
1366
|
let usesMsgValue = false;
|
|
@@ -1248,6 +1386,9 @@ export function inferStateMutability(body) {
|
|
|
1248
1386
|
if (expr.kind === "call" && isStateMutatingCall(expr)) {
|
|
1249
1387
|
writesState = true;
|
|
1250
1388
|
}
|
|
1389
|
+
if (expr.kind === "call" && varTypes && isExternalContractCall(expr, varTypes)) {
|
|
1390
|
+
writesState = true;
|
|
1391
|
+
}
|
|
1251
1392
|
}, (stmt) => {
|
|
1252
1393
|
if (stmt.kind === "emit") {
|
|
1253
1394
|
writesState = true;
|
|
@@ -1330,6 +1471,15 @@ export function inferType(expr, varTypes) {
|
|
|
1330
1471
|
// ============================================================
|
|
1331
1472
|
// Helpers
|
|
1332
1473
|
// ============================================================
|
|
1474
|
+
function collectContractInterfaceTypeRefs(type, refs) {
|
|
1475
|
+
if (type.kind === "contract-interface" && type.structName) {
|
|
1476
|
+
refs.add(type.structName);
|
|
1477
|
+
}
|
|
1478
|
+
if (type.keyType)
|
|
1479
|
+
collectContractInterfaceTypeRefs(type.keyType, refs);
|
|
1480
|
+
if (type.valueType)
|
|
1481
|
+
collectContractInterfaceTypeRefs(type.valueType, refs);
|
|
1482
|
+
}
|
|
1333
1483
|
function isStateAccess(expr) {
|
|
1334
1484
|
if (expr.kind === "property-access" &&
|
|
1335
1485
|
expr.object.kind === "identifier" &&
|
|
@@ -1341,6 +1491,19 @@ function isStateAccess(expr) {
|
|
|
1341
1491
|
}
|
|
1342
1492
|
return false;
|
|
1343
1493
|
}
|
|
1494
|
+
function isExternalContractCall(expr, varTypes) {
|
|
1495
|
+
if (expr.callee.kind === "property-access" &&
|
|
1496
|
+
expr.callee.object.kind === "property-access" &&
|
|
1497
|
+
expr.callee.object.object.kind === "identifier" &&
|
|
1498
|
+
expr.callee.object.object.name === "this") {
|
|
1499
|
+
const propName = expr.callee.object.property;
|
|
1500
|
+
const propType = varTypes.get(propName);
|
|
1501
|
+
if (propType && propType.kind === "contract-interface") {
|
|
1502
|
+
return true;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
return false;
|
|
1506
|
+
}
|
|
1344
1507
|
function isStateMutatingCall(expr) {
|
|
1345
1508
|
if (expr.callee.kind !== "property-access")
|
|
1346
1509
|
return false;
|