skittles 1.2.6 → 1.3.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/README.md +4 -4
- package/dist/commands/clean.d.ts.map +1 -1
- package/dist/commands/clean.js +4 -1
- package/dist/commands/clean.js.map +1 -1
- package/dist/commands/compile.d.ts +1 -0
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +51 -1
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +5 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/test.d.ts +7 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +60 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/compiler/analysis.d.ts +7 -0
- package/dist/compiler/analysis.d.ts.map +1 -0
- package/dist/compiler/analysis.js +234 -0
- package/dist/compiler/analysis.js.map +1 -0
- package/dist/compiler/codegen.d.ts +10 -1
- package/dist/compiler/codegen.d.ts.map +1 -1
- package/dist/compiler/codegen.js +389 -22
- package/dist/compiler/codegen.js.map +1 -1
- package/dist/compiler/compiler.d.ts +2 -1
- package/dist/compiler/compiler.d.ts.map +1 -1
- package/dist/compiler/compiler.js +94 -9
- package/dist/compiler/compiler.js.map +1 -1
- package/dist/compiler/parser.d.ts +7 -1
- package/dist/compiler/parser.d.ts.map +1 -1
- package/dist/compiler/parser.js +731 -47
- package/dist/compiler/parser.js.map +1 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +5 -1
- package/dist/config/config.js.map +1 -1
- package/dist/exports.d.ts +13 -2
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js.map +1 -1
- package/dist/index.js +19 -4
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +93 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +143 -0
- package/dist/testing.js.map +1 -0
- package/dist/types/index.d.ts +51 -2
- 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 +2 -2
package/dist/compiler/codegen.js
CHANGED
|
@@ -11,10 +11,17 @@ export function generateSolidityFile(contracts, imports) {
|
|
|
11
11
|
parts.push("// SPDX-License-Identifier: MIT");
|
|
12
12
|
parts.push("pragma solidity ^0.8.20;");
|
|
13
13
|
parts.push("");
|
|
14
|
+
// Add Hardhat console import if any contract uses console.log
|
|
15
|
+
const needsConsoleImport = contracts.some(contractUsesConsoleLog);
|
|
16
|
+
if (needsConsoleImport) {
|
|
17
|
+
parts.push('import "hardhat/console.sol";');
|
|
18
|
+
}
|
|
14
19
|
if (imports && imports.length > 0) {
|
|
15
20
|
for (const imp of imports) {
|
|
16
21
|
parts.push(`import "${imp}";`);
|
|
17
22
|
}
|
|
23
|
+
}
|
|
24
|
+
if (needsConsoleImport || (imports && imports.length > 0)) {
|
|
18
25
|
parts.push("");
|
|
19
26
|
}
|
|
20
27
|
// Collect and deduplicate contract interfaces across all contracts
|
|
@@ -63,8 +70,36 @@ export function generateSolidityFile(contracts, imports) {
|
|
|
63
70
|
parts.push(generateInterfaceDecl(iface));
|
|
64
71
|
parts.push("");
|
|
65
72
|
}
|
|
73
|
+
// Build per-contract ancestor sets so deduplication only applies when the
|
|
74
|
+
// current contract inherits from the contract that first emitted a definition.
|
|
75
|
+
const contractByName = new Map(contracts.map((c) => [c.name, c]));
|
|
76
|
+
const ancestorsMap = new Map();
|
|
77
|
+
for (const contract of contracts) {
|
|
78
|
+
const ancestors = new Set();
|
|
79
|
+
const queue = [...contract.inherits.filter((n) => contractByName.has(n))];
|
|
80
|
+
let queueIndex = 0;
|
|
81
|
+
while (queueIndex < queue.length) {
|
|
82
|
+
const name = queue[queueIndex++];
|
|
83
|
+
if (ancestors.has(name))
|
|
84
|
+
continue;
|
|
85
|
+
ancestors.add(name);
|
|
86
|
+
const parent = contractByName.get(name);
|
|
87
|
+
if (parent) {
|
|
88
|
+
for (const gp of parent.inherits) {
|
|
89
|
+
if (contractByName.has(gp))
|
|
90
|
+
queue.push(gp);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
ancestorsMap.set(contract.name, ancestors);
|
|
95
|
+
}
|
|
96
|
+
// Track all contracts that emitted each definition / function so child
|
|
97
|
+
// contracts that inherit from any of those origins can skip re-emission.
|
|
98
|
+
const definitionOrigins = new Map();
|
|
99
|
+
const functionOrigins = new Map();
|
|
66
100
|
for (let i = 0; i < contracts.length; i++) {
|
|
67
|
-
|
|
101
|
+
const ancestors = ancestorsMap.get(contracts[i].name);
|
|
102
|
+
parts.push(generateContractBody(contracts[i], emittedFileScopeTypes, definitionOrigins, functionOrigins, ancestors));
|
|
68
103
|
if (i < contracts.length - 1) {
|
|
69
104
|
parts.push("");
|
|
70
105
|
}
|
|
@@ -75,28 +110,49 @@ export function generateSolidityFile(contracts, imports) {
|
|
|
75
110
|
export function generateSolidity(contract, imports) {
|
|
76
111
|
return generateSolidityFile([contract], imports);
|
|
77
112
|
}
|
|
78
|
-
function generateContractBody(contract, fileScopeTypes = new Set()) {
|
|
113
|
+
function generateContractBody(contract, fileScopeTypes = new Set(), definitionOrigins = new Map(), functionOrigins = new Map(), ancestors = new Set()) {
|
|
79
114
|
const parts = [];
|
|
80
115
|
const inheritance = contract.inherits.length > 0
|
|
81
116
|
? ` is ${contract.inherits.join(", ")}`
|
|
82
117
|
: "";
|
|
83
|
-
|
|
118
|
+
const abstractPrefix = contract.isAbstract ? "abstract " : "";
|
|
119
|
+
parts.push(`${abstractPrefix}contract ${contract.name}${inheritance} {`);
|
|
120
|
+
const hasAncestorOrigin = (origins) => origins !== undefined && Array.from(origins).some((o) => ancestors.has(o));
|
|
121
|
+
const addOrigin = (map, key) => {
|
|
122
|
+
let origins = map.get(key);
|
|
123
|
+
if (!origins) {
|
|
124
|
+
origins = new Set();
|
|
125
|
+
map.set(key, origins);
|
|
126
|
+
}
|
|
127
|
+
origins.add(contract.name);
|
|
128
|
+
};
|
|
84
129
|
for (const en of contract.enums ?? []) {
|
|
85
130
|
if (fileScopeTypes.has(en.name))
|
|
86
131
|
continue;
|
|
132
|
+
if (hasAncestorOrigin(definitionOrigins.get(en.name)))
|
|
133
|
+
continue;
|
|
134
|
+
addOrigin(definitionOrigins, en.name);
|
|
87
135
|
parts.push(` enum ${en.name} { ${en.members.join(", ")} }`);
|
|
88
136
|
parts.push("");
|
|
89
137
|
}
|
|
138
|
+
let emittedCustomErrorCount = 0;
|
|
90
139
|
for (const ce of contract.customErrors ?? []) {
|
|
140
|
+
if (hasAncestorOrigin(definitionOrigins.get(ce.name)))
|
|
141
|
+
continue;
|
|
142
|
+
addOrigin(definitionOrigins, ce.name);
|
|
91
143
|
const params = ce.parameters.map((p) => `${generateType(p.type)} ${p.name}`).join(", ");
|
|
92
144
|
parts.push(` error ${ce.name}(${params});`);
|
|
145
|
+
emittedCustomErrorCount++;
|
|
93
146
|
}
|
|
94
|
-
if (
|
|
147
|
+
if (emittedCustomErrorCount > 0) {
|
|
95
148
|
parts.push("");
|
|
96
149
|
}
|
|
97
150
|
for (const s of contract.structs ?? []) {
|
|
98
151
|
if (fileScopeTypes.has(s.name))
|
|
99
152
|
continue;
|
|
153
|
+
if (hasAncestorOrigin(definitionOrigins.get(s.name)))
|
|
154
|
+
continue;
|
|
155
|
+
addOrigin(definitionOrigins, s.name);
|
|
100
156
|
parts.push(generateStructDecl(s));
|
|
101
157
|
parts.push("");
|
|
102
158
|
}
|
|
@@ -109,19 +165,43 @@ function generateContractBody(contract, fileScopeTypes = new Set()) {
|
|
|
109
165
|
for (const v of contract.variables) {
|
|
110
166
|
parts.push(` ${generateVariable(v)}`);
|
|
111
167
|
}
|
|
168
|
+
const readonlyArrayVars = contract.variables.filter((v) => v.immutable && v.type.kind === SkittlesTypeKind.Array);
|
|
169
|
+
// Skip functions already emitted by an ancestor contract in the same file
|
|
170
|
+
// (shared file-level functions injected into both parent and child), unless
|
|
171
|
+
// the child explicitly overrides them. Use a full signature key (name +
|
|
172
|
+
// full parameter types) so overloads are not incorrectly suppressed.
|
|
173
|
+
const getFunctionKey = (f) => {
|
|
174
|
+
const paramTypes = f.parameters
|
|
175
|
+
.map((p) => (p.type ? generateType(p.type) : "unknown"))
|
|
176
|
+
.join(",");
|
|
177
|
+
return `${f.name}(${paramTypes})`;
|
|
178
|
+
};
|
|
179
|
+
const functionsToEmit = contract.functions.filter((f) => {
|
|
180
|
+
const key = getFunctionKey(f);
|
|
181
|
+
return !hasAncestorOrigin(functionOrigins.get(key)) || f.isOverride;
|
|
182
|
+
});
|
|
183
|
+
for (const f of functionsToEmit) {
|
|
184
|
+
addOrigin(functionOrigins, getFunctionKey(f));
|
|
185
|
+
}
|
|
112
186
|
if (contract.variables.length > 0 &&
|
|
113
|
-
(contract.ctor ||
|
|
187
|
+
(contract.ctor || functionsToEmit.length > 0 || readonlyArrayVars.length > 0)) {
|
|
114
188
|
parts.push("");
|
|
115
189
|
}
|
|
116
190
|
if (contract.ctor) {
|
|
117
|
-
parts.push(generateConstructor(contract.ctor));
|
|
118
|
-
if (
|
|
191
|
+
parts.push(generateConstructor(contract.ctor, contract.inherits));
|
|
192
|
+
if (functionsToEmit.length > 0 || readonlyArrayVars.length > 0) {
|
|
193
|
+
parts.push("");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
for (let i = 0; i < functionsToEmit.length; i++) {
|
|
197
|
+
parts.push(generateFunction(functionsToEmit[i]));
|
|
198
|
+
if (i < functionsToEmit.length - 1 || readonlyArrayVars.length > 0) {
|
|
119
199
|
parts.push("");
|
|
120
200
|
}
|
|
121
201
|
}
|
|
122
|
-
for (let i = 0; i <
|
|
123
|
-
parts.push(
|
|
124
|
-
if (i <
|
|
202
|
+
for (let i = 0; i < readonlyArrayVars.length; i++) {
|
|
203
|
+
parts.push(generateReadonlyArrayGetter(readonlyArrayVars[i]));
|
|
204
|
+
if (i < readonlyArrayVars.length - 1) {
|
|
125
205
|
parts.push("");
|
|
126
206
|
}
|
|
127
207
|
}
|
|
@@ -165,6 +245,11 @@ function collectReferencedTypeNames(type, structs, enums) {
|
|
|
165
245
|
collectReferencedTypeNames(type.keyType, structs, enums);
|
|
166
246
|
collectReferencedTypeNames(type.valueType, structs, enums);
|
|
167
247
|
}
|
|
248
|
+
else if (type.kind === SkittlesTypeKind.Tuple && type.tupleTypes) {
|
|
249
|
+
for (const t of type.tupleTypes) {
|
|
250
|
+
collectReferencedTypeNames(t, structs, enums);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
168
253
|
}
|
|
169
254
|
function generateInterfaceDecl(iface) {
|
|
170
255
|
const lines = [];
|
|
@@ -178,7 +263,13 @@ function generateInterfaceDecl(iface) {
|
|
|
178
263
|
: "";
|
|
179
264
|
let returns = "";
|
|
180
265
|
if (f.returnType && f.returnType.kind !== SkittlesTypeKind.Void) {
|
|
181
|
-
|
|
266
|
+
if (f.returnType.kind === SkittlesTypeKind.Tuple) {
|
|
267
|
+
const tupleParams = (f.returnType.tupleTypes ?? []).map(generateParamType).join(", ");
|
|
268
|
+
returns = ` returns (${tupleParams})`;
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
returns = ` returns (${generateParamType(f.returnType)})`;
|
|
272
|
+
}
|
|
182
273
|
}
|
|
183
274
|
lines.push(` function ${f.name}(${params}) external${mut}${returns};`);
|
|
184
275
|
}
|
|
@@ -205,7 +296,8 @@ function isValueType(type) {
|
|
|
205
296
|
}
|
|
206
297
|
function generateVariable(v) {
|
|
207
298
|
const type = generateType(v.type);
|
|
208
|
-
const
|
|
299
|
+
const isReadonlyArray = v.immutable && v.type.kind === SkittlesTypeKind.Array;
|
|
300
|
+
const vis = isReadonlyArray ? "internal" : mapVisibility(v.visibility);
|
|
209
301
|
let modifier = "";
|
|
210
302
|
if (v.constant) {
|
|
211
303
|
modifier = " constant";
|
|
@@ -223,6 +315,16 @@ function generateVariable(v) {
|
|
|
223
315
|
}
|
|
224
316
|
return `${type} ${vis}${modifier}${overrideStr} ${v.name};`;
|
|
225
317
|
}
|
|
318
|
+
function generateReadonlyArrayGetter(v) {
|
|
319
|
+
const type = generateType(v.type);
|
|
320
|
+
const name = v.name;
|
|
321
|
+
const getterName = `get${name.charAt(0).toUpperCase()}${name.slice(1)}`;
|
|
322
|
+
const lines = [];
|
|
323
|
+
lines.push(` function ${getterName}() public view returns (${type} memory) {`);
|
|
324
|
+
lines.push(` return ${name};`);
|
|
325
|
+
lines.push(" }");
|
|
326
|
+
return lines.join("\n");
|
|
327
|
+
}
|
|
226
328
|
function generateFunction(f) {
|
|
227
329
|
if (f.name === "receive") {
|
|
228
330
|
const lines = [];
|
|
@@ -250,23 +352,73 @@ function generateFunction(f) {
|
|
|
250
352
|
const virtOverride = f.isOverride ? " override" : f.isVirtual ? " virtual" : "";
|
|
251
353
|
let returns = "";
|
|
252
354
|
if (f.returnType && f.returnType.kind !== SkittlesTypeKind.Void) {
|
|
253
|
-
|
|
355
|
+
if (f.returnType.kind === SkittlesTypeKind.Tuple) {
|
|
356
|
+
const tupleParams = (f.returnType.tupleTypes ?? []).map(generateParamType).join(", ");
|
|
357
|
+
returns = ` returns (${tupleParams})`;
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
returns = ` returns (${generateParamType(f.returnType)})`;
|
|
361
|
+
}
|
|
254
362
|
}
|
|
255
363
|
const lines = [];
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
364
|
+
if (f.isAbstract) {
|
|
365
|
+
lines.push(` function ${f.name}(${params}) ${vis}${mut}${virtOverride}${returns};`);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
lines.push(` function ${f.name}(${params}) ${vis}${mut}${virtOverride}${returns} {`);
|
|
369
|
+
for (const s of f.body) {
|
|
370
|
+
lines.push(generateStatement(s, " "));
|
|
371
|
+
}
|
|
372
|
+
lines.push(" }");
|
|
259
373
|
}
|
|
260
|
-
lines.push(" }");
|
|
261
374
|
return lines.join("\n");
|
|
262
375
|
}
|
|
263
|
-
function
|
|
264
|
-
|
|
376
|
+
function isSuperCall(stmt) {
|
|
377
|
+
return (stmt.kind === "expression" &&
|
|
378
|
+
stmt.expression.kind === "call" &&
|
|
379
|
+
stmt.expression.callee.kind === "identifier" &&
|
|
380
|
+
stmt.expression.callee.name === "super");
|
|
381
|
+
}
|
|
382
|
+
function getSuperCallArgs(stmt) {
|
|
383
|
+
if (stmt.kind === "expression" &&
|
|
384
|
+
stmt.expression.kind === "call" &&
|
|
385
|
+
stmt.expression.callee.kind === "identifier" &&
|
|
386
|
+
stmt.expression.callee.name === "super") {
|
|
387
|
+
return stmt.expression.args;
|
|
388
|
+
}
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
function generateConstructor(c, inherits = []) {
|
|
392
|
+
const regularParams = c.parameters.filter((p) => !p.defaultValue);
|
|
393
|
+
const defaultParams = c.parameters.filter((p) => p.defaultValue);
|
|
394
|
+
const params = regularParams
|
|
265
395
|
.map((p) => `${generateParamType(p.type)} ${p.name}`)
|
|
266
396
|
.join(", ");
|
|
397
|
+
// Extract super() call(s) from the body and validate.
|
|
398
|
+
const superCalls = c.body.filter(isSuperCall);
|
|
399
|
+
if (superCalls.length > 1) {
|
|
400
|
+
throw new Error("Constructor contains multiple super() calls, but only one is allowed");
|
|
401
|
+
}
|
|
402
|
+
const bodyWithoutSuper = c.body.filter((s) => !isSuperCall(s));
|
|
403
|
+
let parentModifier = "";
|
|
404
|
+
if (superCalls.length === 1) {
|
|
405
|
+
const args = getSuperCallArgs(superCalls[0]);
|
|
406
|
+
if (args.length > 0) {
|
|
407
|
+
if (inherits.length === 0) {
|
|
408
|
+
throw new Error("Constructor contains a super(...) call, but no parent contract is specified in 'inherits'");
|
|
409
|
+
}
|
|
410
|
+
if (defaultParams.length > 0) {
|
|
411
|
+
throw new Error("super(...) with constructor parameters that have default values is not supported");
|
|
412
|
+
}
|
|
413
|
+
parentModifier = ` ${inherits[0]}(${args.map(generateExpression).join(", ")})`;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
267
416
|
const lines = [];
|
|
268
|
-
lines.push(` constructor(${params}) {`);
|
|
269
|
-
for (const
|
|
417
|
+
lines.push(` constructor(${params})${parentModifier} {`);
|
|
418
|
+
for (const p of defaultParams) {
|
|
419
|
+
lines.push(` ${generateParamType(p.type)} ${p.name} = ${generateExpression(p.defaultValue)};`);
|
|
420
|
+
}
|
|
421
|
+
for (const s of bodyWithoutSuper) {
|
|
270
422
|
lines.push(generateStatement(s, " "));
|
|
271
423
|
}
|
|
272
424
|
lines.push(" }");
|
|
@@ -301,6 +453,8 @@ export function generateType(type) {
|
|
|
301
453
|
return type.structName ?? "UnknownInterface";
|
|
302
454
|
case SkittlesTypeKind.Enum:
|
|
303
455
|
return type.structName ?? "UnknownEnum";
|
|
456
|
+
case SkittlesTypeKind.Tuple:
|
|
457
|
+
return `(${(type.tupleTypes ?? []).map(generateType).join(", ")})`;
|
|
304
458
|
case SkittlesTypeKind.Void:
|
|
305
459
|
return "";
|
|
306
460
|
default:
|
|
@@ -382,6 +536,16 @@ export function generateExpression(expr) {
|
|
|
382
536
|
const callResult = tryGenerateBuiltinCall(expr);
|
|
383
537
|
if (callResult)
|
|
384
538
|
return callResult;
|
|
539
|
+
// addr.transfer(amount) → payable(addr).transfer(amount)
|
|
540
|
+
// Exclude this.transfer(...) (internal call) and this.stateVar.transfer(...)
|
|
541
|
+
// (external contract interface call) to avoid misclassifying non-ETH transfers.
|
|
542
|
+
if (expr.callee.kind === "property-access" &&
|
|
543
|
+
expr.callee.property === "transfer" &&
|
|
544
|
+
expr.args.length === 1 &&
|
|
545
|
+
!isThisOrContractCall(expr.callee.object)) {
|
|
546
|
+
const addr = generateExpression(expr.callee.object);
|
|
547
|
+
return `payable(${addr}).transfer(${generateExpression(expr.args[0])})`;
|
|
548
|
+
}
|
|
385
549
|
return `${generateExpression(expr.callee)}(${expr.args.map(generateExpression).join(", ")})`;
|
|
386
550
|
}
|
|
387
551
|
case "conditional":
|
|
@@ -392,6 +556,8 @@ export function generateExpression(expr) {
|
|
|
392
556
|
const values = expr.properties.map((p) => generateExpression(p.value)).join(", ");
|
|
393
557
|
return values;
|
|
394
558
|
}
|
|
559
|
+
case "tuple-literal":
|
|
560
|
+
return `(${expr.elements.map(generateExpression).join(", ")})`;
|
|
395
561
|
default:
|
|
396
562
|
return "/* unsupported */";
|
|
397
563
|
}
|
|
@@ -532,6 +698,26 @@ export function generateStatement(stmt, indent) {
|
|
|
532
698
|
lines.push(`${indent}}`);
|
|
533
699
|
return lines.join("\n");
|
|
534
700
|
}
|
|
701
|
+
case "try-catch": {
|
|
702
|
+
const lines = [];
|
|
703
|
+
const callExpr = generateExpression(stmt.call);
|
|
704
|
+
let returns = "";
|
|
705
|
+
if (stmt.returnVarName && stmt.returnType && stmt.returnType.kind !== SkittlesTypeKind.Void) {
|
|
706
|
+
returns = ` returns (${generateType(stmt.returnType)} ${stmt.returnVarName})`;
|
|
707
|
+
}
|
|
708
|
+
lines.push(`${indent}try ${callExpr}${returns} {`);
|
|
709
|
+
for (const s of stmt.successBody) {
|
|
710
|
+
lines.push(generateStatement(s, inner));
|
|
711
|
+
}
|
|
712
|
+
lines.push(`${indent}} catch {`);
|
|
713
|
+
for (const s of stmt.catchBody) {
|
|
714
|
+
lines.push(generateStatement(s, inner));
|
|
715
|
+
}
|
|
716
|
+
lines.push(`${indent}}`);
|
|
717
|
+
return lines.join("\n");
|
|
718
|
+
}
|
|
719
|
+
case "console-log":
|
|
720
|
+
return `${indent}console.log(${stmt.args.map(generateExpression).join(", ")});`;
|
|
535
721
|
default:
|
|
536
722
|
return `${indent}// unsupported statement`;
|
|
537
723
|
}
|
|
@@ -570,6 +756,16 @@ function negateOperator(op) {
|
|
|
570
756
|
};
|
|
571
757
|
return map[op] ?? null;
|
|
572
758
|
}
|
|
759
|
+
/**
|
|
760
|
+
* Check if a receiver expression is `this` (an internal contract method call).
|
|
761
|
+
* Used to avoid wrapping `this.transfer(...)` calls in `payable(...)`.
|
|
762
|
+
* Note: `this.stateVar.transfer(amount)` is NOT excluded here because
|
|
763
|
+
* codegen strips `this.` and the arg count (1 for ETH transfer vs 2+ for
|
|
764
|
+
* contract interface calls) serves as the discriminator.
|
|
765
|
+
*/
|
|
766
|
+
function isThisOrContractCall(receiver) {
|
|
767
|
+
return receiver.kind === "identifier" && receiver.name === "this";
|
|
768
|
+
}
|
|
573
769
|
// ============================================================
|
|
574
770
|
// Built-in function recognition
|
|
575
771
|
// ============================================================
|
|
@@ -597,8 +793,13 @@ function tryGenerateBuiltinCall(expr) {
|
|
|
597
793
|
return `abi.encode(${args})`;
|
|
598
794
|
case "abi.encodePacked":
|
|
599
795
|
return `abi.encodePacked(${args})`;
|
|
600
|
-
case "abi.decode":
|
|
796
|
+
case "abi.decode": {
|
|
797
|
+
if (expr.typeArgs && expr.typeArgs.length > 0) {
|
|
798
|
+
const types = expr.typeArgs.map(generateType).join(", ");
|
|
799
|
+
return `abi.decode(${args}, (${types}))`;
|
|
800
|
+
}
|
|
601
801
|
return `abi.decode(${args})`;
|
|
802
|
+
}
|
|
602
803
|
case "ecrecover":
|
|
603
804
|
return `ecrecover(${args})`;
|
|
604
805
|
case "addmod":
|
|
@@ -609,6 +810,11 @@ function tryGenerateBuiltinCall(expr) {
|
|
|
609
810
|
return `assert(${args})`;
|
|
610
811
|
case "gasleft":
|
|
611
812
|
return `gasleft()`;
|
|
813
|
+
case "Contract":
|
|
814
|
+
if (expr.typeArgs && expr.typeArgs.length > 0 && expr.typeArgs[0].kind === SkittlesTypeKind.ContractInterface && expr.typeArgs[0].structName) {
|
|
815
|
+
return `${expr.typeArgs[0].structName}(${args})`;
|
|
816
|
+
}
|
|
817
|
+
throw new Error("Contract<T>() requires a contract interface type argument, e.g. Contract<IToken>(address)");
|
|
612
818
|
case "string.concat":
|
|
613
819
|
return `string.concat(${args})`;
|
|
614
820
|
case "bytes.concat":
|
|
@@ -618,6 +824,47 @@ function tryGenerateBuiltinCall(expr) {
|
|
|
618
824
|
}
|
|
619
825
|
}
|
|
620
826
|
// ============================================================
|
|
827
|
+
// Console.log detection
|
|
828
|
+
// ============================================================
|
|
829
|
+
function statementsUseConsoleLog(stmts) {
|
|
830
|
+
for (const stmt of stmts) {
|
|
831
|
+
if (stmt.kind === "console-log")
|
|
832
|
+
return true;
|
|
833
|
+
if (stmt.kind === "if") {
|
|
834
|
+
if (statementsUseConsoleLog(stmt.thenBody))
|
|
835
|
+
return true;
|
|
836
|
+
if (stmt.elseBody && statementsUseConsoleLog(stmt.elseBody))
|
|
837
|
+
return true;
|
|
838
|
+
}
|
|
839
|
+
if (stmt.kind === "for" || stmt.kind === "while" || stmt.kind === "do-while") {
|
|
840
|
+
if (statementsUseConsoleLog(stmt.body))
|
|
841
|
+
return true;
|
|
842
|
+
}
|
|
843
|
+
if (stmt.kind === "switch") {
|
|
844
|
+
for (const c of stmt.cases) {
|
|
845
|
+
if (statementsUseConsoleLog(c.body))
|
|
846
|
+
return true;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (stmt.kind === "try-catch") {
|
|
850
|
+
if (statementsUseConsoleLog(stmt.successBody))
|
|
851
|
+
return true;
|
|
852
|
+
if (statementsUseConsoleLog(stmt.catchBody))
|
|
853
|
+
return true;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
function contractUsesConsoleLog(contract) {
|
|
859
|
+
for (const f of contract.functions) {
|
|
860
|
+
if (statementsUseConsoleLog(f.body))
|
|
861
|
+
return true;
|
|
862
|
+
}
|
|
863
|
+
if (contract.ctor && statementsUseConsoleLog(contract.ctor.body))
|
|
864
|
+
return true;
|
|
865
|
+
return false;
|
|
866
|
+
}
|
|
867
|
+
// ============================================================
|
|
621
868
|
// Visibility mapping (Skittles simplification)
|
|
622
869
|
// ============================================================
|
|
623
870
|
function mapVisibility(vis) {
|
|
@@ -625,4 +872,124 @@ function mapVisibility(vis) {
|
|
|
625
872
|
return "public";
|
|
626
873
|
return "internal";
|
|
627
874
|
}
|
|
875
|
+
// ============================================================
|
|
876
|
+
// Source map generation
|
|
877
|
+
// ============================================================
|
|
878
|
+
/**
|
|
879
|
+
* Build a source map that maps generated Solidity line numbers
|
|
880
|
+
* back to TypeScript source line numbers.
|
|
881
|
+
*
|
|
882
|
+
* The mapping is built by walking the IR (which has source line info
|
|
883
|
+
* from the parser) and counting lines in the generated Solidity output
|
|
884
|
+
* to correlate each Solidity line with its TypeScript origin.
|
|
885
|
+
*/
|
|
886
|
+
export function buildSourceMap(solidity, contracts, sourceFile) {
|
|
887
|
+
const solLines = solidity.split("\n");
|
|
888
|
+
const mappings = {};
|
|
889
|
+
let lineIdx = 0; // 0-based index into solLines
|
|
890
|
+
// Helper: find the next line matching a test, starting from lineIdx
|
|
891
|
+
function findLine(test) {
|
|
892
|
+
for (let i = lineIdx; i < solLines.length; i++) {
|
|
893
|
+
if (test(solLines[i])) {
|
|
894
|
+
lineIdx = i;
|
|
895
|
+
return i;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return -1;
|
|
899
|
+
}
|
|
900
|
+
function addMapping(solLineIdx, tsLine) {
|
|
901
|
+
if (tsLine !== undefined && solLineIdx >= 0) {
|
|
902
|
+
mappings[solLineIdx + 1] = tsLine; // convert to 1-based
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Map function/constructor body statements to Solidity lines.
|
|
907
|
+
* Walks statements in order, using generateStatement to count lines
|
|
908
|
+
* for each statement so we know exactly where each one appears.
|
|
909
|
+
*/
|
|
910
|
+
function mapBodyStatements(body, startLineIdx, indent) {
|
|
911
|
+
let currentIdx = startLineIdx;
|
|
912
|
+
for (const stmt of body) {
|
|
913
|
+
addMapping(currentIdx, stmt.sourceLine);
|
|
914
|
+
const stmtText = generateStatement(stmt, indent);
|
|
915
|
+
const stmtLineCount = stmtText.split("\n").length;
|
|
916
|
+
// Recurse into compound statement bodies
|
|
917
|
+
if (stmt.kind === "if" && !isRequirePattern(stmt)) {
|
|
918
|
+
// Line 0: if (cond) {
|
|
919
|
+
mapBodyStatements(stmt.thenBody, currentIdx + 1, indent + " ");
|
|
920
|
+
if (stmt.elseBody) {
|
|
921
|
+
const thenLineCount = stmt.thenBody.reduce((sum, s) => sum + generateStatement(s, indent + " ").split("\n").length, 0);
|
|
922
|
+
// } else { is at currentIdx + 1 + thenLineCount
|
|
923
|
+
mapBodyStatements(stmt.elseBody, currentIdx + 1 + thenLineCount + 1, indent + " ");
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
else if (stmt.kind === "for" || stmt.kind === "while") {
|
|
927
|
+
mapBodyStatements(stmt.body, currentIdx + 1, indent + " ");
|
|
928
|
+
}
|
|
929
|
+
else if (stmt.kind === "do-while") {
|
|
930
|
+
mapBodyStatements(stmt.body, currentIdx + 1, indent + " ");
|
|
931
|
+
}
|
|
932
|
+
currentIdx += stmtLineCount;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
for (const contract of contracts) {
|
|
936
|
+
// Find the contract declaration line
|
|
937
|
+
const contractIdx = findLine((l) => {
|
|
938
|
+
const trimmed = l.trimStart();
|
|
939
|
+
return trimmed.startsWith(`contract ${contract.name}`) || trimmed.startsWith(`abstract contract ${contract.name}`);
|
|
940
|
+
});
|
|
941
|
+
if (contractIdx === -1)
|
|
942
|
+
continue;
|
|
943
|
+
addMapping(contractIdx, contract.sourceLine);
|
|
944
|
+
lineIdx = contractIdx + 1;
|
|
945
|
+
// Map events
|
|
946
|
+
for (const e of contract.events) {
|
|
947
|
+
const idx = findLine((l) => {
|
|
948
|
+
const trimmed = l.trim();
|
|
949
|
+
return trimmed.startsWith(`event ${e.name}(`);
|
|
950
|
+
});
|
|
951
|
+
if (idx !== -1) {
|
|
952
|
+
addMapping(idx, e.sourceLine);
|
|
953
|
+
lineIdx = idx + 1;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
// Map variables
|
|
957
|
+
for (const v of contract.variables) {
|
|
958
|
+
const idx = findLine((l) => {
|
|
959
|
+
const trimmed = l.trim();
|
|
960
|
+
return trimmed.includes(` ${v.name}`) && trimmed.endsWith(";");
|
|
961
|
+
});
|
|
962
|
+
if (idx !== -1) {
|
|
963
|
+
addMapping(idx, v.sourceLine);
|
|
964
|
+
lineIdx = idx + 1;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
// Map constructor
|
|
968
|
+
if (contract.ctor) {
|
|
969
|
+
const ctorIdx = findLine((l) => l.trim().startsWith("constructor("));
|
|
970
|
+
if (ctorIdx !== -1) {
|
|
971
|
+
addMapping(ctorIdx, contract.ctor.sourceLine);
|
|
972
|
+
lineIdx = ctorIdx + 1;
|
|
973
|
+
mapBodyStatements(contract.ctor.body, lineIdx, " ");
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
// Map functions
|
|
977
|
+
for (const f of contract.functions) {
|
|
978
|
+
const funcIdx = findLine((l) => {
|
|
979
|
+
const trimmed = l.trim();
|
|
980
|
+
if (f.name === "receive")
|
|
981
|
+
return trimmed.startsWith("receive()");
|
|
982
|
+
if (f.name === "fallback")
|
|
983
|
+
return trimmed.startsWith("fallback()");
|
|
984
|
+
return trimmed.startsWith(`function ${f.name}(`);
|
|
985
|
+
});
|
|
986
|
+
if (funcIdx !== -1) {
|
|
987
|
+
addMapping(funcIdx, f.sourceLine);
|
|
988
|
+
lineIdx = funcIdx + 1;
|
|
989
|
+
mapBodyStatements(f.body, lineIdx, " ");
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return { sourceFile, mappings };
|
|
994
|
+
}
|
|
628
995
|
//# sourceMappingURL=codegen.js.map
|