strade-stx 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.activity_counter +1 -0
- package/.gitattributes +3 -0
- package/.vscode/settings.json +4 -0
- package/.vscode/tasks.json +19 -0
- package/CHANGELOG.md +1 -0
- package/Clarinet.toml +56 -0
- package/Clarinet.toml.backup +174 -0
- package/Clarinet.toml.old +146 -0
- package/DEPLOYMENT_RESULTS.md +160 -0
- package/README.md +344 -0
- package/TODO.md +34 -0
- package/contracts/CoreMarketPlace.clar +227 -0
- package/contracts/DisputeResolution_clar.clar +265 -0
- package/contracts/EscrowService.clar +171 -0
- package/contracts/UserProfile.clar +280 -0
- package/contracts/ft-trait.clar +24 -0
- package/contracts/token.clar +178 -0
- package/costs-reports.json +76026 -0
- package/deployments/default.mainnet-plan.yaml +67 -0
- package/deployments/default.simnet-plan.yaml +105 -0
- package/deployments/default.testnet-plan.yaml +67 -0
- package/deployments/new-contracts.testnet-plan.yaml +32 -0
- package/frontend/README.md +10 -0
- package/frontend/components.json +22 -0
- package/frontend/dist/assets/index-BacuuL66.css +1 -0
- package/frontend/dist/assets/index-jryypd5B.js +194 -0
- package/frontend/dist/favicon.png +0 -0
- package/frontend/dist/index.html +15 -0
- package/frontend/dist/manifest.json +15 -0
- package/frontend/dist/vite.svg +1 -0
- package/frontend/empty-mock.js +1 -0
- package/frontend/eslint.config.js +23 -0
- package/frontend/eslint.config.mjs +25 -0
- package/frontend/index.html +14 -0
- package/frontend/next.config.ts +17 -0
- package/frontend/package-lock.json +14740 -0
- package/frontend/package.json +56 -0
- package/frontend/postcss.config.js +5 -0
- package/frontend/postcss.config.mjs +5 -0
- package/frontend/public/favicon.png +0 -0
- package/frontend/public/file.svg +1 -0
- package/frontend/public/globe.svg +1 -0
- package/frontend/public/manifest.json +15 -0
- package/frontend/public/next.svg +1 -0
- package/frontend/public/vercel.svg +1 -0
- package/frontend/public/vite.svg +1 -0
- package/frontend/public/window.svg +1 -0
- package/frontend/src/App.css +42 -0
- package/frontend/src/App.tsx +177 -0
- package/frontend/src/app/about/page.tsx +208 -0
- package/frontend/src/app/favicon.ico +0 -0
- package/frontend/src/app/globals.css +129 -0
- package/frontend/src/app/help/page.tsx +167 -0
- package/frontend/src/app/how-it-works/page.tsx +274 -0
- package/frontend/src/app/layout.tsx +55 -0
- package/frontend/src/app/marketplace/page.tsx +324 -0
- package/frontend/src/app/my-listings/page.tsx +318 -0
- package/frontend/src/app/page.tsx +15 -0
- package/frontend/src/assets/react.svg +1 -0
- package/frontend/src/components/ConfirmDialog.tsx +54 -0
- package/frontend/src/components/CreateListingForm.tsx +231 -0
- package/frontend/src/components/ErrorBoundary.tsx +73 -0
- package/frontend/src/components/FilterPanel.tsx +10 -0
- package/frontend/src/components/Footer.tsx +100 -0
- package/frontend/src/components/Header.tsx +268 -0
- package/frontend/src/components/ImageUpload.tsx +147 -0
- package/frontend/src/components/LandingPage.tsx +322 -0
- package/frontend/src/components/ListingCard.tsx +154 -0
- package/frontend/src/components/LoadingSkeleton.tsx +44 -0
- package/frontend/src/components/MobileNav.tsx +89 -0
- package/frontend/src/components/NotificationBell.tsx +8 -0
- package/frontend/src/components/NotificationPanel.tsx +14 -0
- package/frontend/src/components/README.md +14 -0
- package/frontend/src/components/SearchBar.tsx +10 -0
- package/frontend/src/components/TestnetBanner.tsx +29 -0
- package/frontend/src/components/ThemeToggle.tsx +32 -0
- package/frontend/src/components/__tests__/Header.test.tsx +70 -0
- package/frontend/src/components/__tests__/ListingCard.test.tsx +86 -0
- package/frontend/src/components/providers/ThemeProvider.tsx +9 -0
- package/frontend/src/components/ui/alert-dialog.tsx +141 -0
- package/frontend/src/components/ui/avatar.tsx +53 -0
- package/frontend/src/components/ui/badge.tsx +46 -0
- package/frontend/src/components/ui/button.tsx +60 -0
- package/frontend/src/components/ui/card.tsx +92 -0
- package/frontend/src/components/ui/dialog.tsx +143 -0
- package/frontend/src/components/ui/dropdown-menu.tsx +257 -0
- package/frontend/src/components/ui/input.tsx +21 -0
- package/frontend/src/components/ui/label.tsx +24 -0
- package/frontend/src/components/ui/select.tsx +187 -0
- package/frontend/src/components/ui/sonner.tsx +40 -0
- package/frontend/src/components/ui/textarea.tsx +18 -0
- package/frontend/src/context/README.md +27 -0
- package/frontend/src/index.css +166 -0
- package/frontend/src/lib/notificationEvents.ts +10 -0
- package/frontend/src/lib/notificationStore.ts +13 -0
- package/frontend/src/lib/notifications.ts +13 -0
- package/frontend/src/lib/search.ts +28 -0
- package/frontend/src/lib/stacks.ts +189 -0
- package/frontend/src/lib/utils.ts +6 -0
- package/frontend/src/main.tsx +10 -0
- package/frontend/src/test/setup.ts +23 -0
- package/frontend/src/types.d.ts +9 -0
- package/frontend/tsconfig.app.json +28 -0
- package/frontend/tsconfig.json +41 -0
- package/frontend/tsconfig.node.json +26 -0
- package/frontend/vercel.json +4 -0
- package/frontend/vite.config.ts +6 -0
- package/frontend/vitest.config.ts +17 -0
- package/lcov.info +31338 -0
- package/mainnetcontracts.md +16 -0
- package/package.json +53 -0
- package/scripts/auto-activity.sh +9 -0
- package/scripts/cancel-pending.ts +67 -0
- package/scripts/check-balances.ts +23 -0
- package/scripts/distribute-evenly.ts +56 -0
- package/scripts/drain-accounts.ts +70 -0
- package/scripts/fund-accounts.ts +88 -0
- package/scripts/fund-active.ts +59 -0
- package/scripts/fund-unfunded.ts +88 -0
- package/scripts/generate-activity.ts +181 -0
- package/scripts/git-activity-generator.ts +154 -0
- package/scripts/mobile-server.ts +123 -0
- package/settings/Devnet.toml +155 -0
- package/settings/Mainnet.toml +7 -0
- package/settings/Testnet.toml +9 -0
- package/tests/CoreMarketPlace.fuzz.test.ts +435 -0
- package/tests/CoreMarketPlace.test.ts +564 -0
- package/tsconfig.json +26 -0
- package/vitest.config.js +49 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Cl } from "@stacks/transactions";
|
|
3
|
+
|
|
4
|
+
const CONTRACT_NAME = 'CoreMarketPlace';
|
|
5
|
+
const LISTING_NAME = 'Test Item';
|
|
6
|
+
const LISTING_DESCRIPTION = 'A test item for sale';
|
|
7
|
+
const LISTING_PRICE = 1000000; // 1 STX in micro-STX
|
|
8
|
+
const LISTING_DURATION = 144; // ~1 day in blocks
|
|
9
|
+
|
|
10
|
+
// Helper function to create string arguments
|
|
11
|
+
const utf8 = (str: string) => Cl.stringUtf8(str);
|
|
12
|
+
|
|
13
|
+
describe("CoreMarketPlace Contract Tests", () => {
|
|
14
|
+
|
|
15
|
+
it("ensures that contract initializes correctly", () => {
|
|
16
|
+
const { result } = simnet.callReadOnlyFn(
|
|
17
|
+
CONTRACT_NAME,
|
|
18
|
+
'get-last-listing-id',
|
|
19
|
+
[],
|
|
20
|
+
simnet.deployer
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
expect(result).toBeOk(Cl.uint(0));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("ensures that users can create a listing with valid parameters", () => {
|
|
27
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
28
|
+
|
|
29
|
+
const { result } = simnet.callPublicFn(
|
|
30
|
+
CONTRACT_NAME,
|
|
31
|
+
'create-listing',
|
|
32
|
+
[
|
|
33
|
+
utf8(LISTING_NAME),
|
|
34
|
+
utf8(LISTING_DESCRIPTION),
|
|
35
|
+
Cl.uint(LISTING_PRICE),
|
|
36
|
+
Cl.uint(LISTING_DURATION)
|
|
37
|
+
],
|
|
38
|
+
seller
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
expect(result).toBeOk(Cl.uint(1));
|
|
42
|
+
|
|
43
|
+
// Verify the listing was created correctly
|
|
44
|
+
const { result: listingResult } = simnet.callReadOnlyFn(
|
|
45
|
+
CONTRACT_NAME,
|
|
46
|
+
'get-listing',
|
|
47
|
+
[Cl.uint(1)],
|
|
48
|
+
seller
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Check that the result is Some (not None)
|
|
52
|
+
expect(listingResult).not.toBeNone();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("ensures that creating a listing with zero price fails", () => {
|
|
56
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
57
|
+
|
|
58
|
+
const { result } = simnet.callPublicFn(
|
|
59
|
+
CONTRACT_NAME,
|
|
60
|
+
'create-listing',
|
|
61
|
+
[
|
|
62
|
+
utf8(LISTING_NAME),
|
|
63
|
+
utf8(LISTING_DESCRIPTION),
|
|
64
|
+
Cl.uint(0),
|
|
65
|
+
Cl.uint(LISTING_DURATION)
|
|
66
|
+
],
|
|
67
|
+
seller
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(result).toBeErr(Cl.error(Cl.uint(102))); // ERR_INVALID_PRICE
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("ensures that creating a listing with empty name fails", () => {
|
|
74
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
75
|
+
|
|
76
|
+
const { result } = simnet.callPublicFn(
|
|
77
|
+
CONTRACT_NAME,
|
|
78
|
+
'create-listing',
|
|
79
|
+
[
|
|
80
|
+
utf8(''),
|
|
81
|
+
utf8(LISTING_DESCRIPTION),
|
|
82
|
+
Cl.uint(LISTING_PRICE),
|
|
83
|
+
Cl.uint(LISTING_DURATION)
|
|
84
|
+
],
|
|
85
|
+
seller
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(result).toBeErr(Cl.error(Cl.uint(109))); // ERR_INVALID_INPUT
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("ensures that creating a listing with invalid duration fails", () => {
|
|
92
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
93
|
+
|
|
94
|
+
const { result } = simnet.callPublicFn(
|
|
95
|
+
CONTRACT_NAME,
|
|
96
|
+
'create-listing',
|
|
97
|
+
[
|
|
98
|
+
utf8(LISTING_NAME),
|
|
99
|
+
utf8(LISTING_DESCRIPTION),
|
|
100
|
+
Cl.uint(LISTING_PRICE),
|
|
101
|
+
Cl.uint(0)
|
|
102
|
+
],
|
|
103
|
+
seller
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(result).toBeErr(Cl.error(Cl.uint(110))); // ERR_INVALID_DURATION
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("ensures that creating a listing with duration exceeding max fails", () => {
|
|
110
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
111
|
+
|
|
112
|
+
const { result } = simnet.callPublicFn(
|
|
113
|
+
CONTRACT_NAME,
|
|
114
|
+
'create-listing',
|
|
115
|
+
[
|
|
116
|
+
utf8(LISTING_NAME),
|
|
117
|
+
utf8(LISTING_DESCRIPTION),
|
|
118
|
+
Cl.uint(LISTING_PRICE),
|
|
119
|
+
Cl.uint(52561)
|
|
120
|
+
],
|
|
121
|
+
seller
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
expect(result).toBeErr(Cl.error(Cl.uint(110))); // ERR_INVALID_DURATION
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("ensures that seller can update their own listing", () => {
|
|
128
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
129
|
+
const newPrice = 2000000;
|
|
130
|
+
const newDescription = 'Updated description';
|
|
131
|
+
|
|
132
|
+
// Create listing
|
|
133
|
+
simnet.callPublicFn(
|
|
134
|
+
CONTRACT_NAME,
|
|
135
|
+
'create-listing',
|
|
136
|
+
[
|
|
137
|
+
utf8(LISTING_NAME),
|
|
138
|
+
utf8(LISTING_DESCRIPTION),
|
|
139
|
+
Cl.uint(LISTING_PRICE),
|
|
140
|
+
Cl.uint(LISTING_DURATION)
|
|
141
|
+
],
|
|
142
|
+
seller
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Update listing
|
|
146
|
+
const { result } = simnet.callPublicFn(
|
|
147
|
+
CONTRACT_NAME,
|
|
148
|
+
'update-listing',
|
|
149
|
+
[
|
|
150
|
+
Cl.uint(1),
|
|
151
|
+
Cl.uint(newPrice),
|
|
152
|
+
utf8(newDescription)
|
|
153
|
+
],
|
|
154
|
+
seller
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(result).toBeOk(Cl.bool(true));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("ensures that non-seller cannot update a listing", () => {
|
|
161
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
162
|
+
const nonSeller = simnet.getAccounts().get('wallet_2')!;
|
|
163
|
+
|
|
164
|
+
// Create listing
|
|
165
|
+
simnet.callPublicFn(
|
|
166
|
+
CONTRACT_NAME,
|
|
167
|
+
'create-listing',
|
|
168
|
+
[
|
|
169
|
+
utf8(LISTING_NAME),
|
|
170
|
+
utf8(LISTING_DESCRIPTION),
|
|
171
|
+
Cl.uint(LISTING_PRICE),
|
|
172
|
+
Cl.uint(LISTING_DURATION)
|
|
173
|
+
],
|
|
174
|
+
seller
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Try to update as non-seller
|
|
178
|
+
const { result } = simnet.callPublicFn(
|
|
179
|
+
CONTRACT_NAME,
|
|
180
|
+
'update-listing',
|
|
181
|
+
[
|
|
182
|
+
Cl.uint(1),
|
|
183
|
+
Cl.uint(2000000),
|
|
184
|
+
utf8('Hacked description')
|
|
185
|
+
],
|
|
186
|
+
nonSeller
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
expect(result).toBeErr(Cl.error(Cl.uint(107))); // ERR_NOT_SELLER
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("ensures that seller can cancel their own listing", () => {
|
|
193
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
194
|
+
|
|
195
|
+
// Create listing
|
|
196
|
+
simnet.callPublicFn(
|
|
197
|
+
CONTRACT_NAME,
|
|
198
|
+
'create-listing',
|
|
199
|
+
[
|
|
200
|
+
utf8(LISTING_NAME),
|
|
201
|
+
utf8(LISTING_DESCRIPTION),
|
|
202
|
+
Cl.uint(LISTING_PRICE),
|
|
203
|
+
Cl.uint(LISTING_DURATION)
|
|
204
|
+
],
|
|
205
|
+
seller
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Cancel listing
|
|
209
|
+
const { result } = simnet.callPublicFn(
|
|
210
|
+
CONTRACT_NAME,
|
|
211
|
+
'cancel-listing',
|
|
212
|
+
[Cl.uint(1)],
|
|
213
|
+
seller
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
expect(result).toBeOk(Cl.bool(true));
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("ensures that non-seller cannot cancel a listing", () => {
|
|
220
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
221
|
+
const nonSeller = simnet.getAccounts().get('wallet_2')!;
|
|
222
|
+
|
|
223
|
+
// Create listing
|
|
224
|
+
simnet.callPublicFn(
|
|
225
|
+
CONTRACT_NAME,
|
|
226
|
+
'create-listing',
|
|
227
|
+
[
|
|
228
|
+
utf8(LISTING_NAME),
|
|
229
|
+
utf8(LISTING_DESCRIPTION),
|
|
230
|
+
Cl.uint(LISTING_PRICE),
|
|
231
|
+
Cl.uint(LISTING_DURATION)
|
|
232
|
+
],
|
|
233
|
+
seller
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
// Try to cancel as non-seller
|
|
237
|
+
const { result } = simnet.callPublicFn(
|
|
238
|
+
CONTRACT_NAME,
|
|
239
|
+
'cancel-listing',
|
|
240
|
+
[Cl.uint(1)],
|
|
241
|
+
nonSeller
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
expect(result).toBeErr(Cl.error(Cl.uint(107))); // ERR_NOT_SELLER
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("ensures that buyer can purchase an active listing", () => {
|
|
248
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
249
|
+
const buyer = simnet.getAccounts().get('wallet_2')!;
|
|
250
|
+
|
|
251
|
+
// Create listing
|
|
252
|
+
simnet.callPublicFn(
|
|
253
|
+
CONTRACT_NAME,
|
|
254
|
+
'create-listing',
|
|
255
|
+
[
|
|
256
|
+
utf8(LISTING_NAME),
|
|
257
|
+
utf8(LISTING_DESCRIPTION),
|
|
258
|
+
Cl.uint(LISTING_PRICE),
|
|
259
|
+
Cl.uint(LISTING_DURATION)
|
|
260
|
+
],
|
|
261
|
+
seller
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// Purchase listing
|
|
265
|
+
const { result } = simnet.callPublicFn(
|
|
266
|
+
CONTRACT_NAME,
|
|
267
|
+
'purchase-listing',
|
|
268
|
+
[Cl.uint(1)],
|
|
269
|
+
buyer
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
expect(result).toBeOk(Cl.bool(true));
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("ensures that purchasing a non-existent listing fails", () => {
|
|
276
|
+
const buyer = simnet.getAccounts().get('wallet_2')!;
|
|
277
|
+
|
|
278
|
+
const { result } = simnet.callPublicFn(
|
|
279
|
+
CONTRACT_NAME,
|
|
280
|
+
'purchase-listing',
|
|
281
|
+
[Cl.uint(999)],
|
|
282
|
+
buyer
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
expect(result).toBeErr(Cl.error(Cl.uint(111))); // ERR_INVALID_LISTING_ID
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("ensures that purchasing a cancelled listing fails", () => {
|
|
289
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
290
|
+
const buyer = simnet.getAccounts().get('wallet_2')!;
|
|
291
|
+
|
|
292
|
+
// Create listing
|
|
293
|
+
simnet.callPublicFn(
|
|
294
|
+
CONTRACT_NAME,
|
|
295
|
+
'create-listing',
|
|
296
|
+
[
|
|
297
|
+
utf8(LISTING_NAME),
|
|
298
|
+
utf8(LISTING_DESCRIPTION),
|
|
299
|
+
Cl.uint(LISTING_PRICE),
|
|
300
|
+
Cl.uint(LISTING_DURATION)
|
|
301
|
+
],
|
|
302
|
+
seller
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Cancel listing
|
|
306
|
+
simnet.callPublicFn(
|
|
307
|
+
CONTRACT_NAME,
|
|
308
|
+
'cancel-listing',
|
|
309
|
+
[Cl.uint(1)],
|
|
310
|
+
seller
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
// Try to purchase
|
|
314
|
+
const { result } = simnet.callPublicFn(
|
|
315
|
+
CONTRACT_NAME,
|
|
316
|
+
'purchase-listing',
|
|
317
|
+
[Cl.uint(1)],
|
|
318
|
+
buyer
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
expect(result).toBeErr(Cl.error(Cl.uint(106))); // ERR_INVALID_STATUS
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("ensures that purchasing an already sold listing fails", () => {
|
|
325
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
326
|
+
const buyer1 = simnet.getAccounts().get('wallet_2')!;
|
|
327
|
+
const buyer2 = simnet.getAccounts().get('wallet_3')!;
|
|
328
|
+
|
|
329
|
+
// Create listing
|
|
330
|
+
simnet.callPublicFn(
|
|
331
|
+
CONTRACT_NAME,
|
|
332
|
+
'create-listing',
|
|
333
|
+
[
|
|
334
|
+
utf8(LISTING_NAME),
|
|
335
|
+
utf8(LISTING_DESCRIPTION),
|
|
336
|
+
Cl.uint(LISTING_PRICE),
|
|
337
|
+
Cl.uint(LISTING_DURATION)
|
|
338
|
+
],
|
|
339
|
+
seller
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// First purchase
|
|
343
|
+
simnet.callPublicFn(
|
|
344
|
+
CONTRACT_NAME,
|
|
345
|
+
'purchase-listing',
|
|
346
|
+
[Cl.uint(1)],
|
|
347
|
+
buyer1
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
// Second purchase attempt
|
|
351
|
+
const { result } = simnet.callPublicFn(
|
|
352
|
+
CONTRACT_NAME,
|
|
353
|
+
'purchase-listing',
|
|
354
|
+
[Cl.uint(1)],
|
|
355
|
+
buyer2
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
expect(result).toBeErr(Cl.error(Cl.uint(106))); // ERR_INVALID_STATUS
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("ensures that purchasing an expired listing fails", () => {
|
|
362
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
363
|
+
const buyer = simnet.getAccounts().get('wallet_2')!;
|
|
364
|
+
const shortDuration = 5;
|
|
365
|
+
|
|
366
|
+
// Create listing with short duration
|
|
367
|
+
simnet.callPublicFn(
|
|
368
|
+
CONTRACT_NAME,
|
|
369
|
+
'create-listing',
|
|
370
|
+
[
|
|
371
|
+
utf8(LISTING_NAME),
|
|
372
|
+
utf8(LISTING_DESCRIPTION),
|
|
373
|
+
Cl.uint(LISTING_PRICE),
|
|
374
|
+
Cl.uint(shortDuration)
|
|
375
|
+
],
|
|
376
|
+
seller
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
// Mine blocks to expire the listing
|
|
380
|
+
simnet.mineEmptyBlocks(shortDuration + 1);
|
|
381
|
+
|
|
382
|
+
// Try to purchase expired listing
|
|
383
|
+
const { result } = simnet.callPublicFn(
|
|
384
|
+
CONTRACT_NAME,
|
|
385
|
+
'purchase-listing',
|
|
386
|
+
[Cl.uint(1)],
|
|
387
|
+
buyer
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
expect(result).toBeErr(Cl.error(Cl.uint(105))); // ERR_LISTING_EXPIRED
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("ensures that updating a cancelled listing fails", () => {
|
|
394
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
395
|
+
|
|
396
|
+
// Create listing
|
|
397
|
+
simnet.callPublicFn(
|
|
398
|
+
CONTRACT_NAME,
|
|
399
|
+
'create-listing',
|
|
400
|
+
[
|
|
401
|
+
utf8(LISTING_NAME),
|
|
402
|
+
utf8(LISTING_DESCRIPTION),
|
|
403
|
+
Cl.uint(LISTING_PRICE),
|
|
404
|
+
Cl.uint(LISTING_DURATION)
|
|
405
|
+
],
|
|
406
|
+
seller
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
// Cancel listing
|
|
410
|
+
simnet.callPublicFn(
|
|
411
|
+
CONTRACT_NAME,
|
|
412
|
+
'cancel-listing',
|
|
413
|
+
[Cl.uint(1)],
|
|
414
|
+
seller
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
// Try to update cancelled listing
|
|
418
|
+
const { result } = simnet.callPublicFn(
|
|
419
|
+
CONTRACT_NAME,
|
|
420
|
+
'update-listing',
|
|
421
|
+
[
|
|
422
|
+
Cl.uint(1),
|
|
423
|
+
Cl.uint(2000000),
|
|
424
|
+
utf8('New description')
|
|
425
|
+
],
|
|
426
|
+
seller
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
expect(result).toBeErr(Cl.error(Cl.uint(106))); // ERR_INVALID_STATUS
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("ensures that multiple listings can be created and tracked correctly", () => {
|
|
433
|
+
const seller1 = simnet.getAccounts().get('wallet_1')!;
|
|
434
|
+
const seller2 = simnet.getAccounts().get('wallet_2')!;
|
|
435
|
+
|
|
436
|
+
// Create first listing
|
|
437
|
+
const { result: result1 } = simnet.callPublicFn(
|
|
438
|
+
CONTRACT_NAME,
|
|
439
|
+
'create-listing',
|
|
440
|
+
[
|
|
441
|
+
utf8('Item 1'),
|
|
442
|
+
utf8('First item'),
|
|
443
|
+
Cl.uint(LISTING_PRICE),
|
|
444
|
+
Cl.uint(LISTING_DURATION)
|
|
445
|
+
],
|
|
446
|
+
seller1
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
// Create second listing
|
|
450
|
+
const { result: result2 } = simnet.callPublicFn(
|
|
451
|
+
CONTRACT_NAME,
|
|
452
|
+
'create-listing',
|
|
453
|
+
[
|
|
454
|
+
utf8('Item 2'),
|
|
455
|
+
utf8('Second item'),
|
|
456
|
+
Cl.uint(LISTING_PRICE * 2),
|
|
457
|
+
Cl.uint(LISTING_DURATION)
|
|
458
|
+
],
|
|
459
|
+
seller2
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
// Create third listing
|
|
463
|
+
const { result: result3 } = simnet.callPublicFn(
|
|
464
|
+
CONTRACT_NAME,
|
|
465
|
+
'create-listing',
|
|
466
|
+
[
|
|
467
|
+
utf8('Item 3'),
|
|
468
|
+
utf8('Third item'),
|
|
469
|
+
Cl.uint(LISTING_PRICE * 3),
|
|
470
|
+
Cl.uint(LISTING_DURATION)
|
|
471
|
+
],
|
|
472
|
+
seller1
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
expect(result1).toBeOk(Cl.uint(1));
|
|
476
|
+
expect(result2).toBeOk(Cl.uint(2));
|
|
477
|
+
expect(result3).toBeOk(Cl.uint(3));
|
|
478
|
+
|
|
479
|
+
// Verify last listing ID
|
|
480
|
+
const { result: lastId } = simnet.callReadOnlyFn(
|
|
481
|
+
CONTRACT_NAME,
|
|
482
|
+
'get-last-listing-id',
|
|
483
|
+
[],
|
|
484
|
+
seller1
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
expect(lastId).toBeOk(Cl.uint(3));
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it("ensures that get-listing returns none for invalid listing ID", () => {
|
|
491
|
+
const user = simnet.getAccounts().get('wallet_1')!;
|
|
492
|
+
|
|
493
|
+
const { result } = simnet.callReadOnlyFn(
|
|
494
|
+
CONTRACT_NAME,
|
|
495
|
+
'get-listing',
|
|
496
|
+
[Cl.uint(999)],
|
|
497
|
+
user
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
expect(result).toBeNone();
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("ensures that updating with invalid price fails", () => {
|
|
504
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
505
|
+
|
|
506
|
+
// Create listing
|
|
507
|
+
simnet.callPublicFn(
|
|
508
|
+
CONTRACT_NAME,
|
|
509
|
+
'create-listing',
|
|
510
|
+
[
|
|
511
|
+
utf8(LISTING_NAME),
|
|
512
|
+
utf8(LISTING_DESCRIPTION),
|
|
513
|
+
Cl.uint(LISTING_PRICE),
|
|
514
|
+
Cl.uint(LISTING_DURATION)
|
|
515
|
+
],
|
|
516
|
+
seller
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
// Try to update with invalid price
|
|
520
|
+
const { result } = simnet.callPublicFn(
|
|
521
|
+
CONTRACT_NAME,
|
|
522
|
+
'update-listing',
|
|
523
|
+
[
|
|
524
|
+
Cl.uint(1),
|
|
525
|
+
Cl.uint(0),
|
|
526
|
+
utf8('New description')
|
|
527
|
+
],
|
|
528
|
+
seller
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
expect(result).toBeErr(Cl.error(Cl.uint(102))); // ERR_INVALID_PRICE
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it("ensures that updating with empty description fails", () => {
|
|
535
|
+
const seller = simnet.getAccounts().get('wallet_1')!;
|
|
536
|
+
|
|
537
|
+
// Create listing
|
|
538
|
+
simnet.callPublicFn(
|
|
539
|
+
CONTRACT_NAME,
|
|
540
|
+
'create-listing',
|
|
541
|
+
[
|
|
542
|
+
utf8(LISTING_NAME),
|
|
543
|
+
utf8(LISTING_DESCRIPTION),
|
|
544
|
+
Cl.uint(LISTING_PRICE),
|
|
545
|
+
Cl.uint(LISTING_DURATION)
|
|
546
|
+
],
|
|
547
|
+
seller
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
// Try to update with empty description
|
|
551
|
+
const { result } = simnet.callPublicFn(
|
|
552
|
+
CONTRACT_NAME,
|
|
553
|
+
'update-listing',
|
|
554
|
+
[
|
|
555
|
+
Cl.uint(1),
|
|
556
|
+
Cl.uint(2000000),
|
|
557
|
+
utf8('')
|
|
558
|
+
],
|
|
559
|
+
seller
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
expect(result).toBeErr(Cl.error(Cl.uint(109))); // ERR_INVALID_INPUT
|
|
563
|
+
});
|
|
564
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
{
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"useDefineForClassFields": true,
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"lib": ["ESNext"],
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noImplicitAny": true,
|
|
18
|
+
"noUnusedLocals": true,
|
|
19
|
+
"noUnusedParameters": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true
|
|
21
|
+
},
|
|
22
|
+
"include": [
|
|
23
|
+
"node_modules/@hirosystems/clarinet-sdk/vitest-helpers/src",
|
|
24
|
+
"tests"
|
|
25
|
+
]
|
|
26
|
+
}
|
package/vitest.config.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
/// <reference types="vitest" />
|
|
3
|
+
|
|
4
|
+
import { defineConfig } from "vite";
|
|
5
|
+
import { vitestSetupFilePath, getClarinetVitestsArgv } from "@hirosystems/clarinet-sdk/vitest";
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
In this file, Vitest is configured so that it works seamlessly with Clarinet and the Simnet.
|
|
9
|
+
|
|
10
|
+
The `vitest-environment-clarinet` will initialise the clarinet-sdk
|
|
11
|
+
and make the `simnet` object available globally in the test files.
|
|
12
|
+
|
|
13
|
+
`vitestSetupFilePath` points to a file in the `@hirosystems/clarinet-sdk` package that does two things:
|
|
14
|
+
- run `before` hooks to initialize the simnet and `after` hooks to collect costs and coverage reports.
|
|
15
|
+
- load custom vitest matchers to work with Clarity values (such as `expect(...).toBeUint()`)
|
|
16
|
+
|
|
17
|
+
The `getClarinetVitestsArgv()` will parse options passed to the command `vitest run --`
|
|
18
|
+
- vitest run -- --manifest ./Clarinet.toml # pass a custom path
|
|
19
|
+
- vitest run -- --coverage --costs # collect coverage and cost reports
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export default defineConfig({
|
|
23
|
+
test: {
|
|
24
|
+
environment: "clarinet", // use vitest-environment-clarinet
|
|
25
|
+
pool: "forks",
|
|
26
|
+
poolOptions: {
|
|
27
|
+
threads: { singleThread: true },
|
|
28
|
+
forks: { singleFork: true },
|
|
29
|
+
},
|
|
30
|
+
setupFiles: [
|
|
31
|
+
vitestSetupFilePath,
|
|
32
|
+
// custom setup files can be added here
|
|
33
|
+
],
|
|
34
|
+
environmentOptions: {
|
|
35
|
+
clarinet: {
|
|
36
|
+
...getClarinetVitestsArgv(),
|
|
37
|
+
// add or override options
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
exclude: [
|
|
41
|
+
'**/node_modules/**',
|
|
42
|
+
'**/dist/**',
|
|
43
|
+
'**/frontend/**', // Exclude frontend tests from root config
|
|
44
|
+
'**/.{idea,git,cache,output,temp}/**',
|
|
45
|
+
'**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*'
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|