midnight-mcp 0.2.8 → 0.2.10
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 +18 -11
- package/dist/bin.js +3 -3
- package/dist/{chunk-QPQOD3S6.js → chunk-7KJSVMFI.js} +1016 -478
- package/dist/{chunk-VQTI7YUU.js → chunk-C23TNY65.js} +36 -9
- package/dist/db-BCMNMI6F.js +7 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/dist/db-7F5YOZB7.js +0 -7
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
validateNumber,
|
|
26
26
|
validateQuery,
|
|
27
27
|
vectorStore
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-C23TNY65.js";
|
|
29
29
|
|
|
30
30
|
// src/tools/search/schemas.ts
|
|
31
31
|
import { z } from "zod";
|
|
@@ -102,7 +102,49 @@ async function tryHostedSearch(searchType, hostedSearchFn, cacheKey, warnings) {
|
|
|
102
102
|
...response,
|
|
103
103
|
...warnings.length > 0 && { warnings }
|
|
104
104
|
};
|
|
105
|
-
searchCache.set(cacheKey,
|
|
105
|
+
searchCache.set(cacheKey, {
|
|
106
|
+
results: finalResponse.results.map((item) => {
|
|
107
|
+
const result = item;
|
|
108
|
+
let startLine;
|
|
109
|
+
let endLine;
|
|
110
|
+
if (result.source.lines) {
|
|
111
|
+
const lineParts = result.source.lines.split("-");
|
|
112
|
+
if (lineParts.length === 2) {
|
|
113
|
+
const parsedStart = parseInt(lineParts[0], 10);
|
|
114
|
+
const parsedEnd = parseInt(lineParts[1], 10);
|
|
115
|
+
if (!Number.isNaN(parsedStart)) {
|
|
116
|
+
startLine = parsedStart;
|
|
117
|
+
}
|
|
118
|
+
if (!Number.isNaN(parsedEnd)) {
|
|
119
|
+
endLine = parsedEnd;
|
|
120
|
+
}
|
|
121
|
+
} else if (lineParts.length === 1) {
|
|
122
|
+
const parsed = parseInt(lineParts[0], 10);
|
|
123
|
+
if (!Number.isNaN(parsed)) {
|
|
124
|
+
startLine = endLine = parsed;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
code: result.code,
|
|
130
|
+
content: result.content,
|
|
131
|
+
relevanceScore: result.relevanceScore,
|
|
132
|
+
source: {
|
|
133
|
+
repository: result.source.repository,
|
|
134
|
+
filePath: result.source.filePath,
|
|
135
|
+
startLine,
|
|
136
|
+
endLine,
|
|
137
|
+
lines: result.source.lines,
|
|
138
|
+
section: result.source.section
|
|
139
|
+
},
|
|
140
|
+
codeType: result.codeType,
|
|
141
|
+
name: result.name,
|
|
142
|
+
isExported: result.isExported
|
|
143
|
+
};
|
|
144
|
+
}),
|
|
145
|
+
totalResults: finalResponse.totalResults ?? finalResponse.results.length,
|
|
146
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
147
|
+
});
|
|
106
148
|
return {
|
|
107
149
|
result: finalResponse,
|
|
108
150
|
cached: true
|
|
@@ -122,7 +164,11 @@ function finalizeResponse(response, cacheKey, warnings) {
|
|
|
122
164
|
...response,
|
|
123
165
|
...warnings.length > 0 && { warnings }
|
|
124
166
|
};
|
|
125
|
-
searchCache.set(cacheKey,
|
|
167
|
+
searchCache.set(cacheKey, {
|
|
168
|
+
results: finalResponse.results,
|
|
169
|
+
totalResults: finalResponse.totalResults ?? finalResponse.results.length,
|
|
170
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
171
|
+
});
|
|
126
172
|
return finalResponse;
|
|
127
173
|
}
|
|
128
174
|
async function searchCompact(input) {
|
|
@@ -740,11 +786,11 @@ async function analyzeContract(input) {
|
|
|
740
786
|
});
|
|
741
787
|
}
|
|
742
788
|
}
|
|
743
|
-
if (!parsed.imports.includes("
|
|
789
|
+
if (!parsed.imports.includes("CompactStandardLibrary")) {
|
|
744
790
|
findings.push({
|
|
745
791
|
severity: "info",
|
|
746
792
|
message: "Standard library not imported",
|
|
747
|
-
suggestion:
|
|
793
|
+
suggestion: "Consider adding 'import CompactStandardLibrary;' for common utilities"
|
|
748
794
|
});
|
|
749
795
|
}
|
|
750
796
|
}
|
|
@@ -1584,13 +1630,15 @@ Always import the standard library:
|
|
|
1584
1630
|
import CompactStandardLibrary;
|
|
1585
1631
|
\`\`\`
|
|
1586
1632
|
|
|
1587
|
-
For
|
|
1633
|
+
For modular code, use module imports (not a separate \`include\` directive):
|
|
1588
1634
|
\`\`\`compact
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
include "circuits";
|
|
1635
|
+
import "path/to/module";
|
|
1636
|
+
import { SomeType } from "other/module";
|
|
1592
1637
|
\`\`\`
|
|
1593
1638
|
|
|
1639
|
+
> **Note:** The docs describe modules and \`import\` syntax, but do not document
|
|
1640
|
+
> a separate \`include\` directive. Use \`import\` for code organization.
|
|
1641
|
+
|
|
1594
1642
|
---
|
|
1595
1643
|
|
|
1596
1644
|
## 3. Ledger Declarations
|
|
@@ -1635,6 +1683,14 @@ ledger privateData: Field; // Private, not exported
|
|
|
1635
1683
|
| \`Uint<N>\` | Unsigned integer (N = 8, 16, 32, 64, 128, 256) | \`balance: Uint<64>\` |
|
|
1636
1684
|
| \`Uint<MIN..MAX>\` | Bounded unsigned integer | \`score: Uint<0..100>\` |
|
|
1637
1685
|
|
|
1686
|
+
**\u26A0\uFE0F Uint Type Equivalence:** \`Uint<N>\` (sized) and \`Uint<0..MAX>\` (bounded) are the **SAME type family**.
|
|
1687
|
+
\`Uint<N>\` is exactly equivalent to \`Uint<0..(2^N - 1)>\`:
|
|
1688
|
+
- \`Uint<8>\` = \`Uint<0..255>\` (2^8 - 1 = 255)
|
|
1689
|
+
- \`Uint<16>\` = \`Uint<0..65535>\` (2^16 - 1 = 65535)
|
|
1690
|
+
- \`Uint<64>\` = \`Uint<0..18446744073709551615>\`
|
|
1691
|
+
|
|
1692
|
+
These can be used interchangeably - they are **not** separate types.
|
|
1693
|
+
|
|
1638
1694
|
### Collection Types
|
|
1639
1695
|
| Type | Description | Example |
|
|
1640
1696
|
|------|-------------|---------|
|
|
@@ -1867,41 +1923,60 @@ export circuit check_broken(guess: Field): Boolean {
|
|
|
1867
1923
|
|
|
1868
1924
|
### Counter Operations
|
|
1869
1925
|
\`\`\`compact
|
|
1870
|
-
//
|
|
1871
|
-
counter.increment(1);
|
|
1872
|
-
counter.decrement(1);
|
|
1873
|
-
counter.
|
|
1874
|
-
|
|
1875
|
-
//
|
|
1876
|
-
|
|
1877
|
-
//
|
|
1926
|
+
// ALL Counter methods work in circuits:
|
|
1927
|
+
counter.increment(1); // Increase by amount (Uint<16>)
|
|
1928
|
+
counter.decrement(1); // Decrease by amount (Uint<16>)
|
|
1929
|
+
const val = counter.read(); // Get current value (returns Uint<64>)
|
|
1930
|
+
const low = counter.lessThan(100); // Compare with threshold (returns Boolean)
|
|
1931
|
+
counter.resetToDefault(); // Reset to zero
|
|
1932
|
+
|
|
1933
|
+
// \u26A0\uFE0F WRONG: counter.value() does NOT exist - use counter.read()
|
|
1878
1934
|
\`\`\`
|
|
1879
1935
|
|
|
1880
1936
|
### Map Operations
|
|
1881
1937
|
\`\`\`compact
|
|
1882
|
-
//
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
//
|
|
1887
|
-
|
|
1888
|
-
//
|
|
1889
|
-
|
|
1890
|
-
|
|
1938
|
+
// Map<key_type, value_type> - All operations work in circuits unless noted
|
|
1939
|
+
|
|
1940
|
+
// Insert/update operations
|
|
1941
|
+
balances.insert(address, 100); // insert(key, value): []
|
|
1942
|
+
balances.insertDefault(address); // insertDefault(key): [] - inserts default value
|
|
1943
|
+
|
|
1944
|
+
// Query operations (all work in circuits \u2705)
|
|
1945
|
+
const balance = balances.lookup(address); // lookup(key): value_type
|
|
1946
|
+
const exists = balances.member(address); // member(key): Boolean
|
|
1947
|
+
const empty = balances.isEmpty(); // isEmpty(): Boolean
|
|
1948
|
+
const count = balances.size(); // size(): Uint<64>
|
|
1949
|
+
|
|
1950
|
+
// Remove operations
|
|
1951
|
+
balances.remove(address); // remove(key): []
|
|
1952
|
+
balances.resetToDefault(); // resetToDefault(): [] - clears entire map
|
|
1953
|
+
|
|
1954
|
+
// Coin-specific (only when value_type is QualifiedCoinInfo)
|
|
1955
|
+
// coinMap.insertCoin(key, coinInfo, recipient): []
|
|
1891
1956
|
\`\`\`
|
|
1892
1957
|
|
|
1958
|
+
**TypeScript-only:** \`[Symbol.iterator]()\` for iteration - not available in circuits.
|
|
1959
|
+
|
|
1893
1960
|
### Set Operations
|
|
1894
1961
|
\`\`\`compact
|
|
1895
|
-
//
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
//
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1962
|
+
// Set<value_type> - All operations work in circuits unless noted
|
|
1963
|
+
|
|
1964
|
+
// Insert/remove operations
|
|
1965
|
+
members.insert(address); // insert(elem): []
|
|
1966
|
+
members.remove(address); // remove(elem): []
|
|
1967
|
+
members.resetToDefault(); // resetToDefault(): [] - clears entire set
|
|
1968
|
+
|
|
1969
|
+
// Query operations (all work in circuits \u2705)
|
|
1970
|
+
const isMember = members.member(address); // member(elem): Boolean
|
|
1971
|
+
const empty = members.isEmpty(); // isEmpty(): Boolean
|
|
1972
|
+
const count = members.size(); // size(): Uint<64>
|
|
1973
|
+
|
|
1974
|
+
// Coin-specific (only when value_type is QualifiedCoinInfo)
|
|
1975
|
+
// coinSet.insertCoin(coinInfo, recipient): []
|
|
1903
1976
|
\`\`\`
|
|
1904
1977
|
|
|
1978
|
+
**TypeScript-only:** \`[Symbol.iterator]()\` for iteration - not available in circuits.
|
|
1979
|
+
|
|
1905
1980
|
### Maybe Operations
|
|
1906
1981
|
\`\`\`compact
|
|
1907
1982
|
const opt: Maybe<Field> = some<Field>(42);
|
|
@@ -2116,9 +2191,13 @@ npm install @openzeppelin/compact-contracts
|
|
|
2116
2191
|
## Usage Example
|
|
2117
2192
|
|
|
2118
2193
|
\`\`\`compact
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2194
|
+
pragma language_version >= 0.18.0;
|
|
2195
|
+
|
|
2196
|
+
import CompactStandardLibrary;
|
|
2197
|
+
import "@openzeppelin/compact-contracts/src/token/FungibleToken"
|
|
2198
|
+
prefix FungibleToken_;
|
|
2199
|
+
import "@openzeppelin/compact-contracts/src/access/Ownable"
|
|
2200
|
+
prefix Ownable_;
|
|
2122
2201
|
|
|
2123
2202
|
ledger {
|
|
2124
2203
|
// Inherit from OpenZeppelin contracts
|
|
@@ -2153,8 +2232,11 @@ The recommended standard for privacy-preserving tokens on Midnight.
|
|
|
2153
2232
|
## Basic Usage
|
|
2154
2233
|
|
|
2155
2234
|
\`\`\`compact
|
|
2156
|
-
|
|
2157
|
-
|
|
2235
|
+
pragma language_version >= 0.18.0;
|
|
2236
|
+
|
|
2237
|
+
import CompactStandardLibrary;
|
|
2238
|
+
import "@openzeppelin/compact-contracts/src/token/FungibleToken"
|
|
2239
|
+
prefix FungibleToken_;
|
|
2158
2240
|
|
|
2159
2241
|
ledger {
|
|
2160
2242
|
...FungibleToken.ledger;
|
|
@@ -2195,7 +2277,8 @@ export circuit revealBalance(): Field {
|
|
|
2195
2277
|
## Minting and Burning
|
|
2196
2278
|
|
|
2197
2279
|
\`\`\`compact
|
|
2198
|
-
|
|
2280
|
+
import "@openzeppelin/compact-contracts/src/access/Ownable"
|
|
2281
|
+
prefix Ownable_;
|
|
2199
2282
|
|
|
2200
2283
|
ledger {
|
|
2201
2284
|
...FungibleToken.ledger;
|
|
@@ -2237,7 +2320,8 @@ Patterns for controlling who can call contract functions.
|
|
|
2237
2320
|
Simple single-owner access control.
|
|
2238
2321
|
|
|
2239
2322
|
\`\`\`compact
|
|
2240
|
-
|
|
2323
|
+
import "@openzeppelin/compact-contracts/src/access/Ownable"
|
|
2324
|
+
prefix Ownable_;
|
|
2241
2325
|
|
|
2242
2326
|
ledger {
|
|
2243
2327
|
...Ownable.ledger;
|
|
@@ -2263,7 +2347,8 @@ export circuit transferOwnership(newOwner: Address): Void {
|
|
|
2263
2347
|
For more complex permission systems.
|
|
2264
2348
|
|
|
2265
2349
|
\`\`\`compact
|
|
2266
|
-
|
|
2350
|
+
import "@openzeppelin/compact-contracts/src/access/AccessControl"
|
|
2351
|
+
prefix AccessControl_;
|
|
2267
2352
|
|
|
2268
2353
|
ledger {
|
|
2269
2354
|
...AccessControl.ledger;
|
|
@@ -2291,8 +2376,10 @@ export circuit grantMinterRole(account: Address): Void {
|
|
|
2291
2376
|
## Combining Patterns
|
|
2292
2377
|
|
|
2293
2378
|
\`\`\`compact
|
|
2294
|
-
|
|
2295
|
-
|
|
2379
|
+
import "@openzeppelin/compact-contracts/src/access/Ownable"
|
|
2380
|
+
prefix Ownable_;
|
|
2381
|
+
import "@openzeppelin/compact-contracts/src/security/Pausable"
|
|
2382
|
+
prefix Pausable_;
|
|
2296
2383
|
|
|
2297
2384
|
ledger {
|
|
2298
2385
|
...Ownable.ledger;
|
|
@@ -2320,8 +2407,10 @@ Security utilities for Compact contracts.
|
|
|
2320
2407
|
Emergency stop mechanism for contracts.
|
|
2321
2408
|
|
|
2322
2409
|
\`\`\`compact
|
|
2323
|
-
|
|
2324
|
-
|
|
2410
|
+
import "@openzeppelin/compact-contracts/src/security/Pausable"
|
|
2411
|
+
prefix Pausable_;
|
|
2412
|
+
import "@openzeppelin/compact-contracts/src/access/Ownable"
|
|
2413
|
+
prefix Ownable_;
|
|
2325
2414
|
|
|
2326
2415
|
ledger {
|
|
2327
2416
|
...Pausable.ledger;
|
|
@@ -2727,134 +2816,131 @@ var EMBEDDED_CODE = {
|
|
|
2727
2816
|
"midnight://code/examples/counter": `// Counter Example Contract
|
|
2728
2817
|
// A simple contract demonstrating basic Compact concepts
|
|
2729
2818
|
|
|
2730
|
-
|
|
2819
|
+
pragma language_version >= 0.16 && <= 0.18;
|
|
2731
2820
|
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2821
|
+
import CompactStandardLibrary;
|
|
2822
|
+
|
|
2823
|
+
// Public counter - visible to everyone
|
|
2824
|
+
export ledger counter: Counter;
|
|
2825
|
+
|
|
2826
|
+
// Track last modifier (public)
|
|
2827
|
+
export ledger lastModifier: Opaque<"address">;
|
|
2739
2828
|
|
|
2740
2829
|
// Increment the counter
|
|
2741
|
-
export circuit increment(amount:
|
|
2830
|
+
export circuit increment(amount: Uint<16>): Uint<64> {
|
|
2742
2831
|
// Validate input
|
|
2743
|
-
assert(amount > 0
|
|
2744
|
-
assert(amount <= 100
|
|
2832
|
+
assert(amount > 0 as Uint<16>, "Amount must be positive");
|
|
2833
|
+
assert(amount <= 100 as Uint<16>, "Amount too large");
|
|
2745
2834
|
|
|
2746
2835
|
// Update counter
|
|
2747
|
-
|
|
2836
|
+
counter.increment(amount);
|
|
2748
2837
|
|
|
2749
2838
|
// Return new value
|
|
2750
|
-
return
|
|
2839
|
+
return counter.read();
|
|
2751
2840
|
}
|
|
2752
2841
|
|
|
2753
2842
|
// Decrement the counter
|
|
2754
|
-
export circuit decrement(amount:
|
|
2843
|
+
export circuit decrement(amount: Uint<16>): Uint<64> {
|
|
2755
2844
|
// Validate input
|
|
2756
|
-
assert(amount > 0
|
|
2757
|
-
assert(
|
|
2845
|
+
assert(amount > 0 as Uint<16>, "Amount must be positive");
|
|
2846
|
+
assert(counter.read() >= (amount as Uint<64>), "Counter would go negative");
|
|
2758
2847
|
|
|
2759
2848
|
// Update counter
|
|
2760
|
-
|
|
2849
|
+
counter.decrement(amount);
|
|
2761
2850
|
|
|
2762
2851
|
// Return new value
|
|
2763
|
-
return
|
|
2852
|
+
return counter.read();
|
|
2764
2853
|
}
|
|
2765
2854
|
|
|
2766
2855
|
// Read current value (view function)
|
|
2767
|
-
export circuit getValue():
|
|
2768
|
-
return
|
|
2856
|
+
export circuit getValue(): Uint<64> {
|
|
2857
|
+
return counter.read();
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
// Check if counter is below threshold
|
|
2861
|
+
export circuit isLessThan(threshold: Uint<64>): Boolean {
|
|
2862
|
+
return counter.lessThan(threshold);
|
|
2769
2863
|
}
|
|
2770
2864
|
`,
|
|
2771
2865
|
"midnight://code/examples/bboard": `// Bulletin Board Example Contract
|
|
2772
2866
|
// Demonstrates private messaging with selective disclosure
|
|
2773
2867
|
|
|
2774
|
-
|
|
2868
|
+
pragma language_version >= 0.16 && <= 0.18;
|
|
2775
2869
|
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2870
|
+
import CompactStandardLibrary;
|
|
2871
|
+
|
|
2872
|
+
// Public: message count and IDs
|
|
2873
|
+
export ledger messageCount: Counter;
|
|
2874
|
+
export ledger messageIds: Set<Field>;
|
|
2875
|
+
|
|
2876
|
+
// Private: actual message contents (no export = private)
|
|
2877
|
+
ledger messages: Map<Field, Opaque<"string">>;
|
|
2878
|
+
|
|
2879
|
+
// Private: message authors
|
|
2880
|
+
ledger authors: Map<Field, Opaque<"address">>;
|
|
2881
|
+
|
|
2882
|
+
// Witness to fetch message content
|
|
2883
|
+
witness getMessageContent(id: Field): Opaque<"string">;
|
|
2789
2884
|
|
|
2790
2885
|
// Post a new message (content is private)
|
|
2791
|
-
export circuit postMessage(content: Opaque<"string">, author: Opaque<"address">):
|
|
2792
|
-
// Generate unique message ID
|
|
2793
|
-
const messageId =
|
|
2886
|
+
export circuit postMessage(content: Opaque<"string">, author: Opaque<"address">): Uint<64> {
|
|
2887
|
+
// Generate unique message ID using counter read
|
|
2888
|
+
const messageId = messageCount.read();
|
|
2794
2889
|
|
|
2795
2890
|
// Store message privately
|
|
2796
|
-
|
|
2797
|
-
|
|
2891
|
+
messages.insert(messageId as Field, content);
|
|
2892
|
+
authors.insert(messageId as Field, author);
|
|
2798
2893
|
|
|
2799
2894
|
// Update public counters
|
|
2800
|
-
|
|
2801
|
-
|
|
2895
|
+
messageCount.increment(1);
|
|
2896
|
+
messageIds.insert(messageId as Field);
|
|
2802
2897
|
|
|
2803
2898
|
return messageId;
|
|
2804
2899
|
}
|
|
2805
2900
|
|
|
2806
|
-
// Witness to fetch message content
|
|
2807
|
-
witness getMessageContent(id: Field): Opaque<"string"> {
|
|
2808
|
-
return ledger.messages.get(id);
|
|
2809
|
-
}
|
|
2810
|
-
|
|
2811
2901
|
// Reveal a message publicly (owner's choice)
|
|
2812
2902
|
export circuit revealMessage(id: Field): Opaque<"string"> {
|
|
2813
|
-
assert(
|
|
2903
|
+
assert(messageIds.member(id), "Message not found");
|
|
2814
2904
|
|
|
2815
2905
|
const content = getMessageContent(id);
|
|
2816
2906
|
return disclose(content);
|
|
2817
2907
|
}
|
|
2818
2908
|
|
|
2819
2909
|
// Get total message count
|
|
2820
|
-
export circuit getMessageCount():
|
|
2821
|
-
return
|
|
2910
|
+
export circuit getMessageCount(): Uint<64> {
|
|
2911
|
+
return messageCount.read();
|
|
2822
2912
|
}
|
|
2823
2913
|
`,
|
|
2824
2914
|
"midnight://code/patterns/state-management": `// State Management Pattern
|
|
2825
2915
|
// Best practices for managing public and private state
|
|
2826
2916
|
|
|
2827
|
-
|
|
2917
|
+
pragma language_version >= 0.16 && <= 0.18;
|
|
2828
2918
|
|
|
2829
|
-
|
|
2830
|
-
// PUBLIC STATE
|
|
2831
|
-
// - Use for data that should be transparent
|
|
2832
|
-
// - Visible in blockchain explorers
|
|
2833
|
-
// - Can be queried by anyone
|
|
2834
|
-
|
|
2835
|
-
totalSupply: Counter;
|
|
2836
|
-
publicConfig: Field;
|
|
2837
|
-
|
|
2838
|
-
// PRIVATE STATE
|
|
2839
|
-
// - Use for sensitive user data
|
|
2840
|
-
// - Only owner can read
|
|
2841
|
-
// - Requires witnesses to access in circuits
|
|
2842
|
-
|
|
2843
|
-
@private
|
|
2844
|
-
userSecrets: Map<Opaque<"address">, Bytes<32>>;
|
|
2845
|
-
|
|
2846
|
-
@private
|
|
2847
|
-
privateBalances: Map<Opaque<"address">, Field>;
|
|
2848
|
-
}
|
|
2919
|
+
import CompactStandardLibrary;
|
|
2849
2920
|
|
|
2850
|
-
//
|
|
2851
|
-
export
|
|
2852
|
-
|
|
2853
|
-
|
|
2921
|
+
// PUBLIC STATE
|
|
2922
|
+
// - Use 'export ledger' for data that should be transparent
|
|
2923
|
+
// - Visible in blockchain explorers
|
|
2924
|
+
// - Can be queried by anyone
|
|
2925
|
+
|
|
2926
|
+
export ledger totalSupply: Uint<64>;
|
|
2927
|
+
export ledger publicConfig: Field;
|
|
2928
|
+
|
|
2929
|
+
// PRIVATE STATE
|
|
2930
|
+
// - Use 'ledger' without export for sensitive user data
|
|
2931
|
+
// - Only owner can read
|
|
2932
|
+
// - Requires witnesses to access in circuits
|
|
2854
2933
|
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2934
|
+
ledger userSecrets: Map<Opaque<"address">, Bytes<32>>;
|
|
2935
|
+
ledger privateBalances: Map<Opaque<"address">, Field>;
|
|
2936
|
+
|
|
2937
|
+
// Witnesses for private data access
|
|
2938
|
+
witness getUserSecret(user: Opaque<"address">): Bytes<32>;
|
|
2939
|
+
witness getPrivateBalance(user: Opaque<"address">): Field;
|
|
2940
|
+
|
|
2941
|
+
// Reading public state is straightforward
|
|
2942
|
+
export circuit getTotalSupply(): Uint<64> {
|
|
2943
|
+
return ledger.totalSupply;
|
|
2858
2944
|
}
|
|
2859
2945
|
|
|
2860
2946
|
// Using private state in a circuit
|
|
@@ -2865,7 +2951,7 @@ export circuit proveSecretKnowledge(
|
|
|
2865
2951
|
const secret = getUserSecret(user);
|
|
2866
2952
|
|
|
2867
2953
|
// Prove knowledge without revealing secret
|
|
2868
|
-
assert(
|
|
2954
|
+
assert(persistentHash(secret) == secretHash, "Invalid secret");
|
|
2869
2955
|
return true;
|
|
2870
2956
|
}
|
|
2871
2957
|
|
|
@@ -2875,53 +2961,45 @@ export circuit revealBalance(user: Opaque<"address">): Field {
|
|
|
2875
2961
|
// Explicitly reveal - user's choice
|
|
2876
2962
|
return disclose(balance);
|
|
2877
2963
|
}
|
|
2878
|
-
|
|
2879
|
-
witness getPrivateBalance(user: Opaque<"address">): Field {
|
|
2880
|
-
return ledger.privateBalances.get(user);
|
|
2881
|
-
}
|
|
2882
2964
|
`,
|
|
2883
2965
|
"midnight://code/patterns/access-control": `// Access Control Pattern
|
|
2884
2966
|
// Implementing permissions and authorization
|
|
2885
2967
|
|
|
2886
|
-
|
|
2968
|
+
pragma language_version >= 0.16 && <= 0.18;
|
|
2887
2969
|
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2970
|
+
import CompactStandardLibrary;
|
|
2971
|
+
|
|
2972
|
+
// Role definitions
|
|
2973
|
+
export ledger owner: Opaque<"address">;
|
|
2974
|
+
export ledger admins: Set<Opaque<"address">>;
|
|
2975
|
+
|
|
2976
|
+
// Access-controlled state
|
|
2977
|
+
export ledger sensitiveData: Field;
|
|
2978
|
+
|
|
2979
|
+
// Private admin keys
|
|
2980
|
+
ledger adminKeys: Map<Opaque<"address">, Bytes<32>>;
|
|
2899
2981
|
|
|
2900
2982
|
// Witness to get caller identity
|
|
2901
|
-
witness getCaller(): Opaque<"address"
|
|
2902
|
-
return getCurrentCaller();
|
|
2903
|
-
}
|
|
2983
|
+
witness getCaller(): Opaque<"address">;
|
|
2904
2984
|
|
|
2905
2985
|
// Only owner can call
|
|
2906
|
-
export circuit onlyOwnerAction(newValue: Field):
|
|
2986
|
+
export circuit onlyOwnerAction(newValue: Field): [] {
|
|
2907
2987
|
const caller = getCaller();
|
|
2908
|
-
assert(caller ==
|
|
2988
|
+
assert(caller == owner, "Not owner");
|
|
2909
2989
|
|
|
2910
|
-
|
|
2990
|
+
sensitiveData = newValue;
|
|
2911
2991
|
}
|
|
2912
2992
|
|
|
2913
2993
|
// Only admins can call
|
|
2914
|
-
export circuit onlyAdminAction(data: Field):
|
|
2994
|
+
export circuit onlyAdminAction(data: Field): [] {
|
|
2915
2995
|
const caller = getCaller();
|
|
2916
|
-
assert(
|
|
2996
|
+
assert(admins.member(caller), "Not admin");
|
|
2917
2997
|
|
|
2918
2998
|
// Admin action here
|
|
2919
2999
|
}
|
|
2920
3000
|
|
|
2921
3001
|
// Multi-sig pattern (require multiple approvals)
|
|
2922
|
-
witness getApprovalCount(action: Bytes<32>): Field
|
|
2923
|
-
return countApprovals(action);
|
|
2924
|
-
}
|
|
3002
|
+
witness getApprovalCount(action: Bytes<32>): Field;
|
|
2925
3003
|
|
|
2926
3004
|
export circuit requireMultisig(action: Bytes<32>, threshold: Field): Boolean {
|
|
2927
3005
|
const approvals = getApprovalCount(action);
|
|
@@ -2930,11 +3008,9 @@ export circuit requireMultisig(action: Bytes<32>, threshold: Field): Boolean {
|
|
|
2930
3008
|
}
|
|
2931
3009
|
|
|
2932
3010
|
// Time-locked action
|
|
2933
|
-
witness getCurrentTime(): Field
|
|
2934
|
-
return getBlockTimestamp();
|
|
2935
|
-
}
|
|
3011
|
+
witness getCurrentTime(): Field;
|
|
2936
3012
|
|
|
2937
|
-
export circuit timeLockedAction(unlockTime: Field):
|
|
3013
|
+
export circuit timeLockedAction(unlockTime: Field): [] {
|
|
2938
3014
|
const currentTime = getCurrentTime();
|
|
2939
3015
|
assert(currentTime >= unlockTime, "Action is timelocked");
|
|
2940
3016
|
|
|
@@ -2944,21 +3020,23 @@ export circuit timeLockedAction(unlockTime: Field): Void {
|
|
|
2944
3020
|
"midnight://code/patterns/privacy-preserving": `// Privacy-Preserving Patterns
|
|
2945
3021
|
// Techniques for maintaining privacy in smart contracts
|
|
2946
3022
|
|
|
2947
|
-
|
|
3023
|
+
pragma language_version >= 0.16 && <= 0.18;
|
|
2948
3024
|
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
3025
|
+
import CompactStandardLibrary;
|
|
3026
|
+
|
|
3027
|
+
// Commitment-based private balance
|
|
3028
|
+
export ledger balanceCommitments: Map<Opaque<"address">, Field>;
|
|
3029
|
+
|
|
3030
|
+
// Nullifier set (prevents double-spending)
|
|
3031
|
+
export ledger nullifiers: Set<Field>;
|
|
3032
|
+
|
|
3033
|
+
// Private state
|
|
3034
|
+
ledger secretBalances: Map<Opaque<"address">, Field>;
|
|
3035
|
+
ledger secretNonces: Map<Opaque<"address">, Field>;
|
|
3036
|
+
|
|
3037
|
+
// Witnesses for private state
|
|
3038
|
+
witness getSecretBalance(user: Opaque<"address">): Field;
|
|
3039
|
+
witness getSecretNonce(user: Opaque<"address">): Field;
|
|
2962
3040
|
|
|
2963
3041
|
// PATTERN 1: Commitment Scheme
|
|
2964
3042
|
// Store commitments instead of values
|
|
@@ -2968,11 +3046,14 @@ export circuit deposit(
|
|
|
2968
3046
|
amount: Field,
|
|
2969
3047
|
nonce: Field
|
|
2970
3048
|
): Field {
|
|
2971
|
-
// Create commitment: hash(amount
|
|
2972
|
-
|
|
3049
|
+
// Create commitment: hash(amount || nonce)
|
|
3050
|
+
// Use a struct to combine values into a single hashable input
|
|
3051
|
+
struct CommitmentInput { amount: Field, nonce: Field }
|
|
3052
|
+
const input = CommitmentInput { amount: amount, nonce: nonce };
|
|
3053
|
+
const commitment = persistentHash<Field>(input);
|
|
2973
3054
|
|
|
2974
3055
|
// Store commitment (hides amount)
|
|
2975
|
-
|
|
3056
|
+
balanceCommitments.insert(user, commitment);
|
|
2976
3057
|
|
|
2977
3058
|
return commitment;
|
|
2978
3059
|
}
|
|
@@ -2984,31 +3065,31 @@ export circuit proveBalance(
|
|
|
2984
3065
|
minBalance: Field
|
|
2985
3066
|
): Boolean {
|
|
2986
3067
|
// Verify commitment
|
|
2987
|
-
|
|
2988
|
-
|
|
3068
|
+
struct CommitmentInput { amount: Field, nonce: Field }
|
|
3069
|
+
const input = CommitmentInput { amount: amount, nonce: nonce };
|
|
3070
|
+
const expectedCommitment = persistentHash<Field>(input);
|
|
3071
|
+
assert(balanceCommitments.lookup(user) == expectedCommitment, "Invalid commitment");
|
|
2989
3072
|
|
|
2990
3073
|
// Prove property without revealing value
|
|
2991
|
-
assert(amount >= minBalance);
|
|
3074
|
+
assert(amount >= minBalance, "Insufficient balance");
|
|
2992
3075
|
return true;
|
|
2993
3076
|
}
|
|
2994
3077
|
|
|
2995
3078
|
// PATTERN 2: Nullifiers (Prevent Double-Spending)
|
|
2996
3079
|
|
|
2997
|
-
witness generateNullifier(secret: Bytes<32>, action: Field): Field
|
|
2998
|
-
return hash(secret, action);
|
|
2999
|
-
}
|
|
3080
|
+
witness generateNullifier(secret: Bytes<32>, action: Field): Field;
|
|
3000
3081
|
|
|
3001
3082
|
export circuit spendOnce(
|
|
3002
3083
|
secret: Bytes<32>,
|
|
3003
3084
|
action: Field
|
|
3004
|
-
):
|
|
3085
|
+
): [] {
|
|
3005
3086
|
const nullifier = generateNullifier(secret, action);
|
|
3006
3087
|
|
|
3007
3088
|
// Check nullifier hasn't been used
|
|
3008
|
-
assert(!
|
|
3089
|
+
assert(!nullifiers.member(nullifier), "Already spent");
|
|
3009
3090
|
|
|
3010
3091
|
// Mark as used
|
|
3011
|
-
|
|
3092
|
+
nullifiers.insert(nullifier);
|
|
3012
3093
|
|
|
3013
3094
|
// Perform action
|
|
3014
3095
|
}
|
|
@@ -3016,72 +3097,55 @@ export circuit spendOnce(
|
|
|
3016
3097
|
// PATTERN 3: Range Proofs
|
|
3017
3098
|
|
|
3018
3099
|
export circuit proveInRange(
|
|
3019
|
-
|
|
3100
|
+
value: Field,
|
|
3020
3101
|
min: Field,
|
|
3021
3102
|
max: Field
|
|
3022
3103
|
): Boolean {
|
|
3023
3104
|
// Prove value is in range without revealing it
|
|
3024
|
-
assert(value >= min);
|
|
3025
|
-
assert(value <= max);
|
|
3105
|
+
assert(value >= min, "Below minimum");
|
|
3106
|
+
assert(value <= max, "Above maximum");
|
|
3026
3107
|
return true;
|
|
3027
3108
|
}
|
|
3028
3109
|
|
|
3029
3110
|
// PATTERN 4: Private Set Membership
|
|
3030
3111
|
|
|
3112
|
+
witness computeMerkleRoot(element: Field, proof: Vector<10, Field>): Field;
|
|
3113
|
+
|
|
3031
3114
|
export circuit proveMembership(
|
|
3032
|
-
|
|
3115
|
+
element: Field,
|
|
3033
3116
|
setRoot: Field,
|
|
3034
|
-
|
|
3117
|
+
proof: Vector<10, Field>
|
|
3035
3118
|
): Boolean {
|
|
3036
3119
|
// Prove element is in set without revealing which element
|
|
3037
3120
|
const computedRoot = computeMerkleRoot(element, proof);
|
|
3038
|
-
assert(computedRoot == setRoot);
|
|
3121
|
+
assert(computedRoot == setRoot, "Invalid membership proof");
|
|
3039
3122
|
return true;
|
|
3040
3123
|
}
|
|
3041
|
-
|
|
3042
|
-
witness computeMerkleRoot(element: Field, proof: Array<Field>): Field {
|
|
3043
|
-
// Compute Merkle root from element and proof
|
|
3044
|
-
return merkleCompute(element, proof);
|
|
3045
|
-
}
|
|
3046
3124
|
`,
|
|
3047
3125
|
"midnight://code/templates/token": `// Privacy-Preserving Token Template
|
|
3048
3126
|
// Starter template for token contracts with privacy features
|
|
3049
3127
|
|
|
3050
|
-
|
|
3128
|
+
pragma language_version >= 0.16 && <= 0.18;
|
|
3051
3129
|
|
|
3052
|
-
|
|
3053
|
-
// Public token metadata
|
|
3054
|
-
name: Opaque<"string">;
|
|
3055
|
-
symbol: Opaque<"string">;
|
|
3056
|
-
decimals: Field;
|
|
3057
|
-
totalSupply: Counter;
|
|
3058
|
-
|
|
3059
|
-
// Private balances
|
|
3060
|
-
@private
|
|
3061
|
-
balances: Map<Opaque<"address">, Field>;
|
|
3062
|
-
|
|
3063
|
-
// Private allowances
|
|
3064
|
-
@private
|
|
3065
|
-
allowances: Map<Opaque<"address">, Map<Opaque<"address">, Field>>;
|
|
3066
|
-
}
|
|
3130
|
+
import CompactStandardLibrary;
|
|
3067
3131
|
|
|
3068
|
-
//
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3132
|
+
// Public token metadata
|
|
3133
|
+
export ledger name: Bytes<32>;
|
|
3134
|
+
export ledger symbol: Bytes<8>;
|
|
3135
|
+
export ledger decimals: Uint<8>;
|
|
3136
|
+
export ledger totalSupply: Uint<64>;
|
|
3072
3137
|
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
}
|
|
3138
|
+
// Private balances
|
|
3139
|
+
ledger balances: Map<Opaque<"address">, Uint<64>>;
|
|
3076
3140
|
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3141
|
+
// Witnesses for private state access
|
|
3142
|
+
witness getBalance(account: Opaque<"address">): Uint<64>;
|
|
3143
|
+
witness getCaller(): Opaque<"address">;
|
|
3080
3144
|
|
|
3081
3145
|
// Transfer tokens privately
|
|
3082
3146
|
export circuit transfer(
|
|
3083
3147
|
to: Opaque<"address">,
|
|
3084
|
-
amount:
|
|
3148
|
+
amount: Uint<64>
|
|
3085
3149
|
): Boolean {
|
|
3086
3150
|
const from = getCaller();
|
|
3087
3151
|
const fromBalance = getBalance(from);
|
|
@@ -3091,224 +3155,153 @@ export circuit transfer(
|
|
|
3091
3155
|
assert(fromBalance >= amount, "Insufficient balance");
|
|
3092
3156
|
|
|
3093
3157
|
// Update balances privately
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
return true;
|
|
3098
|
-
}
|
|
3099
|
-
|
|
3100
|
-
// Approve spender
|
|
3101
|
-
export circuit approve(
|
|
3102
|
-
spender: Opaque<"address">,
|
|
3103
|
-
amount: Field
|
|
3104
|
-
): Boolean {
|
|
3105
|
-
const owner = getCaller();
|
|
3106
|
-
|
|
3107
|
-
// Get or create allowance map for owner
|
|
3108
|
-
// Note: Simplified - actual implementation needs nested map handling
|
|
3109
|
-
ledger.allowances.get(owner).insert(spender, amount);
|
|
3110
|
-
|
|
3111
|
-
return true;
|
|
3112
|
-
}
|
|
3113
|
-
|
|
3114
|
-
// Transfer from approved account
|
|
3115
|
-
export circuit transferFrom(
|
|
3116
|
-
from: Opaque<"address">,
|
|
3117
|
-
to: Opaque<"address">,
|
|
3118
|
-
amount: Field
|
|
3119
|
-
): Boolean {
|
|
3120
|
-
const spender = getCaller();
|
|
3121
|
-
const allowance = getAllowance(from, spender);
|
|
3122
|
-
const fromBalance = getBalance(from);
|
|
3123
|
-
|
|
3124
|
-
// Validate
|
|
3125
|
-
assert(amount > 0, "Invalid amount");
|
|
3126
|
-
assert(allowance >= amount, "Insufficient allowance");
|
|
3127
|
-
assert(fromBalance >= amount, "Insufficient balance");
|
|
3128
|
-
|
|
3129
|
-
// Update state
|
|
3130
|
-
ledger.balances.insert(from, fromBalance - amount);
|
|
3131
|
-
ledger.balances.insert(to, getBalance(to) + amount);
|
|
3132
|
-
ledger.allowances.get(from).insert(spender, allowance - amount);
|
|
3158
|
+
balances.insert(from, fromBalance - amount);
|
|
3159
|
+
balances.insert(to, getBalance(to) + amount);
|
|
3133
3160
|
|
|
3134
3161
|
return true;
|
|
3135
3162
|
}
|
|
3136
3163
|
|
|
3137
3164
|
// Reveal balance (user's choice)
|
|
3138
|
-
export circuit revealMyBalance():
|
|
3165
|
+
export circuit revealMyBalance(): Uint<64> {
|
|
3139
3166
|
const caller = getCaller();
|
|
3140
3167
|
const balance = getBalance(caller);
|
|
3141
3168
|
return disclose(balance);
|
|
3142
3169
|
}
|
|
3170
|
+
|
|
3171
|
+
// Get total supply
|
|
3172
|
+
witness getTotalSupply(): Uint<64> {
|
|
3173
|
+
return ledger.totalSupply;
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
// Mint new tokens (admin only)
|
|
3177
|
+
export circuit mint(to: Opaque<"address">, amount: Uint<64>): Boolean {
|
|
3178
|
+
// Add access control in production
|
|
3179
|
+
balances.insert(to, getBalance(to) + amount);
|
|
3180
|
+
ledger.totalSupply = getTotalSupply() + amount;
|
|
3181
|
+
return true;
|
|
3182
|
+
}
|
|
3143
3183
|
`,
|
|
3144
3184
|
"midnight://code/templates/voting": `// Private Voting Template
|
|
3145
3185
|
// Starter template for privacy-preserving voting contracts
|
|
3146
3186
|
|
|
3147
|
-
|
|
3187
|
+
pragma language_version >= 0.16 && <= 0.18;
|
|
3148
3188
|
|
|
3149
|
-
|
|
3150
|
-
// Public: proposal metadata
|
|
3151
|
-
proposalCount: Counter;
|
|
3152
|
-
proposals: Map<Field, Opaque<"string">>;
|
|
3153
|
-
votingDeadlines: Map<Field, Field>;
|
|
3154
|
-
|
|
3155
|
-
// Public: vote tallies (revealed after voting ends)
|
|
3156
|
-
finalTallies: Map<Field, Map<Field, Field>>; // proposalId -> optionId -> count
|
|
3157
|
-
|
|
3158
|
-
// Private: individual votes
|
|
3159
|
-
@private
|
|
3160
|
-
votes: Map<Field, Map<Opaque<"address">, Field>>; // proposalId -> voter -> option
|
|
3161
|
-
|
|
3162
|
-
// Nullifiers to prevent double voting
|
|
3163
|
-
voteNullifiers: Set<Field>;
|
|
3164
|
-
|
|
3165
|
-
// Eligible voters
|
|
3166
|
-
eligibleVoters: Set<Opaque<"address">>;
|
|
3167
|
-
}
|
|
3189
|
+
import CompactStandardLibrary;
|
|
3168
3190
|
|
|
3169
|
-
//
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3191
|
+
// Public: proposal metadata
|
|
3192
|
+
export ledger proposalCount: Counter;
|
|
3193
|
+
export ledger proposals: Map<Uint<64>, Bytes<256>>;
|
|
3194
|
+
export ledger votingDeadlines: Map<Uint<64>, Uint<64>>;
|
|
3173
3195
|
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
}
|
|
3196
|
+
// Private: individual votes
|
|
3197
|
+
ledger votes: Map<Uint<64>, Map<Opaque<"address">, Uint<8>>>;
|
|
3177
3198
|
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
}
|
|
3199
|
+
// Nullifiers to prevent double voting
|
|
3200
|
+
export ledger voteNullifiers: Set<Bytes<32>>;
|
|
3181
3201
|
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3202
|
+
// Eligible voters
|
|
3203
|
+
export ledger eligibleVoters: Set<Opaque<"address">>;
|
|
3204
|
+
|
|
3205
|
+
// Witnesses
|
|
3206
|
+
witness getCaller(): Opaque<"address">;
|
|
3207
|
+
witness getCurrentTime(): Uint<64>;
|
|
3208
|
+
witness getVote(proposalId: Uint<64>, voter: Opaque<"address">): Uint<8>;
|
|
3209
|
+
witness computeNullifier(voter: Opaque<"address">, proposalId: Uint<64>): Bytes<32>;
|
|
3185
3210
|
|
|
3186
3211
|
// Create a new proposal
|
|
3187
3212
|
export circuit createProposal(
|
|
3188
|
-
description:
|
|
3189
|
-
deadline:
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
const proposalId = ledger.proposalCount.value();
|
|
3213
|
+
description: Bytes<256>,
|
|
3214
|
+
deadline: Uint<64>
|
|
3215
|
+
): Uint<64> {
|
|
3216
|
+
const proposalId = proposalCount.read();
|
|
3193
3217
|
|
|
3194
|
-
// Store proposal
|
|
3195
|
-
|
|
3196
|
-
|
|
3218
|
+
// Store proposal - proposalId is Uint<64> from Counter.read()
|
|
3219
|
+
proposals.insert(proposalId, description);
|
|
3220
|
+
votingDeadlines.insert(proposalId, deadline);
|
|
3197
3221
|
|
|
3198
|
-
|
|
3199
|
-
// (Simplified - actual implementation needs loop)
|
|
3200
|
-
|
|
3201
|
-
ledger.proposalCount.increment(1);
|
|
3222
|
+
proposalCount.increment(1);
|
|
3202
3223
|
return proposalId;
|
|
3203
3224
|
}
|
|
3204
3225
|
|
|
3205
3226
|
// Cast a private vote
|
|
3206
3227
|
export circuit vote(
|
|
3207
|
-
proposalId:
|
|
3208
|
-
option:
|
|
3228
|
+
proposalId: Uint<64>,
|
|
3229
|
+
option: Uint<8>
|
|
3209
3230
|
): Boolean {
|
|
3210
3231
|
const voter = getCaller();
|
|
3211
3232
|
const currentTime = getCurrentTime();
|
|
3212
3233
|
|
|
3213
3234
|
// Check eligibility
|
|
3214
|
-
assert(
|
|
3235
|
+
assert(eligibleVoters.member(voter), "Not eligible to vote");
|
|
3215
3236
|
|
|
3216
3237
|
// Check deadline
|
|
3217
|
-
const deadline =
|
|
3238
|
+
const deadline = votingDeadlines.lookup(proposalId);
|
|
3218
3239
|
assert(currentTime < deadline, "Voting ended");
|
|
3219
3240
|
|
|
3220
3241
|
// Check for double voting using nullifier
|
|
3221
3242
|
const nullifier = computeNullifier(voter, proposalId);
|
|
3222
|
-
assert(!
|
|
3223
|
-
|
|
3224
|
-
// Record vote privately
|
|
3225
|
-
ledger.votes.get(proposalId).insert(voter, option);
|
|
3243
|
+
assert(!voteNullifiers.member(nullifier), "Already voted");
|
|
3226
3244
|
|
|
3227
3245
|
// Add nullifier to prevent double voting
|
|
3228
|
-
|
|
3246
|
+
voteNullifiers.insert(nullifier);
|
|
3229
3247
|
|
|
3230
3248
|
return true;
|
|
3231
3249
|
}
|
|
3232
3250
|
|
|
3233
3251
|
// Reveal individual vote (voter's choice)
|
|
3234
|
-
export circuit revealMyVote(proposalId:
|
|
3252
|
+
export circuit revealMyVote(proposalId: Uint<32>): Uint<8> {
|
|
3235
3253
|
const voter = getCaller();
|
|
3236
3254
|
const myVote = getVote(proposalId, voter);
|
|
3237
3255
|
return disclose(myVote);
|
|
3238
3256
|
}
|
|
3239
3257
|
|
|
3240
|
-
// Tally votes (after deadline)
|
|
3241
|
-
// Note: This is simplified - real implementation would need
|
|
3242
|
-
// a mechanism to privately aggregate votes
|
|
3243
|
-
export circuit tallyVotes(proposalId: Field): Boolean {
|
|
3244
|
-
const currentTime = getCurrentTime();
|
|
3245
|
-
const deadline = ledger.votingDeadlines.get(proposalId);
|
|
3246
|
-
|
|
3247
|
-
assert(currentTime >= deadline, "Voting still active");
|
|
3248
|
-
|
|
3249
|
-
// In a real implementation, votes would be aggregated
|
|
3250
|
-
// using homomorphic encryption or MPC
|
|
3251
|
-
|
|
3252
|
-
return true;
|
|
3253
|
-
}
|
|
3254
|
-
|
|
3255
3258
|
// Add eligible voter (admin only)
|
|
3256
|
-
export circuit addVoter(voter: Opaque<"address">):
|
|
3259
|
+
export circuit addVoter(voter: Opaque<"address">): [] {
|
|
3257
3260
|
// Add access control in real implementation
|
|
3258
|
-
|
|
3261
|
+
eligibleVoters.insert(voter);
|
|
3259
3262
|
}
|
|
3260
3263
|
`,
|
|
3261
3264
|
"midnight://code/examples/nullifier": `// Nullifier Pattern Example
|
|
3262
3265
|
// Demonstrates how to create and use nullifiers to prevent double-spending/actions
|
|
3263
3266
|
|
|
3264
|
-
|
|
3267
|
+
pragma language_version >= 0.16 && <= 0.18;
|
|
3265
3268
|
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3269
|
+
import CompactStandardLibrary;
|
|
3270
|
+
|
|
3271
|
+
// Set of used nullifiers - prevents replay attacks
|
|
3272
|
+
export ledger usedNullifiers: Set<Bytes<32>>;
|
|
3273
|
+
|
|
3274
|
+
// Track claimed rewards
|
|
3275
|
+
export ledger claimedRewards: Counter;
|
|
3273
3276
|
|
|
3274
3277
|
// Hash function for creating nullifiers
|
|
3275
3278
|
// Combines secret + public data to create unique identifier
|
|
3276
|
-
witness computeNullifier(secret: Field, commitment: Field): Bytes<32
|
|
3277
|
-
// Hash the secret with the commitment
|
|
3278
|
-
// The nullifier reveals nothing about the secret
|
|
3279
|
-
// but is unique per secret+commitment pair
|
|
3280
|
-
return hash(secret, commitment);
|
|
3281
|
-
}
|
|
3279
|
+
witness computeNullifier(secret: Field, commitment: Field): Bytes<32>;
|
|
3282
3280
|
|
|
3283
3281
|
// Alternative: nullifier from address and action ID
|
|
3284
|
-
witness computeActionNullifier(
|
|
3285
|
-
userSecret: Field,
|
|
3286
|
-
actionId: Field
|
|
3287
|
-
): Bytes<32> {
|
|
3288
|
-
// Create nullifier: hash(secret || actionId)
|
|
3289
|
-
return hash(userSecret, actionId);
|
|
3290
|
-
}
|
|
3282
|
+
witness computeActionNullifier(userSecret: Field, actionId: Field): Bytes<32>;
|
|
3291
3283
|
|
|
3292
3284
|
// Claim a reward (can only claim once per user)
|
|
3285
|
+
// Note: rewardAmount is Uint<16> to match Counter.increment signature
|
|
3293
3286
|
export circuit claimReward(
|
|
3294
3287
|
secret: Field,
|
|
3295
3288
|
commitment: Field,
|
|
3296
|
-
rewardAmount:
|
|
3289
|
+
rewardAmount: Uint<16>
|
|
3297
3290
|
): Boolean {
|
|
3298
3291
|
// Compute the nullifier
|
|
3299
3292
|
const nullifier = computeNullifier(secret, commitment);
|
|
3300
3293
|
|
|
3301
3294
|
// Check nullifier hasn't been used (prevents double-claim)
|
|
3302
3295
|
assert(
|
|
3303
|
-
!
|
|
3296
|
+
!usedNullifiers.member(nullifier),
|
|
3304
3297
|
"Reward already claimed"
|
|
3305
3298
|
);
|
|
3306
3299
|
|
|
3307
3300
|
// Mark nullifier as used
|
|
3308
|
-
|
|
3301
|
+
usedNullifiers.insert(nullifier);
|
|
3309
3302
|
|
|
3310
|
-
// Process reward
|
|
3311
|
-
|
|
3303
|
+
// Process reward (Counter.increment takes Uint<16>)
|
|
3304
|
+
claimedRewards.increment(rewardAmount);
|
|
3312
3305
|
|
|
3313
3306
|
return true;
|
|
3314
3307
|
}
|
|
@@ -3324,12 +3317,12 @@ export circuit voteWithNullifier(
|
|
|
3324
3317
|
|
|
3325
3318
|
// Ensure hasn't voted on this proposal
|
|
3326
3319
|
assert(
|
|
3327
|
-
!
|
|
3320
|
+
!usedNullifiers.member(nullifier),
|
|
3328
3321
|
"Already voted on this proposal"
|
|
3329
3322
|
);
|
|
3330
3323
|
|
|
3331
3324
|
// Record nullifier
|
|
3332
|
-
|
|
3325
|
+
usedNullifiers.insert(nullifier);
|
|
3333
3326
|
|
|
3334
3327
|
// Process vote...
|
|
3335
3328
|
return true;
|
|
@@ -3338,42 +3331,18 @@ export circuit voteWithNullifier(
|
|
|
3338
3331
|
"midnight://code/examples/hash": `// Hash Functions in Compact
|
|
3339
3332
|
// Examples of using hash functions for various purposes
|
|
3340
3333
|
|
|
3341
|
-
|
|
3334
|
+
pragma language_version >= 0.16 && <= 0.18;
|
|
3342
3335
|
|
|
3343
|
-
|
|
3344
|
-
commitments: Set<Bytes<32>>;
|
|
3345
|
-
hashedData: Map<Field, Bytes<32>>;
|
|
3346
|
-
}
|
|
3336
|
+
import CompactStandardLibrary;
|
|
3347
3337
|
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
// Hash a single field element
|
|
3351
|
-
return hash(data);
|
|
3352
|
-
}
|
|
3338
|
+
export ledger commitments: Set<Bytes<32>>;
|
|
3339
|
+
export ledger hashedData: Map<Field, Bytes<32>>;
|
|
3353
3340
|
|
|
3354
|
-
//
|
|
3355
|
-
witness
|
|
3356
|
-
// Concatenate and hash
|
|
3357
|
-
return hash(a, b, c);
|
|
3358
|
-
}
|
|
3341
|
+
// Basic hash function usage
|
|
3342
|
+
witness simpleHash(data: Field): Bytes<32>;
|
|
3359
3343
|
|
|
3360
3344
|
// Create a commitment (hash of value + randomness)
|
|
3361
|
-
witness createCommitment(value: Field, randomness: Field): Bytes<32
|
|
3362
|
-
// Pedersen-style commitment: H(value || randomness)
|
|
3363
|
-
return hash(value, randomness);
|
|
3364
|
-
}
|
|
3365
|
-
|
|
3366
|
-
// Hash bytes data
|
|
3367
|
-
witness hashBytes(data: Bytes<64>): Bytes<32> {
|
|
3368
|
-
return hash(data);
|
|
3369
|
-
}
|
|
3370
|
-
|
|
3371
|
-
// Create nullifier from secret
|
|
3372
|
-
witness createNullifier(secret: Field, publicInput: Field): Bytes<32> {
|
|
3373
|
-
// Nullifier = H(secret || publicInput)
|
|
3374
|
-
// Reveals nothing about secret, but is deterministic
|
|
3375
|
-
return hash(secret, publicInput);
|
|
3376
|
-
}
|
|
3345
|
+
witness createCommitment(value: Field, randomness: Field): Bytes<32>;
|
|
3377
3346
|
|
|
3378
3347
|
// Verify a commitment matches
|
|
3379
3348
|
export circuit verifyCommitment(
|
|
@@ -3389,108 +3358,105 @@ export circuit verifyCommitment(
|
|
|
3389
3358
|
// Store a hashed value
|
|
3390
3359
|
export circuit storeHashed(id: Field, data: Field): Bytes<32> {
|
|
3391
3360
|
const hashed = simpleHash(data);
|
|
3392
|
-
|
|
3361
|
+
hashedData.insert(id, hashed);
|
|
3393
3362
|
return hashed;
|
|
3394
3363
|
}
|
|
3395
3364
|
|
|
3396
3365
|
// Commit-reveal pattern
|
|
3397
3366
|
export circuit commit(commitment: Bytes<32>): Boolean {
|
|
3398
|
-
assert(!
|
|
3399
|
-
|
|
3367
|
+
assert(!commitments.member(commitment), "Already committed");
|
|
3368
|
+
commitments.insert(commitment);
|
|
3400
3369
|
return true;
|
|
3401
3370
|
}
|
|
3402
3371
|
|
|
3403
3372
|
export circuit reveal(value: Field, randomness: Field): Field {
|
|
3404
3373
|
const commitment = createCommitment(value, randomness);
|
|
3405
|
-
assert(
|
|
3374
|
+
assert(commitments.member(commitment), "No matching commitment");
|
|
3406
3375
|
return disclose(value);
|
|
3407
3376
|
}
|
|
3408
3377
|
`,
|
|
3409
3378
|
"midnight://code/examples/simple-counter": `// Simple Counter Contract
|
|
3410
3379
|
// Minimal example for learning Compact basics
|
|
3411
3380
|
|
|
3412
|
-
|
|
3381
|
+
pragma language_version >= 0.16 && <= 0.18;
|
|
3382
|
+
|
|
3383
|
+
import CompactStandardLibrary;
|
|
3413
3384
|
|
|
3414
3385
|
// Ledger state - stored on chain
|
|
3415
|
-
ledger
|
|
3416
|
-
counter: Counter;
|
|
3417
|
-
}
|
|
3386
|
+
export ledger counter: Counter;
|
|
3418
3387
|
|
|
3419
3388
|
// Increment the counter by 1
|
|
3420
|
-
export circuit increment():
|
|
3421
|
-
|
|
3422
|
-
return
|
|
3389
|
+
export circuit increment(): Uint<64> {
|
|
3390
|
+
counter.increment(1);
|
|
3391
|
+
return counter.read();
|
|
3423
3392
|
}
|
|
3424
3393
|
|
|
3425
3394
|
// Decrement the counter by 1
|
|
3426
|
-
export circuit decrement():
|
|
3427
|
-
assert(
|
|
3428
|
-
|
|
3429
|
-
return
|
|
3395
|
+
export circuit decrement(): Uint<64> {
|
|
3396
|
+
assert(counter.read() > 0, "Cannot go below zero");
|
|
3397
|
+
counter.decrement(1);
|
|
3398
|
+
return counter.read();
|
|
3430
3399
|
}
|
|
3431
3400
|
|
|
3432
3401
|
// Get current value
|
|
3433
|
-
export circuit get():
|
|
3434
|
-
return
|
|
3402
|
+
export circuit get(): Uint<64> {
|
|
3403
|
+
return counter.read();
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
// Check if below threshold
|
|
3407
|
+
export circuit isBelowLimit(limit: Uint<64>): Boolean {
|
|
3408
|
+
return counter.lessThan(limit);
|
|
3435
3409
|
}
|
|
3436
3410
|
|
|
3437
|
-
// Reset to zero
|
|
3438
|
-
export circuit reset():
|
|
3439
|
-
|
|
3440
|
-
ledger.counter.decrement(current);
|
|
3411
|
+
// Reset to zero
|
|
3412
|
+
export circuit reset(): [] {
|
|
3413
|
+
counter.resetToDefault();
|
|
3441
3414
|
}
|
|
3442
3415
|
`,
|
|
3443
3416
|
"midnight://code/templates/basic": `// Basic Compact Contract Template
|
|
3444
3417
|
// Starting point for new contracts
|
|
3445
3418
|
|
|
3446
|
-
|
|
3419
|
+
pragma language_version >= 0.16 && <= 0.18;
|
|
3420
|
+
|
|
3421
|
+
import CompactStandardLibrary;
|
|
3447
3422
|
|
|
3448
3423
|
// ============================================
|
|
3449
3424
|
// LEDGER STATE
|
|
3450
3425
|
// ============================================
|
|
3451
3426
|
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3427
|
+
// Public state (visible on-chain)
|
|
3428
|
+
export ledger initialized: Boolean;
|
|
3429
|
+
export ledger owner: Opaque<"address">;
|
|
3430
|
+
|
|
3431
|
+
// Private state (only owner can see)
|
|
3432
|
+
ledger secretData: Field;
|
|
3433
|
+
|
|
3434
|
+
// ============================================
|
|
3435
|
+
// WITNESSES
|
|
3436
|
+
// ============================================
|
|
3437
|
+
|
|
3438
|
+
witness getCaller(): Opaque<"address">;
|
|
3439
|
+
witness getSecret(): Field;
|
|
3461
3440
|
|
|
3462
3441
|
// ============================================
|
|
3463
3442
|
// INITIALIZATION
|
|
3464
3443
|
// ============================================
|
|
3465
3444
|
|
|
3466
3445
|
export circuit initialize(ownerAddress: Opaque<"address">): Boolean {
|
|
3467
|
-
assert(!
|
|
3446
|
+
assert(!initialized, "Already initialized");
|
|
3468
3447
|
|
|
3469
|
-
|
|
3470
|
-
|
|
3448
|
+
owner = ownerAddress;
|
|
3449
|
+
initialized = true;
|
|
3471
3450
|
|
|
3472
3451
|
return true;
|
|
3473
3452
|
}
|
|
3474
3453
|
|
|
3475
|
-
// ============================================
|
|
3476
|
-
// ACCESS CONTROL
|
|
3477
|
-
// ============================================
|
|
3478
|
-
|
|
3479
|
-
witness getCaller(): Opaque<"address"> {
|
|
3480
|
-
// Returns the transaction sender
|
|
3481
|
-
return context.caller;
|
|
3482
|
-
}
|
|
3483
|
-
|
|
3484
|
-
witness isOwner(): Boolean {
|
|
3485
|
-
return getCaller() == ledger.owner;
|
|
3486
|
-
}
|
|
3487
|
-
|
|
3488
3454
|
// ============================================
|
|
3489
3455
|
// PUBLIC FUNCTIONS
|
|
3490
3456
|
// ============================================
|
|
3491
3457
|
|
|
3492
3458
|
export circuit publicFunction(input: Field): Field {
|
|
3493
|
-
assert(
|
|
3459
|
+
assert(initialized, "Not initialized");
|
|
3494
3460
|
|
|
3495
3461
|
// Your logic here
|
|
3496
3462
|
return input * 2;
|
|
@@ -3500,22 +3466,19 @@ export circuit publicFunction(input: Field): Field {
|
|
|
3500
3466
|
// OWNER-ONLY FUNCTIONS
|
|
3501
3467
|
// ============================================
|
|
3502
3468
|
|
|
3503
|
-
export circuit setSecret(newSecret: Field):
|
|
3504
|
-
|
|
3505
|
-
|
|
3469
|
+
export circuit setSecret(newSecret: Field): [] {
|
|
3470
|
+
const caller = getCaller();
|
|
3471
|
+
assert(caller == owner, "Only owner can set secret");
|
|
3472
|
+
secretData = newSecret;
|
|
3506
3473
|
}
|
|
3507
3474
|
|
|
3508
3475
|
// ============================================
|
|
3509
3476
|
// PRIVATE DATA ACCESS
|
|
3510
3477
|
// ============================================
|
|
3511
3478
|
|
|
3512
|
-
witness getSecret(): Field {
|
|
3513
|
-
assert(isOwner(), "Only owner can view");
|
|
3514
|
-
return ledger.secretData;
|
|
3515
|
-
}
|
|
3516
|
-
|
|
3517
3479
|
export circuit revealSecret(): Field {
|
|
3518
|
-
|
|
3480
|
+
const caller = getCaller();
|
|
3481
|
+
assert(caller == owner, "Only owner can reveal");
|
|
3519
3482
|
return disclose(getSecret());
|
|
3520
3483
|
}
|
|
3521
3484
|
`
|
|
@@ -4625,10 +4588,11 @@ Key Compact concepts:
|
|
|
4625
4588
|
- \`Field\`, \`Boolean\`, \`Uint<N>\`, \`Bytes<N>\` - Primitive types
|
|
4626
4589
|
|
|
4627
4590
|
Always include:
|
|
4628
|
-
1. Proper imports (
|
|
4629
|
-
2.
|
|
4630
|
-
3.
|
|
4631
|
-
4.
|
|
4591
|
+
1. Proper imports (import CompactStandardLibrary;)
|
|
4592
|
+
2. pragma language_version >= 0.18.0;
|
|
4593
|
+
3. Clear ledger state definitions
|
|
4594
|
+
4. Access control where appropriate
|
|
4595
|
+
5. Comprehensive inline comments
|
|
4632
4596
|
|
|
4633
4597
|
Return ONLY the Compact code, no explanations.`;
|
|
4634
4598
|
const userPrompt = options.baseExample ? `Based on this example contract:
|
|
@@ -4813,6 +4777,406 @@ ${code}
|
|
|
4813
4777
|
}
|
|
4814
4778
|
}
|
|
4815
4779
|
|
|
4780
|
+
// src/services/syntax-validator.ts
|
|
4781
|
+
var ADT_SEARCH_LIMIT = 10;
|
|
4782
|
+
var SYNTAX_RESULTS_LIMIT = 10;
|
|
4783
|
+
var SYNTAX_SUBQUERY_LIMIT = 5;
|
|
4784
|
+
var MAX_NOTE_SOURCE_LENGTH = 500;
|
|
4785
|
+
var NOTE_MIN_LENGTH = 20;
|
|
4786
|
+
var NOTE_MAX_LENGTH = 300;
|
|
4787
|
+
var MAX_NOTES_PER_ADT = 3;
|
|
4788
|
+
async function searchADTInfo(adtName) {
|
|
4789
|
+
try {
|
|
4790
|
+
const docsResult = await searchDocsHosted(
|
|
4791
|
+
`${adtName} ADT operations methods ledger`,
|
|
4792
|
+
ADT_SEARCH_LIMIT,
|
|
4793
|
+
"reference"
|
|
4794
|
+
);
|
|
4795
|
+
if (!docsResult.results || docsResult.results.length === 0) {
|
|
4796
|
+
logger.debug(`No indexed docs found for ADT: ${adtName}`);
|
|
4797
|
+
return null;
|
|
4798
|
+
}
|
|
4799
|
+
const operations = [];
|
|
4800
|
+
const notes = [];
|
|
4801
|
+
let sourceDocPath;
|
|
4802
|
+
for (const result of docsResult.results) {
|
|
4803
|
+
const content = result.content || result.code || "";
|
|
4804
|
+
const isLedgerADTDoc = result.source.filePath.includes("ledger-adt");
|
|
4805
|
+
if (!sourceDocPath && isLedgerADTDoc) {
|
|
4806
|
+
sourceDocPath = result.source.filePath;
|
|
4807
|
+
}
|
|
4808
|
+
if (isLedgerADTDoc) {
|
|
4809
|
+
const methodPattern = /\|\s*`?(\w+)`?\s*\|\s*`?\(([^)]*)\)\s*(?::\s*([^|`]+))?`?\s*\|\s*([^|]+)\|/g;
|
|
4810
|
+
let match;
|
|
4811
|
+
while ((match = methodPattern.exec(content)) !== null) {
|
|
4812
|
+
const [, methodName, params, returnType, description] = match;
|
|
4813
|
+
if (methodName && !operations.some((op) => op.method === methodName)) {
|
|
4814
|
+
const descText = description?.trim() || "";
|
|
4815
|
+
const worksInCircuits = !/not available|not supported|not in circuits|typescript|sdk|off[-\s]?chain/i.test(
|
|
4816
|
+
descText
|
|
4817
|
+
);
|
|
4818
|
+
operations.push({
|
|
4819
|
+
method: methodName,
|
|
4820
|
+
signature: `(${params})${returnType ? `: ${returnType.trim()}` : ""}`,
|
|
4821
|
+
description: descText,
|
|
4822
|
+
worksInCircuits,
|
|
4823
|
+
source: "indexed-docs"
|
|
4824
|
+
});
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
const inlinePattern = /(?:method|operation|function)\s+`?(\w+)`?\s*\(([^)]*)\)/gi;
|
|
4828
|
+
while ((match = inlinePattern.exec(content)) !== null) {
|
|
4829
|
+
const [, methodName, params] = match;
|
|
4830
|
+
if (methodName && !operations.some((op) => op.method === methodName)) {
|
|
4831
|
+
const worksInCircuits = !/not available|not supported|not in circuits|typescript|sdk|off[-\s]?chain/i.test(
|
|
4832
|
+
content
|
|
4833
|
+
);
|
|
4834
|
+
operations.push({
|
|
4835
|
+
method: methodName,
|
|
4836
|
+
signature: `(${params})`,
|
|
4837
|
+
description: "Extracted from documentation",
|
|
4838
|
+
worksInCircuits,
|
|
4839
|
+
source: "indexed-docs"
|
|
4840
|
+
});
|
|
4841
|
+
}
|
|
4842
|
+
}
|
|
4843
|
+
}
|
|
4844
|
+
if (content.toLowerCase().includes(adtName.toLowerCase()) && content.length < MAX_NOTE_SOURCE_LENGTH) {
|
|
4845
|
+
const cleanNote = content.replace(/\s+/g, " ").trim();
|
|
4846
|
+
if (cleanNote.length > NOTE_MIN_LENGTH && cleanNote.length < NOTE_MAX_LENGTH) {
|
|
4847
|
+
notes.push(cleanNote);
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
}
|
|
4851
|
+
if (operations.length === 0) {
|
|
4852
|
+
logger.debug(`Could not parse operations for ADT: ${adtName}`);
|
|
4853
|
+
return null;
|
|
4854
|
+
}
|
|
4855
|
+
return {
|
|
4856
|
+
name: adtName,
|
|
4857
|
+
operations,
|
|
4858
|
+
notes: notes.slice(0, MAX_NOTES_PER_ADT),
|
|
4859
|
+
sourceDocPath,
|
|
4860
|
+
lastVerified: (/* @__PURE__ */ new Date()).toISOString()
|
|
4861
|
+
};
|
|
4862
|
+
} catch (error) {
|
|
4863
|
+
logger.warn(`Failed to search indexed docs for ADT ${adtName}`, {
|
|
4864
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4865
|
+
});
|
|
4866
|
+
return null;
|
|
4867
|
+
}
|
|
4868
|
+
}
|
|
4869
|
+
async function searchCompactSyntax(topic) {
|
|
4870
|
+
try {
|
|
4871
|
+
const [docsResult, codeResult] = await Promise.all([
|
|
4872
|
+
searchDocsHosted(
|
|
4873
|
+
`Compact ${topic} syntax`,
|
|
4874
|
+
SYNTAX_SUBQUERY_LIMIT,
|
|
4875
|
+
"reference"
|
|
4876
|
+
),
|
|
4877
|
+
searchCompactHosted(`${topic}`, SYNTAX_SUBQUERY_LIMIT)
|
|
4878
|
+
]);
|
|
4879
|
+
const mergedResults = [
|
|
4880
|
+
...docsResult.results || [],
|
|
4881
|
+
...codeResult.results || []
|
|
4882
|
+
];
|
|
4883
|
+
return {
|
|
4884
|
+
results: mergedResults.slice(0, SYNTAX_RESULTS_LIMIT),
|
|
4885
|
+
totalResults: mergedResults.length,
|
|
4886
|
+
query: topic,
|
|
4887
|
+
lastIndexed: docsResult.lastIndexed || codeResult.lastIndexed
|
|
4888
|
+
};
|
|
4889
|
+
} catch (error) {
|
|
4890
|
+
logger.warn(`Failed to search Compact syntax for topic ${topic}`, {
|
|
4891
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4892
|
+
});
|
|
4893
|
+
return null;
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
async function validateADTOperations(adtName, staticOperations) {
|
|
4897
|
+
const discrepancies = [];
|
|
4898
|
+
const enrichments = [];
|
|
4899
|
+
const indexedInfo = await searchADTInfo(adtName);
|
|
4900
|
+
if (!indexedInfo) {
|
|
4901
|
+
return {
|
|
4902
|
+
validated: false,
|
|
4903
|
+
operations: staticOperations.map((op) => ({
|
|
4904
|
+
method: op.method,
|
|
4905
|
+
description: op.note,
|
|
4906
|
+
worksInCircuits: op.works,
|
|
4907
|
+
source: "static-fallback"
|
|
4908
|
+
})),
|
|
4909
|
+
discrepancies: [
|
|
4910
|
+
"Could not validate against indexed docs - using static data"
|
|
4911
|
+
],
|
|
4912
|
+
enrichments: []
|
|
4913
|
+
};
|
|
4914
|
+
}
|
|
4915
|
+
const operations = [];
|
|
4916
|
+
const indexedMethodNames = new Set(
|
|
4917
|
+
indexedInfo.operations.map((op) => op.method.toLowerCase())
|
|
4918
|
+
);
|
|
4919
|
+
const staticMethodNames = new Set(
|
|
4920
|
+
staticOperations.map(
|
|
4921
|
+
(op) => op.method.replace(/^\./, "").replace(/\(.*$/, "").toLowerCase()
|
|
4922
|
+
)
|
|
4923
|
+
);
|
|
4924
|
+
for (const indexedOp of indexedInfo.operations) {
|
|
4925
|
+
operations.push(indexedOp);
|
|
4926
|
+
const staticOp = staticOperations.find(
|
|
4927
|
+
(s) => s.method.toLowerCase().includes(indexedOp.method.toLowerCase()) || indexedOp.method.toLowerCase().includes(
|
|
4928
|
+
s.method.replace(/^\./, "").replace(/\(.*$/, "").toLowerCase()
|
|
4929
|
+
)
|
|
4930
|
+
);
|
|
4931
|
+
if (staticOp && !staticOp.works && indexedOp.worksInCircuits) {
|
|
4932
|
+
discrepancies.push(
|
|
4933
|
+
`Static said ${staticOp.method} doesn't work, but indexed docs show it does`
|
|
4934
|
+
);
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
for (const staticOp of staticOperations) {
|
|
4938
|
+
const methodName = staticOp.method.replace(/^\./, "").replace(/\(.*$/, "").toLowerCase();
|
|
4939
|
+
if (!indexedMethodNames.has(methodName)) {
|
|
4940
|
+
discrepancies.push(
|
|
4941
|
+
`Static references ${staticOp.method} but not found in indexed docs - may not exist`
|
|
4942
|
+
);
|
|
4943
|
+
}
|
|
4944
|
+
}
|
|
4945
|
+
for (const indexedOp of indexedInfo.operations) {
|
|
4946
|
+
const methodName = indexedOp.method.toLowerCase();
|
|
4947
|
+
if (!staticMethodNames.has(methodName)) {
|
|
4948
|
+
enrichments.push(
|
|
4949
|
+
`Indexed docs show ${indexedOp.method} exists but was missing from static reference`
|
|
4950
|
+
);
|
|
4951
|
+
}
|
|
4952
|
+
}
|
|
4953
|
+
return {
|
|
4954
|
+
validated: true,
|
|
4955
|
+
operations,
|
|
4956
|
+
discrepancies,
|
|
4957
|
+
enrichments
|
|
4958
|
+
};
|
|
4959
|
+
}
|
|
4960
|
+
async function validateBuiltinFunctions(staticBuiltins) {
|
|
4961
|
+
const discrepancies = [];
|
|
4962
|
+
const enrichments = [];
|
|
4963
|
+
const results = await Promise.all(
|
|
4964
|
+
staticBuiltins.map(async (builtin) => {
|
|
4965
|
+
const searchResult = await searchCompactSyntax(
|
|
4966
|
+
`${builtin.name} function builtin`
|
|
4967
|
+
);
|
|
4968
|
+
return { builtin, found: (searchResult?.results?.length || 0) > 0 };
|
|
4969
|
+
})
|
|
4970
|
+
);
|
|
4971
|
+
for (const { builtin, found } of results) {
|
|
4972
|
+
if (!found) {
|
|
4973
|
+
discrepancies.push(
|
|
4974
|
+
`Builtin "${builtin.name}" not found in indexed docs - may not exist or have different name`
|
|
4975
|
+
);
|
|
4976
|
+
}
|
|
4977
|
+
}
|
|
4978
|
+
const allBuiltinsSearch = await searchCompactSyntax(
|
|
4979
|
+
"builtin function stdlib standard library"
|
|
4980
|
+
);
|
|
4981
|
+
if (allBuiltinsSearch?.results) {
|
|
4982
|
+
const knownNames = new Set(staticBuiltins.map((b) => b.name.toLowerCase()));
|
|
4983
|
+
for (const result of allBuiltinsSearch.results) {
|
|
4984
|
+
const content = result.content || result.code || "";
|
|
4985
|
+
const funcPattern = /\b(export\s+)?function\s+(\w+)/gi;
|
|
4986
|
+
let match;
|
|
4987
|
+
while ((match = funcPattern.exec(content)) !== null) {
|
|
4988
|
+
const funcName = match[2].toLowerCase();
|
|
4989
|
+
if (!knownNames.has(funcName) && funcName.length > 2) {
|
|
4990
|
+
enrichments.push(`Indexed docs mention "${match[2]}" function`);
|
|
4991
|
+
}
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
}
|
|
4995
|
+
return {
|
|
4996
|
+
dataType: "BUILTIN_FUNCTIONS",
|
|
4997
|
+
validated: discrepancies.length === 0,
|
|
4998
|
+
discrepancies,
|
|
4999
|
+
enrichments: [...new Set(enrichments)].slice(0, 10),
|
|
5000
|
+
deprecatedPatterns: [],
|
|
5001
|
+
lastValidated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5002
|
+
};
|
|
5003
|
+
}
|
|
5004
|
+
async function validateTypeCompatibility(staticRules) {
|
|
5005
|
+
const discrepancies = [];
|
|
5006
|
+
const enrichments = [];
|
|
5007
|
+
const typeSearch = await searchCompactSyntax(
|
|
5008
|
+
"type cast Field Uint Bytes conversion compatible"
|
|
5009
|
+
);
|
|
5010
|
+
if (typeSearch?.results) {
|
|
5011
|
+
for (const result of typeSearch.results) {
|
|
5012
|
+
const content = (result.content || result.code || "").toLowerCase();
|
|
5013
|
+
for (const rule of staticRules) {
|
|
5014
|
+
const typeMatch = rule.types.match(
|
|
5015
|
+
/^\s*(.+?)\s*(?:==|=|<=|>=|<|>|[+*])\s*(.+?)\s*$/
|
|
5016
|
+
);
|
|
5017
|
+
const typeA = typeMatch?.[1]?.trim().toLowerCase();
|
|
5018
|
+
const typeB = typeMatch?.[2]?.trim().toLowerCase();
|
|
5019
|
+
if (typeA && typeB) {
|
|
5020
|
+
if (content.includes(typeA) && content.includes(typeB) && content.includes("compatible")) {
|
|
5021
|
+
if (!rule.works) {
|
|
5022
|
+
discrepancies.push(
|
|
5023
|
+
`Static says "${rule.types}" doesn't work, but docs suggest compatibility`
|
|
5024
|
+
);
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
}
|
|
5028
|
+
}
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
return {
|
|
5032
|
+
dataType: "TYPE_COMPATIBILITY",
|
|
5033
|
+
validated: discrepancies.length === 0,
|
|
5034
|
+
discrepancies,
|
|
5035
|
+
enrichments,
|
|
5036
|
+
deprecatedPatterns: [],
|
|
5037
|
+
lastValidated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5038
|
+
};
|
|
5039
|
+
}
|
|
5040
|
+
async function validateCommonErrors(staticErrors) {
|
|
5041
|
+
const discrepancies = [];
|
|
5042
|
+
const enrichments = [];
|
|
5043
|
+
const errorSearch = await searchDocsHosted(
|
|
5044
|
+
"error compile compilation failure parse",
|
|
5045
|
+
ADT_SEARCH_LIMIT,
|
|
5046
|
+
"all"
|
|
5047
|
+
);
|
|
5048
|
+
if (errorSearch?.results) {
|
|
5049
|
+
const knownErrors = new Set(
|
|
5050
|
+
staticErrors.map((e) => e.error.toLowerCase().slice(0, 30))
|
|
5051
|
+
);
|
|
5052
|
+
for (const result of errorSearch.results) {
|
|
5053
|
+
const content = result.content || result.code || "";
|
|
5054
|
+
const errorPattern = /(?:error|Error):\s*["']?([^"'\n]+)["']?/g;
|
|
5055
|
+
let match;
|
|
5056
|
+
while ((match = errorPattern.exec(content)) !== null) {
|
|
5057
|
+
const errorMsg = match[1].toLowerCase().slice(0, 30);
|
|
5058
|
+
if (!knownErrors.has(errorMsg) && errorMsg.length > 10) {
|
|
5059
|
+
enrichments.push(`Docs mention error: "${match[1].slice(0, 50)}..."`);
|
|
5060
|
+
}
|
|
5061
|
+
}
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
return {
|
|
5065
|
+
dataType: "COMMON_ERRORS",
|
|
5066
|
+
validated: true,
|
|
5067
|
+
// Errors are informational, hard to validate
|
|
5068
|
+
discrepancies,
|
|
5069
|
+
enrichments: [...new Set(enrichments)].slice(0, 10),
|
|
5070
|
+
deprecatedPatterns: [],
|
|
5071
|
+
lastValidated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5072
|
+
};
|
|
5073
|
+
}
|
|
5074
|
+
var DEPRECATED_SYNTAX_PATTERNS = [
|
|
5075
|
+
{
|
|
5076
|
+
pattern: /ledger\s*\{/,
|
|
5077
|
+
name: "ledger-block",
|
|
5078
|
+
message: "Deprecated: Use 'export ledger field: Type;' instead of ledger { } block",
|
|
5079
|
+
since: "0.16"
|
|
5080
|
+
},
|
|
5081
|
+
{
|
|
5082
|
+
pattern: /Cell\s*<\s*\w+\s*>/,
|
|
5083
|
+
name: "cell-wrapper",
|
|
5084
|
+
message: "Deprecated: Cell<T> wrapper removed, use Type directly",
|
|
5085
|
+
since: "0.15"
|
|
5086
|
+
},
|
|
5087
|
+
{
|
|
5088
|
+
pattern: /:\s*Void\b/,
|
|
5089
|
+
name: "void-type",
|
|
5090
|
+
message: "Void type doesn't exist, use [] (empty tuple) for no return",
|
|
5091
|
+
since: "always"
|
|
5092
|
+
},
|
|
5093
|
+
{
|
|
5094
|
+
pattern: /\.\s*value\s*\(\s*\)/,
|
|
5095
|
+
name: "counter-value",
|
|
5096
|
+
message: "Counter.value() doesn't exist, use Counter.read() instead",
|
|
5097
|
+
since: "always"
|
|
5098
|
+
},
|
|
5099
|
+
{
|
|
5100
|
+
pattern: /::\w+/,
|
|
5101
|
+
name: "rust-enum-syntax",
|
|
5102
|
+
message: "Rust-style :: enum access doesn't work, use dot notation (Choice.rock)",
|
|
5103
|
+
since: "always"
|
|
5104
|
+
},
|
|
5105
|
+
{
|
|
5106
|
+
pattern: /pure\s+function\b/,
|
|
5107
|
+
name: "pure-function",
|
|
5108
|
+
message: "Use 'pure circuit' not 'pure function' for helper functions",
|
|
5109
|
+
since: "0.16"
|
|
5110
|
+
},
|
|
5111
|
+
{
|
|
5112
|
+
pattern: /witness\s+\w+\s*\([^)]*\)\s*:\s*\w+\s*\{/,
|
|
5113
|
+
name: "witness-with-body",
|
|
5114
|
+
message: "Witnesses are declarations only - no body allowed. Implementation goes in TypeScript prover.",
|
|
5115
|
+
since: "always"
|
|
5116
|
+
}
|
|
5117
|
+
];
|
|
5118
|
+
function scanForDeprecatedPatterns(code) {
|
|
5119
|
+
const issues = [];
|
|
5120
|
+
const lines = code.split("\n");
|
|
5121
|
+
for (const { pattern, name, message } of DEPRECATED_SYNTAX_PATTERNS) {
|
|
5122
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5123
|
+
if (pattern.test(lines[i])) {
|
|
5124
|
+
issues.push({
|
|
5125
|
+
pattern: name,
|
|
5126
|
+
message,
|
|
5127
|
+
lineNumber: i + 1
|
|
5128
|
+
});
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
}
|
|
5132
|
+
return issues;
|
|
5133
|
+
}
|
|
5134
|
+
async function validateAllStaticData(staticData) {
|
|
5135
|
+
const results = {};
|
|
5136
|
+
const [builtinResult, typeResult, errorResult, adtResults] = await Promise.all([
|
|
5137
|
+
staticData.builtinFunctions ? validateBuiltinFunctions(staticData.builtinFunctions) : Promise.resolve(null),
|
|
5138
|
+
staticData.typeCompatibility ? validateTypeCompatibility(staticData.typeCompatibility) : Promise.resolve(null),
|
|
5139
|
+
staticData.commonErrors ? validateCommonErrors(staticData.commonErrors) : Promise.resolve(null),
|
|
5140
|
+
staticData.ledgerTypeLimits ? Promise.all(
|
|
5141
|
+
Object.entries(staticData.ledgerTypeLimits).map(
|
|
5142
|
+
async ([name, data]) => ({
|
|
5143
|
+
name,
|
|
5144
|
+
result: await validateADTOperations(
|
|
5145
|
+
name,
|
|
5146
|
+
data.circuitOperations
|
|
5147
|
+
)
|
|
5148
|
+
})
|
|
5149
|
+
)
|
|
5150
|
+
) : Promise.resolve([])
|
|
5151
|
+
]);
|
|
5152
|
+
if (builtinResult) results.builtinFunctions = builtinResult;
|
|
5153
|
+
if (typeResult) results.typeCompatibility = typeResult;
|
|
5154
|
+
if (errorResult) results.commonErrors = errorResult;
|
|
5155
|
+
for (const { name, result } of adtResults) {
|
|
5156
|
+
results[`adt_${name}`] = {
|
|
5157
|
+
dataType: `LEDGER_TYPE_LIMITS.${name}`,
|
|
5158
|
+
validated: result.validated,
|
|
5159
|
+
discrepancies: result.discrepancies,
|
|
5160
|
+
enrichments: result.enrichments,
|
|
5161
|
+
deprecatedPatterns: [],
|
|
5162
|
+
lastValidated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5163
|
+
};
|
|
5164
|
+
}
|
|
5165
|
+
const allDiscrepancies = Object.values(results).flatMap(
|
|
5166
|
+
(r) => r.discrepancies
|
|
5167
|
+
);
|
|
5168
|
+
const allEnrichments = Object.values(results).flatMap((r) => r.enrichments);
|
|
5169
|
+
return {
|
|
5170
|
+
overall: {
|
|
5171
|
+
validated: allDiscrepancies.length === 0,
|
|
5172
|
+
totalDiscrepancies: allDiscrepancies.length,
|
|
5173
|
+
totalEnrichments: allEnrichments.length
|
|
5174
|
+
},
|
|
5175
|
+
results,
|
|
5176
|
+
lastValidated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5177
|
+
};
|
|
5178
|
+
}
|
|
5179
|
+
|
|
4816
5180
|
// src/server.ts
|
|
4817
5181
|
var SERVER_INFO = {
|
|
4818
5182
|
name: "midnight-mcp",
|
|
@@ -5679,17 +6043,30 @@ var TYPE_COMPATIBILITY = {
|
|
|
5679
6043
|
var LEDGER_TYPE_LIMITS = {
|
|
5680
6044
|
Counter: {
|
|
5681
6045
|
circuitOperations: [
|
|
5682
|
-
{
|
|
6046
|
+
{
|
|
6047
|
+
method: ".increment(n)",
|
|
6048
|
+
works: true,
|
|
6049
|
+
note: "Increase counter by n (Uint<16>)"
|
|
6050
|
+
},
|
|
5683
6051
|
{
|
|
5684
6052
|
method: ".decrement(n)",
|
|
5685
6053
|
works: true,
|
|
5686
|
-
note: "
|
|
6054
|
+
note: "Decrease counter by n (Uint<16>)"
|
|
6055
|
+
},
|
|
6056
|
+
{
|
|
6057
|
+
method: ".read()",
|
|
6058
|
+
works: true,
|
|
6059
|
+
note: "Get current value (returns Uint<64>)"
|
|
5687
6060
|
},
|
|
5688
|
-
{
|
|
5689
|
-
|
|
6061
|
+
{
|
|
6062
|
+
method: ".lessThan(n)",
|
|
6063
|
+
works: true,
|
|
6064
|
+
note: "Compare with threshold (returns Boolean)"
|
|
6065
|
+
},
|
|
6066
|
+
{ method: ".resetToDefault()", works: true, note: "Reset to 0" }
|
|
5690
6067
|
],
|
|
5691
6068
|
typescriptAccess: "Access counter value via `ledgerState.counter` in TypeScript SDK",
|
|
5692
|
-
|
|
6069
|
+
note: "All Counter operations work in circuits. Use .read() to get value, NOT .value()"
|
|
5693
6070
|
},
|
|
5694
6071
|
Map: {
|
|
5695
6072
|
circuitOperations: [
|
|
@@ -5698,20 +6075,40 @@ var LEDGER_TYPE_LIMITS = {
|
|
|
5698
6075
|
works: true,
|
|
5699
6076
|
note: "Adds/updates entry"
|
|
5700
6077
|
},
|
|
6078
|
+
{
|
|
6079
|
+
method: ".insertDefault(key)",
|
|
6080
|
+
works: true,
|
|
6081
|
+
note: "Inserts default value for key"
|
|
6082
|
+
},
|
|
5701
6083
|
{ method: ".remove(key)", works: true, note: "Removes entry" },
|
|
5702
6084
|
{
|
|
5703
6085
|
method: ".lookup(key)",
|
|
5704
6086
|
works: true,
|
|
5705
|
-
note: "Returns
|
|
6087
|
+
note: "Returns value_type - gets value for key"
|
|
5706
6088
|
},
|
|
5707
6089
|
{
|
|
5708
6090
|
method: ".member(key)",
|
|
5709
6091
|
works: true,
|
|
5710
6092
|
note: "Returns Boolean - checks if key exists"
|
|
6093
|
+
},
|
|
6094
|
+
{
|
|
6095
|
+
method: ".isEmpty()",
|
|
6096
|
+
works: true,
|
|
6097
|
+
note: "Returns Boolean - checks if map is empty"
|
|
6098
|
+
},
|
|
6099
|
+
{
|
|
6100
|
+
method: ".size()",
|
|
6101
|
+
works: true,
|
|
6102
|
+
note: "Returns Uint<64> - number of entries"
|
|
6103
|
+
},
|
|
6104
|
+
{
|
|
6105
|
+
method: ".resetToDefault()",
|
|
6106
|
+
works: true,
|
|
6107
|
+
note: "Clears entire map"
|
|
5711
6108
|
}
|
|
5712
6109
|
],
|
|
5713
|
-
typescriptAccess: "Query map via `contractState.data.get(key)` in TypeScript SDK",
|
|
5714
|
-
note: "
|
|
6110
|
+
typescriptAccess: "Query map via `contractState.data.get(key)` or iterate with `[Symbol.iterator]()` in TypeScript SDK",
|
|
6111
|
+
note: "All Map operations work in circuits. insertCoin() available when value_type is QualifiedCoinInfo."
|
|
5715
6112
|
},
|
|
5716
6113
|
Set: {
|
|
5717
6114
|
circuitOperations: [
|
|
@@ -5721,10 +6118,25 @@ var LEDGER_TYPE_LIMITS = {
|
|
|
5721
6118
|
method: ".member(value)",
|
|
5722
6119
|
works: true,
|
|
5723
6120
|
note: "Returns Boolean - checks if value exists in set"
|
|
6121
|
+
},
|
|
6122
|
+
{
|
|
6123
|
+
method: ".isEmpty()",
|
|
6124
|
+
works: true,
|
|
6125
|
+
note: "Returns Boolean - checks if set is empty"
|
|
6126
|
+
},
|
|
6127
|
+
{
|
|
6128
|
+
method: ".size()",
|
|
6129
|
+
works: true,
|
|
6130
|
+
note: "Returns Uint<64> - number of elements"
|
|
6131
|
+
},
|
|
6132
|
+
{
|
|
6133
|
+
method: ".resetToDefault()",
|
|
6134
|
+
works: true,
|
|
6135
|
+
note: "Clears entire set"
|
|
5724
6136
|
}
|
|
5725
6137
|
],
|
|
5726
|
-
typescriptAccess: "Check membership via `contractState.set.has(value)` in TypeScript SDK",
|
|
5727
|
-
note: "Set.
|
|
6138
|
+
typescriptAccess: "Check membership via `contractState.set.has(value)` or iterate with `[Symbol.iterator]()` in TypeScript SDK",
|
|
6139
|
+
note: "All Set operations work in circuits. insertCoin() available when value_type is QualifiedCoinInfo."
|
|
5728
6140
|
},
|
|
5729
6141
|
MerkleTree: {
|
|
5730
6142
|
circuitOperations: [
|
|
@@ -5754,11 +6166,16 @@ Or use bounded Uint<0..N> for parameters that need constraints`
|
|
|
5754
6166
|
},
|
|
5755
6167
|
{
|
|
5756
6168
|
error: 'operation "value" undefined for ledger field type Counter',
|
|
5757
|
-
cause: "
|
|
5758
|
-
fix: `
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
6169
|
+
cause: "Using wrong method name - Counter uses .read() not .value()",
|
|
6170
|
+
fix: `Use counter.read() to get the current value:
|
|
6171
|
+
const current = ledger.counter.read();
|
|
6172
|
+
|
|
6173
|
+
Counter ADT methods available in circuits:
|
|
6174
|
+
- increment(amount: Uint<16>): [] - increase counter
|
|
6175
|
+
- decrement(amount: Uint<16>): [] - decrease counter
|
|
6176
|
+
- read(): Uint<64> - get current value
|
|
6177
|
+
- lessThan(threshold: Uint<64>): Boolean - compare
|
|
6178
|
+
- resetToDefault(): [] - reset to zero`
|
|
5762
6179
|
},
|
|
5763
6180
|
{
|
|
5764
6181
|
error: "implicit disclosure of witness value",
|
|
@@ -5852,6 +6269,9 @@ CORRECT: pure circuit helper(...): Type { }`
|
|
|
5852
6269
|
import { readFile } from "fs/promises";
|
|
5853
6270
|
import { basename, isAbsolute, resolve } from "path";
|
|
5854
6271
|
import { platform } from "process";
|
|
6272
|
+
function escapeRegex(str) {
|
|
6273
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6274
|
+
}
|
|
5855
6275
|
function validateFilePath(filePath) {
|
|
5856
6276
|
if (!isAbsolute(filePath)) {
|
|
5857
6277
|
return {
|
|
@@ -6287,7 +6707,7 @@ async function extractContractStructure(input) {
|
|
|
6287
6707
|
});
|
|
6288
6708
|
}
|
|
6289
6709
|
}
|
|
6290
|
-
const hasStdlibImport = imports.includes("CompactStandardLibrary") || code.includes(
|
|
6710
|
+
const hasStdlibImport = imports.includes("CompactStandardLibrary") || code.includes("import CompactStandardLibrary");
|
|
6291
6711
|
if (hasStdlibImport) {
|
|
6292
6712
|
for (const circuit of circuits) {
|
|
6293
6713
|
if (stdlibExports.includes(circuit.name)) {
|
|
@@ -6311,16 +6731,20 @@ async function extractContractStructure(input) {
|
|
|
6311
6731
|
if (sealedFields.length > 0) {
|
|
6312
6732
|
for (const circuit of circuits) {
|
|
6313
6733
|
if (circuit.isExport) {
|
|
6734
|
+
const escapedCircuitName = escapeRegex(circuit.name);
|
|
6314
6735
|
const circuitBodyMatch = code.match(
|
|
6315
6736
|
new RegExp(
|
|
6316
|
-
`(?:export\\s+)?circuit\\s+${
|
|
6737
|
+
`(?:export\\s+)?circuit\\s+${escapedCircuitName}\\s*\\([^)]*\\)\\s*:[^{]*\\{([\\s\\S]*?)\\n\\}`,
|
|
6317
6738
|
"m"
|
|
6318
6739
|
)
|
|
6319
6740
|
);
|
|
6320
6741
|
if (circuitBodyMatch) {
|
|
6321
6742
|
const body = circuitBodyMatch[1];
|
|
6322
6743
|
for (const field of sealedFields) {
|
|
6323
|
-
|
|
6744
|
+
const escapedFieldName = escapeRegex(field.name);
|
|
6745
|
+
if (new RegExp(`\\b${escapedFieldName}\\s*=`).test(body) || new RegExp(`\\b${escapedFieldName}\\s*\\.\\s*\\w+\\s*\\(`).test(
|
|
6746
|
+
body
|
|
6747
|
+
)) {
|
|
6324
6748
|
potentialIssues.push({
|
|
6325
6749
|
type: "sealed_export_conflict",
|
|
6326
6750
|
line: circuit.line,
|
|
@@ -6394,7 +6818,7 @@ async function extractContractStructure(input) {
|
|
|
6394
6818
|
});
|
|
6395
6819
|
break;
|
|
6396
6820
|
}
|
|
6397
|
-
const counterValuePattern = /(\w+)\.value\
|
|
6821
|
+
const counterValuePattern = /(\w+)\.value\s*\(/g;
|
|
6398
6822
|
let counterMatch;
|
|
6399
6823
|
while ((counterMatch = counterValuePattern.exec(code)) !== null) {
|
|
6400
6824
|
const varName = counterMatch[1];
|
|
@@ -6406,8 +6830,8 @@ async function extractContractStructure(input) {
|
|
|
6406
6830
|
potentialIssues.push({
|
|
6407
6831
|
type: "invalid_counter_access",
|
|
6408
6832
|
line: lineNum,
|
|
6409
|
-
message: `Counter type '${varName}' does not have
|
|
6410
|
-
suggestion: `Counter
|
|
6833
|
+
message: `Counter type '${varName}' does not have '.value()' - use '.read()' instead`,
|
|
6834
|
+
suggestion: `Counter ADT methods: increment(n), decrement(n), read(), lessThan(n), resetToDefault()`,
|
|
6411
6835
|
severity: "error"
|
|
6412
6836
|
});
|
|
6413
6837
|
}
|
|
@@ -6469,8 +6893,9 @@ async function extractContractStructure(input) {
|
|
|
6469
6893
|
constructorParams.push(paramMatch[1]);
|
|
6470
6894
|
}
|
|
6471
6895
|
for (const param of constructorParams) {
|
|
6896
|
+
const escapedParam = escapeRegex(param);
|
|
6472
6897
|
const assignmentPattern = new RegExp(
|
|
6473
|
-
`(\\w+)\\s*=\\s*(?!disclose\\s*\\()${
|
|
6898
|
+
`(\\w+)\\s*=\\s*(?!disclose\\s*\\()${escapedParam}\\b`,
|
|
6474
6899
|
"g"
|
|
6475
6900
|
);
|
|
6476
6901
|
let assignMatch;
|
|
@@ -7052,6 +7477,96 @@ async function getLatestSyntax(input) {
|
|
|
7052
7477
|
} catch {
|
|
7053
7478
|
}
|
|
7054
7479
|
if (compactReference) {
|
|
7480
|
+
logger.debug(
|
|
7481
|
+
"Starting comprehensive hybrid validation against indexed docs"
|
|
7482
|
+
);
|
|
7483
|
+
let comprehensiveValidation;
|
|
7484
|
+
try {
|
|
7485
|
+
comprehensiveValidation = await validateAllStaticData({
|
|
7486
|
+
builtinFunctions: BUILTIN_FUNCTIONS.stdlib,
|
|
7487
|
+
typeCompatibility: [
|
|
7488
|
+
...TYPE_COMPATIBILITY.comparisons,
|
|
7489
|
+
...TYPE_COMPATIBILITY.arithmetic
|
|
7490
|
+
],
|
|
7491
|
+
commonErrors: COMMON_ERRORS,
|
|
7492
|
+
ledgerTypeLimits: LEDGER_TYPE_LIMITS
|
|
7493
|
+
});
|
|
7494
|
+
logger.debug("Comprehensive validation complete", {
|
|
7495
|
+
validated: comprehensiveValidation.overall.validated,
|
|
7496
|
+
discrepancies: comprehensiveValidation.overall.totalDiscrepancies,
|
|
7497
|
+
enrichments: comprehensiveValidation.overall.totalEnrichments
|
|
7498
|
+
});
|
|
7499
|
+
} catch (err) {
|
|
7500
|
+
logger.warn(
|
|
7501
|
+
"Comprehensive validation failed, falling back to ADT-only validation",
|
|
7502
|
+
{
|
|
7503
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7504
|
+
}
|
|
7505
|
+
);
|
|
7506
|
+
}
|
|
7507
|
+
const validationWarnings = [];
|
|
7508
|
+
const validationEnrichments = [];
|
|
7509
|
+
if (comprehensiveValidation) {
|
|
7510
|
+
for (const [key, result] of Object.entries(
|
|
7511
|
+
comprehensiveValidation.results
|
|
7512
|
+
)) {
|
|
7513
|
+
if (result.discrepancies.length > 0) {
|
|
7514
|
+
validationWarnings.push(
|
|
7515
|
+
`${key}: ${result.discrepancies.join("; ")}`
|
|
7516
|
+
);
|
|
7517
|
+
}
|
|
7518
|
+
if (result.enrichments.length > 0) {
|
|
7519
|
+
validationEnrichments.push(
|
|
7520
|
+
`${key}: ${result.enrichments.join("; ")}`
|
|
7521
|
+
);
|
|
7522
|
+
}
|
|
7523
|
+
}
|
|
7524
|
+
}
|
|
7525
|
+
let indexedSyntaxPatterns;
|
|
7526
|
+
try {
|
|
7527
|
+
const syntaxSearch = await searchCompactSyntax(
|
|
7528
|
+
"circuit witness ledger pragma"
|
|
7529
|
+
);
|
|
7530
|
+
if (syntaxSearch && syntaxSearch.results.length > 0) {
|
|
7531
|
+
indexedSyntaxPatterns = {
|
|
7532
|
+
resultCount: syntaxSearch.results.length,
|
|
7533
|
+
topSources: syntaxSearch.results.slice(0, 3).map((r) => ({
|
|
7534
|
+
path: r.source.filePath,
|
|
7535
|
+
repo: r.source.repository
|
|
7536
|
+
}))
|
|
7537
|
+
};
|
|
7538
|
+
}
|
|
7539
|
+
} catch {
|
|
7540
|
+
}
|
|
7541
|
+
const validatedLedgerTypeLimits = {};
|
|
7542
|
+
for (const [adtName, staticInfo] of Object.entries(LEDGER_TYPE_LIMITS)) {
|
|
7543
|
+
const typedStaticInfo = staticInfo;
|
|
7544
|
+
const adtValidation = comprehensiveValidation?.results[`adt_${adtName}`];
|
|
7545
|
+
if (adtValidation?.validated) {
|
|
7546
|
+
validatedLedgerTypeLimits[adtName] = {
|
|
7547
|
+
...typedStaticInfo,
|
|
7548
|
+
validatedAgainstDocs: true,
|
|
7549
|
+
validationDiscrepancies: adtValidation.discrepancies,
|
|
7550
|
+
validationEnrichments: adtValidation.enrichments
|
|
7551
|
+
};
|
|
7552
|
+
} else {
|
|
7553
|
+
validatedLedgerTypeLimits[adtName] = {
|
|
7554
|
+
...typedStaticInfo,
|
|
7555
|
+
validatedAgainstDocs: false,
|
|
7556
|
+
warning: "Could not validate against indexed docs - using static reference"
|
|
7557
|
+
};
|
|
7558
|
+
}
|
|
7559
|
+
}
|
|
7560
|
+
const quickStartTemplate = `${RECOMMENDED_PRAGMA}
|
|
7561
|
+
|
|
7562
|
+
import CompactStandardLibrary;
|
|
7563
|
+
|
|
7564
|
+
export ledger counter: Counter;
|
|
7565
|
+
|
|
7566
|
+
export circuit increment(): [] {
|
|
7567
|
+
counter.increment(1);
|
|
7568
|
+
}`;
|
|
7569
|
+
const deprecatedInTemplate = scanForDeprecatedPatterns(quickStartTemplate);
|
|
7055
7570
|
return {
|
|
7056
7571
|
repository: "midnightntwrk/compact",
|
|
7057
7572
|
version: `${COMPACT_VERSION.min}-${COMPACT_VERSION.max} (current)`,
|
|
@@ -7062,6 +7577,29 @@ async function getLatestSyntax(input) {
|
|
|
7062
7577
|
maintenanceGuide: "See docs/SYNTAX_MAINTENANCE.md for update instructions"
|
|
7063
7578
|
},
|
|
7064
7579
|
...versionWarning && { versionWarning },
|
|
7580
|
+
// Comprehensive hybrid validation status
|
|
7581
|
+
hybridValidation: {
|
|
7582
|
+
enabled: true,
|
|
7583
|
+
comprehensive: true,
|
|
7584
|
+
// Overall validation stats
|
|
7585
|
+
overall: comprehensiveValidation?.overall || {
|
|
7586
|
+
validated: false,
|
|
7587
|
+
totalDiscrepancies: 0,
|
|
7588
|
+
totalEnrichments: 0
|
|
7589
|
+
},
|
|
7590
|
+
// Which data types were validated
|
|
7591
|
+
validatedDataTypes: comprehensiveValidation ? Object.keys(comprehensiveValidation.results) : [],
|
|
7592
|
+
// All warnings from ALL static data validation
|
|
7593
|
+
warnings: validationWarnings.length > 0 ? validationWarnings : void 0,
|
|
7594
|
+
// All enrichments discovered from indexed docs
|
|
7595
|
+
enrichments: validationEnrichments.length > 0 ? validationEnrichments : void 0,
|
|
7596
|
+
// Indexed syntax pattern sources
|
|
7597
|
+
indexedSyntaxPatterns,
|
|
7598
|
+
// Deprecated patterns found in quick start template (should be none!)
|
|
7599
|
+
deprecatedPatternsInTemplate: deprecatedInTemplate.length > 0 ? deprecatedInTemplate : void 0,
|
|
7600
|
+
// Last validation timestamp
|
|
7601
|
+
lastValidated: comprehensiveValidation?.lastValidated || (/* @__PURE__ */ new Date()).toISOString()
|
|
7602
|
+
},
|
|
7065
7603
|
// Quick start template - ALWAYS compiles
|
|
7066
7604
|
quickStartTemplate: `${RECOMMENDED_PRAGMA}
|
|
7067
7605
|
|
|
@@ -7079,8 +7617,8 @@ export circuit increment(): [] {
|
|
|
7079
7617
|
builtinFunctions: BUILTIN_FUNCTIONS,
|
|
7080
7618
|
// Type compatibility rules
|
|
7081
7619
|
typeCompatibility: TYPE_COMPATIBILITY,
|
|
7082
|
-
// Ledger type limitations
|
|
7083
|
-
ledgerTypeLimits:
|
|
7620
|
+
// Ledger type limitations - VALIDATED against indexed docs
|
|
7621
|
+
ledgerTypeLimits: validatedLedgerTypeLimits,
|
|
7084
7622
|
// Common compilation errors with fixes
|
|
7085
7623
|
commonErrors: COMMON_ERRORS,
|
|
7086
7624
|
// Common mistakes that cause compilation failures
|
|
@@ -7122,7 +7660,7 @@ export circuit increment(): [] {
|
|
|
7122
7660
|
},
|
|
7123
7661
|
{
|
|
7124
7662
|
wrong: "counter.value()",
|
|
7125
|
-
correct: "//
|
|
7663
|
+
correct: "counter.read() // Returns Uint<64>",
|
|
7126
7664
|
error: 'operation "value" undefined for Counter'
|
|
7127
7665
|
},
|
|
7128
7666
|
{
|
|
@@ -7187,7 +7725,7 @@ Use quickStartTemplate as your base. Check commonMistakes BEFORE submitting code
|
|
|
7187
7725
|
|
|
7188
7726
|
KEY RULES:
|
|
7189
7727
|
1. public_key() is NOT a builtin - use persistentHash pattern
|
|
7190
|
-
2. Counter.value()
|
|
7728
|
+
2. Counter uses .read() not .value() - all ADT methods work in circuits
|
|
7191
7729
|
3. Map.lookup()/Set.member() ARE available in circuits (verified)
|
|
7192
7730
|
4. Arithmetic results need casting: (a + b) as Uint<64>
|
|
7193
7731
|
5. Uint\u2192Bytes needs two casts: (amount as Field) as Bytes<32>
|
|
@@ -9576,4 +10114,4 @@ export {
|
|
|
9576
10114
|
startServer,
|
|
9577
10115
|
startHttpServer
|
|
9578
10116
|
};
|
|
9579
|
-
//# sourceMappingURL=chunk-
|
|
10117
|
+
//# sourceMappingURL=chunk-7KJSVMFI.js.map
|