Monorepo for Aesthetic.Computer
aesthetic.computer
1/**
2 * Keeps FA2 v4 Contract - Security Tests
3 *
4 * These tests verify security properties of the production contract via TzKT API.
5 * Checks authorization, duplicate prevention, pause functionality, and constraints.
6 * Safe to run against mainnet - read-only verification only.
7 */
8
9import { CONTRACTS, EXPECTED_STORAGE } from './helpers/keeps-test-helper.mjs';
10import {
11 getContractStorage,
12 getOperations,
13 getBigMapKeys,
14 getAllTokens
15} from './helpers/tzkt-helper.mjs';
16
17const RUN_KEEPS_NETWORK_TESTS =
18 process.env.RUN_KEEPS_NETWORK_TESTS === 'true' ||
19 process.env.RUN_KEEPS_V4_NETWORK_TESTS === 'true';
20const describeIfNetworkEnabled = RUN_KEEPS_NETWORK_TESTS ? describe : xdescribe;
21
22if (!RUN_KEEPS_NETWORK_TESTS) {
23 console.log(
24 '⏭️ Skipping keeps network security tests (set RUN_KEEPS_NETWORK_TESTS=true to enable)'
25 );
26}
27
28describeIfNetworkEnabled("🔒 Keeps FA2 v4 Contract - Security Tests", () => {
29 const network = 'mainnet';
30 const address = CONTRACTS.mainnet;
31
32 beforeAll(() => {
33 console.log('\n🔒 Starting Keeps v4 security test suite...\n');
34 console.log(`🎯 Target contract: ${CONTRACTS.mainnet}`);
35 console.log(`🎯 Expected admin: ${EXPECTED_STORAGE.mainnet.administrator}`);
36 });
37
38 afterAll(() => {
39 console.log('\n✅ Security test suite complete!\n');
40 });
41
42 describe("👮 Authorization Controls", () => {
43 it("should only allow admin to pause contract", async () => {
44 const storage = await getContractStorage(address, network);
45 const expectedAdmin = EXPECTED_STORAGE.mainnet.administrator;
46
47 expect(storage.administrator).toBe(expectedAdmin);
48
49 // Verify all pause operations (if any) were from admin
50 const pauseOps = await getOperations(address, 'pause', network, 100);
51
52 if (pauseOps.length > 0) {
53 pauseOps.forEach(op => {
54 expect(op.sender.address).toBe(expectedAdmin);
55 });
56 console.log(` ✅ All ${pauseOps.length} pause operations from admin`);
57 } else {
58 console.log(` ℹ️ No pause operations found (expected for production)`);
59 }
60 });
61
62 it("should only allow admin to unpause contract", async () => {
63 const storage = await getContractStorage(address, network);
64 const expectedAdmin = EXPECTED_STORAGE.mainnet.administrator;
65
66 // Verify all unpause operations (if any) were from admin
67 const unpauseOps = await getOperations(address, 'unpause', network, 100);
68
69 if (unpauseOps.length > 0) {
70 unpauseOps.forEach(op => {
71 expect(op.sender.address).toBe(expectedAdmin);
72 });
73 console.log(` ✅ All ${unpauseOps.length} unpause operations from admin`);
74 } else {
75 console.log(` ℹ️ No unpause operations found (expected for production)`);
76 }
77 });
78
79 it("should only allow admin to set royalty", async () => {
80 const storage = await getContractStorage(address, network);
81 const expectedAdmin = EXPECTED_STORAGE.mainnet.administrator;
82
83 // Verify all set_default_royalty operations (if any) were from admin
84 const royaltyOps = await getOperations(address, 'set_default_royalty', network, 100);
85
86 if (royaltyOps.length > 0) {
87 royaltyOps.forEach(op => {
88 expect(op.sender.address).toBe(expectedAdmin);
89 });
90 console.log(` ✅ All ${royaltyOps.length} royalty operations from admin`);
91 } else {
92 console.log(` ℹ️ No royalty operations found yet`);
93 }
94 });
95
96 it("should only allow admin to set keep fee", async () => {
97 const storage = await getContractStorage(address, network);
98 const expectedAdmin = EXPECTED_STORAGE.mainnet.administrator;
99
100 // Verify all set_keep_fee operations (if any) were from admin
101 const feeOps = await getOperations(address, 'set_keep_fee', network, 100);
102
103 if (feeOps.length > 0) {
104 feeOps.forEach(op => {
105 expect(op.sender.address).toBe(expectedAdmin);
106 });
107 console.log(` ✅ All ${feeOps.length} fee operations from admin`);
108 } else {
109 console.log(` ℹ️ No fee operations found yet`);
110 }
111 });
112
113 it("should only allow admin to withdraw fees", async () => {
114 const storage = await getContractStorage(address, network);
115 const expectedAdmin = EXPECTED_STORAGE.mainnet.administrator;
116
117 // Verify all withdraw_fees operations (if any) were from admin
118 const withdrawOps = await getOperations(address, 'withdraw_fees', network, 100);
119
120 if (withdrawOps.length > 0) {
121 withdrawOps.forEach(op => {
122 expect(op.sender.address).toBe(expectedAdmin);
123 });
124 console.log(` ✅ All ${withdrawOps.length} withdraw operations from admin`);
125 } else {
126 console.log(` ℹ️ No withdraw operations found yet`);
127 }
128 });
129
130 it("should only allow admin to burn keeps", async () => {
131 const storage = await getContractStorage(address, network);
132 const expectedAdmin = EXPECTED_STORAGE.mainnet.administrator;
133
134 // Verify all burn_keep operations (if any) were from admin
135 const burnOps = await getOperations(address, 'burn_keep', network, 100);
136
137 if (burnOps.length > 0) {
138 burnOps.forEach(op => {
139 expect(op.sender.address).toBe(expectedAdmin);
140 });
141 console.log(` ✅ All ${burnOps.length} burn operations from admin`);
142 } else {
143 console.log(` ℹ️ No burn operations found (expected - permanent keeps)`);
144 }
145 });
146
147 it("should only allow admin for admin_transfer", async () => {
148 const storage = await getContractStorage(address, network);
149 const expectedAdmin = EXPECTED_STORAGE.mainnet.administrator;
150
151 // Verify all admin_transfer operations (if any) were from admin
152 const transferOps = await getOperations(address, 'admin_transfer', network, 100);
153
154 if (transferOps.length > 0) {
155 transferOps.forEach(op => {
156 expect(op.sender.address).toBe(expectedAdmin);
157 });
158 console.log(` ✅ All ${transferOps.length} admin_transfer operations from admin`);
159 } else {
160 console.log(` ℹ️ No admin_transfer operations found (expected - emergency only)`);
161 }
162 });
163
164 it("should verify edit_metadata authorization", async () => {
165 const storage = await getContractStorage(address, network);
166 const expectedAdmin = EXPECTED_STORAGE.mainnet.administrator;
167
168 // Verify edit_metadata operations follow auth rules (admin/owner/creator)
169 const editOps = await getOperations(address, 'edit_metadata', network, 20);
170
171 if (editOps.length > 0) {
172 console.log(` 📊 Found ${editOps.length} edit_metadata operations`);
173
174 // All should be successful (no failed auth)
175 editOps.forEach(op => {
176 expect(op.status).toBe('applied');
177 });
178
179 console.log(` ✅ All edit_metadata operations succeeded (proper auth)`);
180 } else {
181 console.log(` ℹ️ No edit_metadata operations found yet`);
182 }
183 });
184 });
185
186 describe("🚫 Duplicate Prevention", () => {
187 it("should have no duplicate content hashes", async () => {
188 const storage = await getContractStorage(address, network);
189 const contentHashesBigMapId = storage.content_hashes;
190
191 // Get all content hashes from bigmap
192 const hashes = await getBigMapKeys(contentHashesBigMapId, network, 1000);
193
194 const hashSet = new Set();
195 const duplicates = [];
196
197 hashes.forEach(entry => {
198 const hash = entry.key;
199 if (hashSet.has(hash)) {
200 duplicates.push(hash);
201 }
202 hashSet.add(hash);
203 });
204
205 expect(duplicates.length).toBe(0);
206
207 if (hashes.length > 0) {
208 console.log(` ✅ No duplicates found in ${hashes.length} content hashes`);
209 } else {
210 console.log(` ℹ️ No content hashes yet (no mints)`);
211 }
212 });
213
214 it("should verify each keep operation used unique content hash", async () => {
215 const keepOps = await getOperations(address, 'keep', network, 100);
216
217 if (keepOps.length > 0) {
218 // All keep operations should be successful
219 keepOps.forEach(op => {
220 expect(op.status).toBe('applied');
221 });
222
223 console.log(` ✅ All ${keepOps.length} keep operations succeeded (no duplicate hash rejections)`);
224 } else {
225 console.log(` ℹ️ No keep operations found yet`);
226 }
227 });
228 });
229
230 describe("⏸️ Pause Functionality", () => {
231 it("should have correct initial pause state", async () => {
232 const storage = await getContractStorage(address, network);
233 const expectedPaused = EXPECTED_STORAGE.mainnet.paused;
234
235 expect(storage.paused).toBe(expectedPaused);
236 console.log(` ✅ Contract pause state: ${storage.paused} (expected: ${expectedPaused})`);
237 });
238
239 it("should verify pause/unpause operation history", async () => {
240 const pauseOps = await getOperations(address, 'pause', network, 100);
241 const unpauseOps = await getOperations(address, 'unpause', network, 100);
242
243 console.log(` 📊 Pause operations: ${pauseOps.length}`);
244 console.log(` 📊 Unpause operations: ${unpauseOps.length}`);
245
246 // All pause/unpause operations should be successful
247 [...pauseOps, ...unpauseOps].forEach(op => {
248 expect(op.status).toBe('applied');
249 });
250
251 if (pauseOps.length + unpauseOps.length > 0) {
252 console.log(` ✅ All pause/unpause operations succeeded`);
253 } else {
254 console.log(` ℹ️ No pause/unpause operations yet (good - stable contract)`);
255 }
256 });
257 });
258
259 describe("💰 Royalty Constraints", () => {
260 it("should enforce 25% max royalty", async () => {
261 const storage = await getContractStorage(address, network);
262 const royaltyBps = parseInt(storage.default_royalty_bps);
263
264 expect(royaltyBps).toBeLessThanOrEqual(2500); // 25% max
265 expect(royaltyBps).toBeGreaterThanOrEqual(0);
266
267 console.log(` ✅ Royalty: ${royaltyBps} bps (${royaltyBps / 100}%) within bounds [0%, 25%]`);
268 });
269
270 it("should verify all royalty changes were within bounds", async () => {
271 const royaltyOps = await getOperations(address, 'set_default_royalty', network, 100);
272
273 if (royaltyOps.length > 0) {
274 // All should be successful (no rejected out-of-bound values)
275 royaltyOps.forEach(op => {
276 expect(op.status).toBe('applied');
277 });
278
279 console.log(` ✅ All ${royaltyOps.length} royalty operations succeeded (within bounds)`);
280 } else {
281 console.log(` ℹ️ No royalty operations found yet`);
282 }
283 });
284 });
285
286 describe("🔒 Metadata Locking", () => {
287 it("should verify metadata lock operations", async () => {
288 const lockOps = await getOperations(address, 'lock_metadata', network, 100);
289
290 if (lockOps.length > 0) {
291 // All lock operations should be successful
292 lockOps.forEach(op => {
293 expect(op.status).toBe('applied');
294 });
295
296 console.log(` ✅ All ${lockOps.length} lock_metadata operations succeeded`);
297 } else {
298 console.log(` ℹ️ No lock_metadata operations found yet`);
299 }
300 });
301
302 it("should verify contract metadata not locked", async () => {
303 const storage = await getContractStorage(address, network);
304
305 expect(storage.contract_metadata_locked).toBe(false);
306 console.log(` ✅ Contract metadata not locked (allows updates)`);
307 });
308 });
309
310 describe("🎯 Token Creator Tracking", () => {
311 it("should track creators for all minted tokens", async () => {
312 const storage = await getContractStorage(address, network);
313 const nextTokenId = parseInt(storage.next_token_id);
314
315 if (nextTokenId > 0) {
316 const tokenCreatorsBigMapId = storage.token_creators;
317 const creators = await getBigMapKeys(tokenCreatorsBigMapId, network, 1000);
318
319 // Every minted token should have a creator
320 expect(creators.length).toBeGreaterThan(0);
321
322 console.log(` ✅ Creator tracking: ${creators.length} creators for ${nextTokenId} tokens`);
323 } else {
324 console.log(` ℹ️ No tokens minted yet`);
325 }
326 });
327 });
328
329 describe("📊 Operation Integrity", () => {
330 it("should verify all operations succeeded (no failures)", async () => {
331 const keepOps = await getOperations(address, 'keep', network, 100);
332 const editOps = await getOperations(address, 'edit_metadata', network, 100);
333 const transferOps = await getOperations(address, 'transfer', network, 100);
334
335 const allOps = [...keepOps, ...editOps, ...transferOps];
336
337 if (allOps.length > 0) {
338 // All operations should be successfully applied
339 allOps.forEach(op => {
340 expect(op.status).toBe('applied');
341 });
342
343 console.log(` ✅ All ${allOps.length} operations successful (no failures)`);
344 } else {
345 console.log(` ℹ️ No operations found yet`);
346 }
347 });
348
349 it("should verify no failed transactions to contract", async () => {
350 // Get recent operations (all entrypoints)
351 const recentOps = await getAllTokens(address, network, 100);
352
353 // TzKT only returns successful operations by default
354 // If we got results, they're all successful
355 if (recentOps.length > 0) {
356 console.log(` ✅ Found ${recentOps.length} successful tokens (no failed mints)`);
357 }
358 });
359 });
360
361 describe("🏗️ Storage Integrity", () => {
362 it("should have consistent token count", async () => {
363 const storage = await getContractStorage(address, network);
364 const nextTokenId = parseInt(storage.next_token_id);
365
366 // Get actual minted tokens
367 const tokens = await getAllTokens(address, network, 1000);
368
369 // next_token_id should equal number of minted tokens (0-indexed)
370 expect(tokens.length).toBeLessThanOrEqual(nextTokenId);
371
372 console.log(` ✅ Token count consistent: next_token_id=${nextTokenId}, minted=${tokens.length}`);
373 });
374
375 it("should verify bigmap IDs are valid", async () => {
376 const storage = await getContractStorage(address, network);
377
378 const bigMaps = {
379 content_hashes: storage.content_hashes,
380 token_creators: storage.token_creators,
381 metadata_locked: storage.metadata_locked,
382 ledger: storage.ledger,
383 token_metadata: storage.token_metadata,
384 operators: storage.operators
385 };
386
387 // All bigmap IDs should be positive integers
388 Object.entries(bigMaps).forEach(([name, id]) => {
389 expect(parseInt(id)).toBeGreaterThan(0);
390 });
391
392 console.log(` ✅ All 6 bigmap IDs are valid positive integers`);
393 });
394 });
395});