@stacks/clarinet-sdk 3.8.1

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.
Files changed (36) hide show
  1. package/README.md +91 -0
  2. package/dist/cjs/common/src/sdkProxyHelpers.d.ts +86 -0
  3. package/dist/cjs/common/src/sdkProxyHelpers.js +66 -0
  4. package/dist/cjs/common/src/sdkProxyHelpers.js.map +1 -0
  5. package/dist/cjs/node/src/index.d.ts +15 -0
  6. package/dist/cjs/node/src/index.js +68 -0
  7. package/dist/cjs/node/src/index.js.map +1 -0
  8. package/dist/cjs/node/src/sdkProxy.d.ts +11 -0
  9. package/dist/cjs/node/src/sdkProxy.js +98 -0
  10. package/dist/cjs/node/src/sdkProxy.js.map +1 -0
  11. package/dist/cjs/node/src/vfs.d.ts +3 -0
  12. package/dist/cjs/node/src/vfs.js +109 -0
  13. package/dist/cjs/node/src/vfs.js.map +1 -0
  14. package/dist/cjs/node/tsconfig.cjs.tsbuildinfo +1 -0
  15. package/dist/esm/common/src/sdkProxyHelpers.d.ts +86 -0
  16. package/dist/esm/common/src/sdkProxyHelpers.js +61 -0
  17. package/dist/esm/common/src/sdkProxyHelpers.js.map +1 -0
  18. package/dist/esm/node/src/index.d.ts +15 -0
  19. package/dist/esm/node/src/index.js +30 -0
  20. package/dist/esm/node/src/index.js.map +1 -0
  21. package/dist/esm/node/src/sdkProxy.d.ts +11 -0
  22. package/dist/esm/node/src/sdkProxy.js +95 -0
  23. package/dist/esm/node/src/sdkProxy.js.map +1 -0
  24. package/dist/esm/node/src/vfs.d.ts +3 -0
  25. package/dist/esm/node/src/vfs.js +70 -0
  26. package/dist/esm/node/src/vfs.js.map +1 -0
  27. package/dist/esm/node/src/vitest/index.d.ts +31 -0
  28. package/dist/esm/node/src/vitest/index.js +49 -0
  29. package/dist/esm/node/src/vitest/index.js.map +1 -0
  30. package/dist/esm/node/tsconfig.tsbuildinfo +1 -0
  31. package/dist/esm/package.json +1 -0
  32. package/package.json +73 -0
  33. package/vitest-helpers/src/clarityValuesMatchers.ts +389 -0
  34. package/vitest-helpers/src/global.d.ts +20 -0
  35. package/vitest-helpers/src/vitest.d.ts +28 -0
  36. package/vitest-helpers/src/vitest.setup.ts +69 -0
@@ -0,0 +1,389 @@
1
+ import { expect, ExpectStatic, assert } from "vitest";
2
+ import {
3
+ Cl,
4
+ ClarityValue,
5
+ ClarityType,
6
+ ResponseOkCV,
7
+ NoneCV,
8
+ SomeCV,
9
+ ResponseErrorCV,
10
+ IntCV,
11
+ UIntCV,
12
+ StringAsciiCV,
13
+ StringUtf8CV,
14
+ ContractPrincipalCV,
15
+ StandardPrincipalCV,
16
+ ListCV,
17
+ TupleCV,
18
+ BufferCV,
19
+ TrueCV,
20
+ FalseCV,
21
+ BooleanCV,
22
+ cvToString,
23
+ } from "@stacks/transactions";
24
+
25
+ import { MatcherState } from "@vitest/expect";
26
+
27
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#use_within_json
28
+ // @ts-ignore
29
+ BigInt.prototype.toJSON = function () {
30
+ return this.toString();
31
+ };
32
+
33
+ function notStr(isNot: boolean) {
34
+ return isNot ? "not " : "";
35
+ }
36
+
37
+ function formatMessage(this: MatcherState, received: string, expected: string) {
38
+ return `expected ${received} ${notStr(this.isNot)}to be ${expected}`;
39
+ }
40
+
41
+ export class ClarityTypeError extends Error {
42
+ actual?: any;
43
+ expected?: any;
44
+
45
+ constructor({ message, actual, expected }: { message: string; actual?: any; expected?: any }) {
46
+ super(message);
47
+ this.actual = actual;
48
+ this.expected = expected;
49
+
50
+ Object.setPrototypeOf(this, ClarityTypeError.prototype);
51
+ }
52
+ }
53
+
54
+ type ClarityTypetoValue = {
55
+ [ClarityType.OptionalNone]: NoneCV;
56
+ [ClarityType.OptionalSome]: SomeCV;
57
+ [ClarityType.ResponseOk]: ResponseOkCV;
58
+ [ClarityType.ResponseErr]: ResponseErrorCV;
59
+ [ClarityType.BoolTrue]: TrueCV;
60
+ [ClarityType.BoolFalse]: FalseCV;
61
+ [ClarityType.Int]: IntCV;
62
+ [ClarityType.UInt]: UIntCV;
63
+ [ClarityType.StringASCII]: StringAsciiCV;
64
+ [ClarityType.StringUTF8]: StringUtf8CV;
65
+ [ClarityType.PrincipalStandard]: StandardPrincipalCV;
66
+ [ClarityType.PrincipalContract]: ContractPrincipalCV;
67
+ [ClarityType.List]: ListCV;
68
+ [ClarityType.Tuple]: TupleCV;
69
+ [ClarityType.Buffer]: BufferCV;
70
+ };
71
+
72
+ const ClarityTypeReversed = Object.fromEntries(Object.entries(ClarityType).map(([k, v]) => [v, k]));
73
+
74
+ // the "simple clarity values" are CVs that can't be nested and have `value` property
75
+ type SimpleCV = BooleanCV | IntCV | UIntCV | StringAsciiCV | StringUtf8CV;
76
+ type SimpleCVTypes =
77
+ | ClarityType.BoolFalse
78
+ | ClarityType.BoolTrue
79
+ | ClarityType.Int
80
+ | ClarityType.UInt
81
+ | ClarityType.StringASCII
82
+ | ClarityType.StringUTF8;
83
+
84
+ const validClarityTypes = Object.values(ClarityType).filter(
85
+ (t) => typeof t === "string",
86
+ ) as string[];
87
+
88
+ function isClarityValue(input: unknown): input is ClarityValue {
89
+ if (!input || typeof input !== "object") return false;
90
+ if (!("type" in input) || typeof input.type !== "string") return false;
91
+ if (!validClarityTypes.includes(input.type)) return false;
92
+
93
+ return true;
94
+ }
95
+
96
+ function isClarityValueWithType<T extends ClarityType>(
97
+ input: unknown,
98
+ withType: T,
99
+ ): input is ClarityTypetoValue[T] {
100
+ if (!isClarityValue(input)) return false;
101
+ if (input.type !== withType) return false;
102
+
103
+ return true;
104
+ }
105
+
106
+ function checkCVType<T extends ClarityType>(
107
+ actual: unknown,
108
+ expectedType: T,
109
+ isNot: boolean,
110
+ ): actual is ClarityTypetoValue[T] {
111
+ const isCV = isClarityValue(actual);
112
+
113
+ if (!isCV) {
114
+ throw new ClarityTypeError({
115
+ message: `actual value must ${notStr(isNot)}be a Clarity "${
116
+ ClarityTypeReversed[expectedType]
117
+ }", received "${typeof actual}"`,
118
+ });
119
+ }
120
+
121
+ const isCVWithType = isClarityValueWithType(actual, expectedType);
122
+
123
+ if (!isCVWithType) {
124
+ // for readability, the error diff is kept short if the developers uses the wrong `expect<ClarityType>`
125
+ // ideally, we should have a way to display short message diffs even if the actual and/or expected data are big lists/tuples/buffers
126
+
127
+ // for now, we make an exception and display the full error message if the actual value is a ResponseErr
128
+ const errorCode = actual.type === ClarityType.ResponseErr ? ` ${Cl.prettyPrint(actual)}` : "";
129
+
130
+ throw new ClarityTypeError({
131
+ // generic and short message
132
+ message: `actual value must ${notStr(isNot)}be a Clarity "${
133
+ ClarityTypeReversed[expectedType]
134
+ }", received "${ClarityTypeReversed[actual.type]}"${errorCode}`,
135
+ actual: ClarityTypeReversed[actual.type],
136
+ expected: ClarityTypeReversed[expectedType],
137
+ });
138
+ }
139
+
140
+ return true;
141
+ }
142
+
143
+ function errorToAssertionResult(this: MatcherState, err: any) {
144
+ return {
145
+ pass: false,
146
+ message: () => err.message,
147
+ actual: err.actual,
148
+ expected: err.expected,
149
+ };
150
+ }
151
+
152
+ function simpleAssertion(
153
+ this: MatcherState,
154
+ cvType: SimpleCVTypes,
155
+ actualRaw: unknown,
156
+ expectedRaw: SimpleCV,
157
+ ) {
158
+ try {
159
+ const isCV = checkCVType(actualRaw, cvType, this.isNot);
160
+ assert(isCV);
161
+ } catch (e: any) {
162
+ return errorToAssertionResult.call(this, e);
163
+ }
164
+
165
+ return {
166
+ pass: this.equals(actualRaw, expectedRaw, undefined, true),
167
+ message: () =>
168
+ `expected ${Cl.prettyPrint(actualRaw)} ${notStr(this.isNot)}to be ${Cl.prettyPrint(
169
+ expectedRaw,
170
+ )}`,
171
+ actual: Cl.prettyPrint(actualRaw, 2),
172
+ expected: Cl.prettyPrint(expectedRaw, 2),
173
+ };
174
+ }
175
+
176
+ const typeToCvMethod = {
177
+ [ClarityType.ResponseOk]: Cl.ok,
178
+ [ClarityType.ResponseErr]: Cl.error,
179
+ [ClarityType.OptionalSome]: Cl.some,
180
+ };
181
+
182
+ // simple composite types are `ok`, `err`, `some`
183
+ function simpleCompositeAssertion(
184
+ this: MatcherState,
185
+ expectedType: ClarityType.ResponseOk | ClarityType.ResponseErr | ClarityType.OptionalSome,
186
+ actualRaw: unknown,
187
+ expectedValue: ClarityValue | ExpectStatic,
188
+ ) {
189
+ try {
190
+ const isCV = checkCVType(actualRaw, expectedType, this.isNot);
191
+ assert(isCV);
192
+ } catch (e: any) {
193
+ return errorToAssertionResult.call(this, e);
194
+ }
195
+
196
+ const clMethod = typeToCvMethod[expectedType];
197
+
198
+ const expectedIsCV = isClarityValue(expectedValue);
199
+ const expectedOneLine = expectedIsCV
200
+ ? Cl.prettyPrint(clMethod(expectedValue))
201
+ : JSON.stringify(expectedValue);
202
+ const expected = expectedIsCV
203
+ ? Cl.prettyPrint(clMethod(expectedValue), 2)
204
+ : JSON.stringify(expectedValue);
205
+
206
+ return {
207
+ pass: this.equals(actualRaw.value, expectedValue, undefined, true),
208
+ message: () => formatMessage.call(this, Cl.prettyPrint(actualRaw), expectedOneLine),
209
+ actual: Cl.prettyPrint(actualRaw, 2),
210
+ expected,
211
+ };
212
+ }
213
+
214
+ expect.extend({
215
+ toHaveClarityType(actual: unknown, expectedType: ClarityType) {
216
+ try {
217
+ const isCV = checkCVType(actual, expectedType, this.isNot);
218
+ assert(isCV);
219
+ } catch (e: any) {
220
+ return errorToAssertionResult.call(this, e);
221
+ }
222
+
223
+ return {
224
+ pass: true,
225
+ message: () =>
226
+ `actual value must ${notStr(this.isNot)}be a Clarity "${ClarityTypeReversed[expectedType]}"`,
227
+ };
228
+ },
229
+
230
+ toBeBool(actual: unknown, expected: boolean) {
231
+ const expectedType = expected ? ClarityType.BoolTrue : ClarityType.BoolFalse;
232
+ return simpleAssertion.call(this, expectedType, actual, Cl.bool(expected));
233
+ },
234
+
235
+ toBeInt(actual: unknown, expected: number | bigint) {
236
+ return simpleAssertion.call(this, ClarityType.Int, actual, Cl.int(expected));
237
+ },
238
+
239
+ toBeUint(actual: unknown, expected: number | bigint) {
240
+ return simpleAssertion.call(this, ClarityType.UInt, actual, Cl.uint(expected));
241
+ },
242
+
243
+ toBeAscii(actual: unknown, expected: string) {
244
+ return simpleAssertion.call(this, ClarityType.StringASCII, actual, Cl.stringAscii(expected));
245
+ },
246
+
247
+ toBeUtf8(actual: unknown, expected: string) {
248
+ return simpleAssertion.call(this, ClarityType.StringUTF8, actual, Cl.stringUtf8(expected));
249
+ },
250
+
251
+ toBeOk(actual: unknown, expectedValue: ExpectStatic | ClarityValue) {
252
+ return simpleCompositeAssertion.call(this, ClarityType.ResponseOk, actual, expectedValue);
253
+ },
254
+
255
+ toBeErr(actual: unknown, expectedValue: ExpectStatic | ClarityValue) {
256
+ return simpleCompositeAssertion.call(this, ClarityType.ResponseErr, actual, expectedValue);
257
+ },
258
+
259
+ toBeSome(actual: unknown, expectedValue: ExpectStatic | ClarityValue) {
260
+ return simpleCompositeAssertion.call(this, ClarityType.OptionalSome, actual, expectedValue);
261
+ },
262
+
263
+ toBeNone(actual: unknown) {
264
+ const expectedType = ClarityType.OptionalNone;
265
+ try {
266
+ const isCV = checkCVType(actual, expectedType, this.isNot);
267
+ assert(isCV);
268
+ } catch (e: any) {
269
+ return errorToAssertionResult.call(this, e);
270
+ }
271
+
272
+ const expected = Cl.none();
273
+ return {
274
+ pass: this.equals(actual, expected, undefined, true),
275
+ message: () => formatMessage.call(this, Cl.prettyPrint(actual), Cl.prettyPrint(actual)),
276
+ actual: Cl.prettyPrint(actual, 2),
277
+ expected: Cl.prettyPrint(actual, 2),
278
+ };
279
+ },
280
+
281
+ toBePrincipal(actual: unknown, expectedString: string) {
282
+ const isStandard = !expectedString.includes(".");
283
+ let expected: StandardPrincipalCV | ContractPrincipalCV;
284
+
285
+ const expectedType = isStandard ? ClarityType.PrincipalStandard : ClarityType.PrincipalContract;
286
+ try {
287
+ const isCV = checkCVType(actual, expectedType, this.isNot);
288
+ assert(isCV);
289
+ } catch (e: any) {
290
+ return errorToAssertionResult.call(this, e);
291
+ }
292
+
293
+ const actualString = cvToString(actual, "tryAscii");
294
+
295
+ try {
296
+ expected = isStandard
297
+ ? Cl.standardPrincipal(expectedString)
298
+ : Cl.contractPrincipal(...(expectedString.split(".") as [string, string]));
299
+ } catch {
300
+ return {
301
+ pass: false,
302
+ message: () => `expected ${expectedString} ${notStr(this.isNot)}to be a principal`,
303
+ actual: actualString,
304
+ expected: expectedString,
305
+ };
306
+ }
307
+
308
+ return {
309
+ pass: this.equals(actual, expected, undefined, true),
310
+ message: () => formatMessage.call(this, actualString, expectedString),
311
+ actual: Cl.prettyPrint(actual, 2),
312
+ expected: Cl.prettyPrint(expected, 2),
313
+ };
314
+ },
315
+
316
+ toBeBuff(actual: unknown, expectedRaw: Uint8Array) {
317
+ const expectedType = ClarityType.Buffer;
318
+ try {
319
+ const isCV = checkCVType(actual, expectedType, this.isNot);
320
+ assert(isCV);
321
+ } catch (e: any) {
322
+ return errorToAssertionResult.call(this, e);
323
+ }
324
+
325
+ const expected = Cl.buffer(expectedRaw);
326
+ return {
327
+ pass: this.equals(actual, expected, undefined, true),
328
+ // note: throw a simple message and rely on `actual` and `expected` to display the diff
329
+ message: () => `the received Buffer does ${this.isNot ? "" : "not "}match the expected one`,
330
+ actual: Cl.prettyPrint(actual, 2),
331
+ expected: Cl.prettyPrint(expected, 2),
332
+ };
333
+ },
334
+
335
+ toBeList(actual: unknown, expectedItems: ExpectStatic[] | ClarityValue[]) {
336
+ const expectedType = ClarityType.List;
337
+ try {
338
+ const isCV = checkCVType(actual, expectedType, this.isNot);
339
+ assert(isCV);
340
+ } catch (e: any) {
341
+ return errorToAssertionResult.call(this, e);
342
+ }
343
+
344
+ const isListArray = checkIsListArray(expectedItems);
345
+ const expected = isListArray ? Cl.prettyPrint(Cl.list(expectedItems), 2) : expectedItems;
346
+
347
+ return {
348
+ pass: this.equals(actual.value, expectedItems, undefined, true),
349
+ // note: throw a simple message and rely on `actual` and `expected` to display the diff
350
+ message: () => `the received List does ${this.isNot ? "" : "not "}match the expected one`,
351
+ actual: Cl.prettyPrint(actual, 2),
352
+ expected,
353
+ };
354
+ },
355
+
356
+ toBeTuple(actual: unknown, expectedData: Record<string, ExpectStatic | ClarityValue>) {
357
+ const expectedType = ClarityType.Tuple;
358
+ try {
359
+ const isCV = checkCVType(actual, expectedType, this.isNot);
360
+ assert(isCV);
361
+ } catch (e: any) {
362
+ return errorToAssertionResult.call(this, e);
363
+ }
364
+
365
+ const isTupleData = checkIsTupleData(expectedData);
366
+ const expected = isTupleData ? Cl.prettyPrint(Cl.tuple(expectedData), 2) : expectedData;
367
+
368
+ return {
369
+ pass: this.equals(actual.value, expectedData, undefined, true),
370
+ // note: throw a simple message and rely on `actual` and `expected` to display the diff
371
+ message: () => `the received Tuple does ${this.isNot ? "" : "not "}match the expected one`,
372
+ actual: Cl.prettyPrint(actual, 2),
373
+ expected,
374
+ };
375
+ },
376
+ });
377
+
378
+ // for composite types, matchers need to narrow the type of the expected value
379
+ // to know if it contains AsymmetricMatchers or if it's only ClarityValues
380
+
381
+ function checkIsTupleData(
382
+ expected: Record<string, ExpectStatic | ClarityValue>,
383
+ ): expected is Record<string, ClarityValue> {
384
+ return Object.values(expected).every((v) => isClarityValue(v));
385
+ }
386
+
387
+ function checkIsListArray(expected: ExpectStatic[] | ClarityValue[]): expected is ClarityValue[] {
388
+ return expected.every((v) => isClarityValue(v));
389
+ }
@@ -0,0 +1,20 @@
1
+ import type { Simnet } from "../../dist/esm/node/src";
2
+
3
+ declare global {
4
+ var simnet: Simnet;
5
+ var testEnvironment: string;
6
+ var coverageReports: string[];
7
+ var costsReports: string[];
8
+ var options: {
9
+ clarinet: {
10
+ manifestPath: string;
11
+ initBeforeEach: boolean;
12
+ coverage: boolean;
13
+ coverageFilename: string;
14
+ costs: boolean;
15
+ costsFilename: string;
16
+ includeBootContracts: boolean;
17
+ bootContractsPath: string;
18
+ };
19
+ };
20
+ }
@@ -0,0 +1,28 @@
1
+ import type { Assertion, AsymmetricMatchersContaining, ExpectStatic } from "vitest";
2
+ import type { ClarityType, ClarityValue } from "@stacks/transactions";
3
+
4
+ interface ClarityValuesMatchers<R = unknown> {
5
+ toHaveClarityType(expectedType: ClarityType): R;
6
+
7
+ toBeOk(expected: ExpectStatic | ClarityValue): R;
8
+ toBeErr(expected: ExpectStatic | ClarityValue): R;
9
+
10
+ toBeSome(expected: ExpectStatic | ClarityValue): R;
11
+ toBeNone(): R;
12
+
13
+ toBeBool(expected: boolean): R;
14
+ toBeInt(rexpected: number | bigint): R;
15
+ toBeUint(expected: number | bigint): R;
16
+ toBeAscii(expected: string): R;
17
+ toBeUtf8(expected: string): R;
18
+ toBePrincipal(expected: string): R;
19
+ toBeBuff(expected: Uint8Array): R;
20
+
21
+ toBeList(expected: ExpectStatic[] | ClarityValue[]): R;
22
+ toBeTuple(expected: Record<string, ExpectStatic | ClarityValue>): R;
23
+ }
24
+
25
+ declare module "vitest" {
26
+ interface Assertion<T = any> extends ClarityValuesMatchers<T> {}
27
+ interface AsymmetricMatchersContaining extends ClarityValuesMatchers<ExpectStatic> {}
28
+ }
@@ -0,0 +1,69 @@
1
+ import { afterAll, beforeAll, beforeEach, afterEach, RunnerTask } from "vitest";
2
+
3
+ import "./clarityValuesMatchers";
4
+
5
+ function getFullTestName(task: RunnerTask, names: string[]) {
6
+ const fullNames = [task.name, ...names];
7
+ if (task.suite?.name) {
8
+ return getFullTestName(task.suite, fullNames);
9
+ }
10
+ return fullNames;
11
+ }
12
+
13
+ /*
14
+ The `initBeforeEach` options controls the initialisation of the session.
15
+ If the session is initialised before each test, the reports are collected after each test.
16
+ If the session is not initialised before each test, it'll be initialized in the `beforeAll`, which
17
+ will run for all test file. In that case reports are collected in the after all.
18
+ */
19
+
20
+ beforeEach(async (ctx) => {
21
+ const { coverage, initBeforeEach, manifestPath } = global.options.clarinet;
22
+
23
+ if (initBeforeEach) {
24
+ await simnet.initSession(process.cwd(), manifestPath);
25
+ }
26
+
27
+ if (coverage) {
28
+ const suiteTestNames = getFullTestName(ctx.task, []);
29
+ const fullName = [ctx.task.file?.name || "", ...suiteTestNames].join("__");
30
+ simnet.setCurrentTestName(fullName);
31
+ }
32
+ });
33
+
34
+ afterEach(async (ctx) => {
35
+ const { coverage, costs, initBeforeEach, includeBootContracts, bootContractsPath } =
36
+ global.options.clarinet;
37
+
38
+ if (ctx.task.result?.state === "fail") {
39
+ const stackTrace = simnet.getLastContractCallTrace();
40
+ if (stackTrace) {
41
+ console.log(stackTrace);
42
+ }
43
+ }
44
+
45
+ if (initBeforeEach && (coverage || costs)) {
46
+ const report = simnet.collectReport(includeBootContracts || false, bootContractsPath || "");
47
+ if (coverage) coverageReports.push(report.coverage);
48
+ if (costs) costsReports.push(report.costs);
49
+ }
50
+ });
51
+
52
+ beforeAll(async () => {
53
+ const { initBeforeEach, manifestPath } = global.options.clarinet;
54
+
55
+ if (!initBeforeEach) {
56
+ await simnet.initSession(process.cwd(), manifestPath);
57
+ }
58
+ });
59
+
60
+ afterAll(() => {
61
+ const { coverage, costs, initBeforeEach, includeBootContracts, bootContractsPath } =
62
+ global.options.clarinet;
63
+
64
+ if (!initBeforeEach && (coverage || costs)) {
65
+ const report = simnet.collectReport(includeBootContracts || false, bootContractsPath || "");
66
+ if (coverage) coverageReports.push(report.coverage);
67
+ if (costs) costsReports.push(report.costs);
68
+ }
69
+ });