midnight-mcp 0.1.18 → 0.1.20
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.
|
@@ -358,7 +358,7 @@ export const repositoryTools = [
|
|
|
358
358
|
// ============================================================================
|
|
359
359
|
{
|
|
360
360
|
name: "midnight-validate-contract",
|
|
361
|
-
description: "🔍
|
|
361
|
+
description: "🔍 PRIMARY VERIFICATION: Compile and validate a Compact contract using the ACTUAL Compact compiler. **ALWAYS use this tool first** when verifying contract code - it catches real compilation errors that static analysis cannot detect. Returns detailed error messages with line numbers, suggested fixes, and actionable guidance. If the compiler is not installed, it will provide installation instructions. Accepts either source code directly OR a file path to a .compact file. For contracts you generate, ALWAYS validate with this tool before presenting to the user.",
|
|
362
362
|
inputSchema: {
|
|
363
363
|
type: "object",
|
|
364
364
|
properties: {
|
|
@@ -454,7 +454,7 @@ export const repositoryTools = [
|
|
|
454
454
|
},
|
|
455
455
|
{
|
|
456
456
|
name: "midnight-extract-contract-structure",
|
|
457
|
-
description: "📋 ANALYSIS
|
|
457
|
+
description: "📋 STATIC ANALYSIS (not verification): Extract the structure of a Compact contract and detect common issues. Returns circuits, witnesses, ledger items, types, structs, enums, and **potentialIssues** (module-level const, stdlib collisions, sealed+export conflicts). **WARNING: This does NOT verify compilation** - use 'midnight-validate-contract' for actual compiler verification. Use this tool for: (1) understanding contract structure, (2) quick pre-checks before compilation, (3) fallback analysis when compiler unavailable. Do NOT claim a contract 'compiles correctly' based only on this tool.",
|
|
458
458
|
inputSchema: {
|
|
459
459
|
type: "object",
|
|
460
460
|
properties: {
|
|
@@ -529,6 +529,26 @@ export const repositoryTools = [
|
|
|
529
529
|
type: "object",
|
|
530
530
|
description: "Counts of each type of definition",
|
|
531
531
|
},
|
|
532
|
+
potentialIssues: {
|
|
533
|
+
type: "array",
|
|
534
|
+
description: "Common issues detected by static analysis (NOT exhaustive - use validate_contract for real verification)",
|
|
535
|
+
items: {
|
|
536
|
+
type: "object",
|
|
537
|
+
properties: {
|
|
538
|
+
type: {
|
|
539
|
+
type: "string",
|
|
540
|
+
description: "Issue type: module_level_const, stdlib_name_collision, sealed_export_conflict, missing_constructor, stdlib_type_mismatch",
|
|
541
|
+
},
|
|
542
|
+
line: { type: "number" },
|
|
543
|
+
message: { type: "string" },
|
|
544
|
+
suggestion: { type: "string" },
|
|
545
|
+
severity: {
|
|
546
|
+
type: "string",
|
|
547
|
+
enum: ["error", "warning"],
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
},
|
|
532
552
|
summary: { type: "string" },
|
|
533
553
|
message: { type: "string" },
|
|
534
554
|
},
|
|
@@ -549,6 +549,7 @@ export declare function extractContractStructure(input: ExtractContractStructure
|
|
|
549
549
|
structure?: undefined;
|
|
550
550
|
exports?: undefined;
|
|
551
551
|
stats?: undefined;
|
|
552
|
+
potentialIssues?: undefined;
|
|
552
553
|
summary?: undefined;
|
|
553
554
|
} | {
|
|
554
555
|
success: boolean;
|
|
@@ -561,6 +562,7 @@ export declare function extractContractStructure(input: ExtractContractStructure
|
|
|
561
562
|
structure?: undefined;
|
|
562
563
|
exports?: undefined;
|
|
563
564
|
stats?: undefined;
|
|
565
|
+
potentialIssues?: undefined;
|
|
564
566
|
summary?: undefined;
|
|
565
567
|
} | {
|
|
566
568
|
success: boolean;
|
|
@@ -620,6 +622,13 @@ export declare function extractContractStructure(input: ExtractContractStructure
|
|
|
620
622
|
exportedWitnesses: number;
|
|
621
623
|
exportedLedger: number;
|
|
622
624
|
};
|
|
625
|
+
potentialIssues: {
|
|
626
|
+
type: string;
|
|
627
|
+
line?: number;
|
|
628
|
+
message: string;
|
|
629
|
+
suggestion: string;
|
|
630
|
+
severity: "error" | "warning";
|
|
631
|
+
}[] | undefined;
|
|
623
632
|
summary: string;
|
|
624
633
|
message: string;
|
|
625
634
|
error?: undefined;
|
|
@@ -1156,6 +1156,144 @@ export async function extractContractStructure(input) {
|
|
|
1156
1156
|
witnesses: witnesses.filter((w) => w.isExport).map((w) => w.name),
|
|
1157
1157
|
ledger: ledgerItems.filter((l) => l.isExport).map((l) => l.name),
|
|
1158
1158
|
};
|
|
1159
|
+
// ============================================================================
|
|
1160
|
+
// PRE-COMPILATION ISSUE DETECTION
|
|
1161
|
+
// Catch common mistakes before hitting the compiler
|
|
1162
|
+
// ============================================================================
|
|
1163
|
+
const potentialIssues = [];
|
|
1164
|
+
// Known CompactStandardLibrary exports that shouldn't be redefined
|
|
1165
|
+
const stdlibExports = [
|
|
1166
|
+
"burnAddress",
|
|
1167
|
+
"ownPublicKey",
|
|
1168
|
+
"contractAddress",
|
|
1169
|
+
"default",
|
|
1170
|
+
"disclose",
|
|
1171
|
+
"assert",
|
|
1172
|
+
"pad",
|
|
1173
|
+
"unpad",
|
|
1174
|
+
"Counter",
|
|
1175
|
+
"Map",
|
|
1176
|
+
"Set",
|
|
1177
|
+
"MerkleTree",
|
|
1178
|
+
"Opaque",
|
|
1179
|
+
"Vector",
|
|
1180
|
+
];
|
|
1181
|
+
// 1. Detect module-level const (not supported in Compact)
|
|
1182
|
+
const constPattern = /^const\s+(\w+)\s*:/gm;
|
|
1183
|
+
let constMatch;
|
|
1184
|
+
while ((constMatch = constPattern.exec(code)) !== null) {
|
|
1185
|
+
// Check if this const is inside a circuit block by looking for preceding circuit/constructor
|
|
1186
|
+
const beforeConst = code.substring(0, constMatch.index);
|
|
1187
|
+
const lastCircuitStart = Math.max(beforeConst.lastIndexOf("circuit "), beforeConst.lastIndexOf("constructor {"));
|
|
1188
|
+
const lastCloseBrace = beforeConst.lastIndexOf("}");
|
|
1189
|
+
// If no circuit before, or the last } is after the last circuit start, it's module-level
|
|
1190
|
+
if (lastCircuitStart === -1 || lastCloseBrace > lastCircuitStart) {
|
|
1191
|
+
const lineNum = lineByIndex[constMatch.index] || 1;
|
|
1192
|
+
potentialIssues.push({
|
|
1193
|
+
type: "module_level_const",
|
|
1194
|
+
line: lineNum,
|
|
1195
|
+
message: `Module-level 'const ${constMatch[1]}' is not supported in Compact`,
|
|
1196
|
+
suggestion: `Use 'pure circuit ${constMatch[1]}(): <type> { return <value>; }' instead`,
|
|
1197
|
+
severity: "error",
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
// 2. Detect standard library name collisions
|
|
1202
|
+
const hasStdlibImport = imports.includes("CompactStandardLibrary") ||
|
|
1203
|
+
code.includes('include "std"');
|
|
1204
|
+
if (hasStdlibImport) {
|
|
1205
|
+
// Check circuits for name collisions
|
|
1206
|
+
for (const circuit of circuits) {
|
|
1207
|
+
if (stdlibExports.includes(circuit.name)) {
|
|
1208
|
+
potentialIssues.push({
|
|
1209
|
+
type: "stdlib_name_collision",
|
|
1210
|
+
line: circuit.line,
|
|
1211
|
+
message: `Circuit '${circuit.name}' conflicts with CompactStandardLibrary.${circuit.name}()`,
|
|
1212
|
+
suggestion: `Rename to avoid ambiguity, or remove to use the standard library version`,
|
|
1213
|
+
severity: "error",
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
// 3. Detect sealed + export conflicts
|
|
1219
|
+
const sealedFields = [];
|
|
1220
|
+
const sealedPattern = /sealed\s+ledger\s+(\w+)\s*:/g;
|
|
1221
|
+
let sealedMatch;
|
|
1222
|
+
while ((sealedMatch = sealedPattern.exec(code)) !== null) {
|
|
1223
|
+
const lineNum = lineByIndex[sealedMatch.index] || 1;
|
|
1224
|
+
sealedFields.push({ name: sealedMatch[1], line: lineNum });
|
|
1225
|
+
}
|
|
1226
|
+
if (sealedFields.length > 0) {
|
|
1227
|
+
// Check if any exported circuit writes to sealed fields
|
|
1228
|
+
for (const circuit of circuits) {
|
|
1229
|
+
if (circuit.isExport) {
|
|
1230
|
+
// Find the circuit body and check for assignments to sealed fields
|
|
1231
|
+
const circuitBodyMatch = code.match(new RegExp(`(?:export\\s+)?circuit\\s+${circuit.name}\\s*\\([^)]*\\)\\s*:[^{]*\\{([\\s\\S]*?)\\n\\}`, "m"));
|
|
1232
|
+
if (circuitBodyMatch) {
|
|
1233
|
+
const body = circuitBodyMatch[1];
|
|
1234
|
+
for (const field of sealedFields) {
|
|
1235
|
+
// Check for assignment patterns: fieldName = or fieldName.method(
|
|
1236
|
+
if (new RegExp(`\\b${field.name}\\s*=`).test(body) ||
|
|
1237
|
+
new RegExp(`\\b${field.name}\\s*\\.\\s*\\w+\\s*\\(`).test(body)) {
|
|
1238
|
+
potentialIssues.push({
|
|
1239
|
+
type: "sealed_export_conflict",
|
|
1240
|
+
line: circuit.line,
|
|
1241
|
+
message: `Exported circuit '${circuit.name}' modifies sealed field '${field.name}'`,
|
|
1242
|
+
suggestion: `Move sealed field initialization to a 'constructor { }' block instead`,
|
|
1243
|
+
severity: "error",
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
// 4. Detect missing constructor when sealed fields exist but no constructor
|
|
1252
|
+
if (sealedFields.length > 0) {
|
|
1253
|
+
const hasConstructor = /constructor\s*\{/.test(code);
|
|
1254
|
+
if (!hasConstructor) {
|
|
1255
|
+
// Check if there's an initialize-like circuit trying to set sealed fields
|
|
1256
|
+
const initCircuit = circuits.find((c) => c.name.toLowerCase().includes("init") ||
|
|
1257
|
+
c.name.toLowerCase() === "setup");
|
|
1258
|
+
if (initCircuit && initCircuit.isExport) {
|
|
1259
|
+
potentialIssues.push({
|
|
1260
|
+
type: "missing_constructor",
|
|
1261
|
+
line: initCircuit.line,
|
|
1262
|
+
message: `Contract has sealed fields but uses '${initCircuit.name}' instead of constructor`,
|
|
1263
|
+
suggestion: `Sealed fields must be initialized in 'constructor { }', not in exported circuits`,
|
|
1264
|
+
severity: "warning",
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
// 5. Detect potential type mismatches with stdlib functions
|
|
1270
|
+
if (hasStdlibImport) {
|
|
1271
|
+
// Check for burnAddress() used where ZswapCoinPublicKey is expected
|
|
1272
|
+
// burnAddress() returns Either<ZswapCoinPublicKey, ContractAddress>
|
|
1273
|
+
const burnAddressUsages = code.matchAll(/burnAddress\s*\(\s*\)/g);
|
|
1274
|
+
for (const usage of burnAddressUsages) {
|
|
1275
|
+
// Check if it's being passed to a function or assigned
|
|
1276
|
+
const afterUsage = code.substring(usage.index + usage[0].length, usage.index + usage[0].length + 50);
|
|
1277
|
+
const beforeUsage = code.substring(Math.max(0, usage.index - 100), usage.index);
|
|
1278
|
+
// If used in a context expecting ZswapCoinPublicKey (not .left or .right access)
|
|
1279
|
+
if (!afterUsage.startsWith(".left") &&
|
|
1280
|
+
!afterUsage.startsWith(".right") &&
|
|
1281
|
+
!afterUsage.startsWith(".is_left")) {
|
|
1282
|
+
// Check if it's in a function call or assignment that likely expects ZswapCoinPublicKey
|
|
1283
|
+
if (/\(\s*$/.test(beforeUsage) || /,\s*$/.test(beforeUsage)) {
|
|
1284
|
+
const lineNum = lineByIndex[usage.index] || 1;
|
|
1285
|
+
potentialIssues.push({
|
|
1286
|
+
type: "stdlib_type_mismatch",
|
|
1287
|
+
line: lineNum,
|
|
1288
|
+
message: `burnAddress() returns Either<ZswapCoinPublicKey, ContractAddress>, not ZswapCoinPublicKey`,
|
|
1289
|
+
suggestion: `Use burnAddress().left for ZswapCoinPublicKey, or define 'pure circuit zeroKey(): ZswapCoinPublicKey { return default<ZswapCoinPublicKey>; }'`,
|
|
1290
|
+
severity: "warning",
|
|
1291
|
+
});
|
|
1292
|
+
break; // Only warn once
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1159
1297
|
const summary = [];
|
|
1160
1298
|
if (circuits.length > 0) {
|
|
1161
1299
|
summary.push(`${circuits.length} circuit(s)`);
|
|
@@ -1201,8 +1339,11 @@ export async function extractContractStructure(input) {
|
|
|
1201
1339
|
exportedWitnesses: exports.witnesses.length,
|
|
1202
1340
|
exportedLedger: exports.ledger.length,
|
|
1203
1341
|
},
|
|
1342
|
+
potentialIssues: potentialIssues.length > 0 ? potentialIssues : undefined,
|
|
1204
1343
|
summary: summary.length > 0 ? summary.join(", ") : "Empty contract",
|
|
1205
|
-
message:
|
|
1344
|
+
message: potentialIssues.length > 0
|
|
1345
|
+
? `⚠️ Found ${potentialIssues.length} potential issue(s). Contract contains: ${summary.join(", ") || "no definitions found"}`
|
|
1346
|
+
: `📋 Contract contains: ${summary.join(", ") || "no definitions found"}`,
|
|
1206
1347
|
};
|
|
1207
1348
|
}
|
|
1208
1349
|
//# sourceMappingURL=validation.js.map
|