Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 2c33ecdf94c8d90b421cc222b310e1e2d179f062 395 lines 15 kB view raw
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});