create-dubhe 1.2.0-pre.124 → 1.2.0-pre.125

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 (104) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/101/sui-template/packages/contracts/src/dubhe/Move.lock +0 -6
  5. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/systems/dapp_system.move +82 -42
  6. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/scene_session_test.move +269 -0
  7. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/typed_scene_test.move +25 -9
  8. package/templates/contract/sui-template/src/dubhe/Move.lock +0 -6
  9. package/templates/contract/sui-template/src/dubhe/sources/systems/dapp_system.move +82 -42
  10. package/templates/contract/sui-template/src/dubhe/sources/tests/scene_session_test.move +269 -0
  11. package/templates/contract/sui-template/src/dubhe/sources/tests/typed_scene_test.move +25 -9
  12. package/templates/nextjs/sui-card-duel/.gitignore_ +51 -0
  13. package/templates/nextjs/sui-card-duel/README.md +213 -0
  14. package/templates/nextjs/sui-card-duel/docker-compose.yml +34 -0
  15. package/templates/nextjs/sui-card-duel/mprocs.yaml +17 -0
  16. package/templates/nextjs/sui-card-duel/package.json +43 -0
  17. package/templates/nextjs/sui-card-duel/packages/client/.gitignore_ +48 -0
  18. package/templates/nextjs/sui-card-duel/packages/client/.prettierignore +1 -0
  19. package/templates/nextjs/sui-card-duel/packages/client/.prettierrc.json +8 -0
  20. package/templates/nextjs/sui-card-duel/packages/client/eslint.config.mjs +22 -0
  21. package/templates/nextjs/sui-card-duel/packages/client/next.config.mjs +8 -0
  22. package/templates/nextjs/sui-card-duel/packages/client/package.json +47 -0
  23. package/templates/nextjs/sui-card-duel/packages/client/postcss.config.mjs +8 -0
  24. package/templates/nextjs/sui-card-duel/packages/client/src/app/admin/ArenaPanel.tsx +307 -0
  25. package/templates/nextjs/sui-card-duel/packages/client/src/app/admin/page.tsx +622 -0
  26. package/templates/nextjs/sui-card-duel/packages/client/src/app/brawl/[id]/page.tsx +559 -0
  27. package/templates/nextjs/sui-card-duel/packages/client/src/app/collection/page.tsx +202 -0
  28. package/templates/nextjs/sui-card-duel/packages/client/src/app/components/CardView.tsx +66 -0
  29. package/templates/nextjs/sui-card-duel/packages/client/src/app/components/GameShell.tsx +294 -0
  30. package/templates/nextjs/sui-card-duel/packages/client/src/app/components/icons.tsx +101 -0
  31. package/templates/nextjs/sui-card-duel/packages/client/src/app/duel/[id]/page.tsx +477 -0
  32. package/templates/nextjs/sui-card-duel/packages/client/src/app/favicon.ico +0 -0
  33. package/templates/nextjs/sui-card-duel/packages/client/src/app/globals.css +85 -0
  34. package/templates/nextjs/sui-card-duel/packages/client/src/app/hooks/useGame.ts +313 -0
  35. package/templates/nextjs/sui-card-duel/packages/client/src/app/layout.tsx +24 -0
  36. package/templates/nextjs/sui-card-duel/packages/client/src/app/leaderboard/page.tsx +123 -0
  37. package/templates/nextjs/sui-card-duel/packages/client/src/app/lib/game.ts +77 -0
  38. package/templates/nextjs/sui-card-duel/packages/client/src/app/lib/scenes.ts +226 -0
  39. package/templates/nextjs/sui-card-duel/packages/client/src/app/market/page.tsx +418 -0
  40. package/templates/nextjs/sui-card-duel/packages/client/src/app/page.tsx +427 -0
  41. package/templates/nextjs/sui-card-duel/packages/client/src/app/providers.tsx +69 -0
  42. package/templates/nextjs/sui-card-duel/packages/client/tailwind.config.ts +21 -0
  43. package/templates/nextjs/sui-card-duel/packages/client/tsconfig.json +30 -0
  44. package/templates/nextjs/sui-card-duel/packages/contracts/.gitignore_ +51 -0
  45. package/templates/nextjs/sui-card-duel/packages/contracts/.prettierignore +1 -0
  46. package/templates/nextjs/sui-card-duel/packages/contracts/.prettierrc.json +8 -0
  47. package/templates/nextjs/sui-card-duel/packages/contracts/Dockerfile +5 -0
  48. package/templates/nextjs/sui-card-duel/packages/contracts/Pub.localnet.toml +13 -0
  49. package/templates/nextjs/sui-card-duel/packages/contracts/card_duel.lock.json +66 -0
  50. package/templates/nextjs/sui-card-duel/packages/contracts/docker-compose.yml +34 -0
  51. package/templates/nextjs/sui-card-duel/packages/contracts/dubhe.config.ts +150 -0
  52. package/templates/nextjs/sui-card-duel/packages/contracts/logs/combined.log +31383 -0
  53. package/templates/nextjs/sui-card-duel/packages/contracts/package.json +49 -0
  54. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/config.ts +17 -0
  55. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-brawl.ts +270 -0
  56. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-duel.ts +272 -0
  57. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-lobby.ts +177 -0
  58. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-market.ts +219 -0
  59. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-session.ts +342 -0
  60. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/query-dapp-revenue.ts +95 -0
  61. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/seed.ts +92 -0
  62. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/withdraw-dapp-revenue.ts +56 -0
  63. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/Move.toml +15 -0
  64. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/dapp_key.move +46 -0
  65. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/enums/card_kind.move +47 -0
  66. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/enums/match_state.move +37 -0
  67. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/enums/rarity.move +37 -0
  68. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/error.move +126 -0
  69. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/genesis.move +33 -0
  70. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/init_test.move +23 -0
  71. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/objects/arena.move +137 -0
  72. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/permits/brawl_permit.move +125 -0
  73. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/permits/duel_permit.move +125 -0
  74. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/battle_state.move +255 -0
  75. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/card.move +282 -0
  76. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/deck.move +64 -0
  77. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/game_config.move +267 -0
  78. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/gold.move +205 -0
  79. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/profile.move +255 -0
  80. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/scenes/brawl.move +313 -0
  81. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/scenes/duel.move +313 -0
  82. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/user_storage_init.move +26 -0
  83. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/scripts/deploy_hook.move +32 -0
  84. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/scripts/migrate.move +7 -0
  85. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/arena_system.move +57 -0
  86. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/brawl_system.move +559 -0
  87. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/card_system.move +146 -0
  88. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/duel_system.move +515 -0
  89. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/market_system.move +115 -0
  90. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/player_system.move +37 -0
  91. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/tests/brawl_tests.move +385 -0
  92. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/tests/duel_tests.move +656 -0
  93. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/tests/test_helpers.move +31 -0
  94. package/templates/nextjs/sui-card-duel/packages/contracts/sui-indexer-schema.graphql +5973 -0
  95. package/templates/nextjs/sui-card-duel/pnpm-workspace.yaml +2 -0
  96. package/templates/nextjs/sui-farm/packages/client/src/app/market/page.tsx +4 -13
  97. package/templates/nextjs/sui-farm/packages/client/src/app/page.tsx +73 -130
  98. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/permits/world.move +12 -9
  99. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/world_system.move +1 -1
  100. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/Move.lock +0 -6
  101. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/systems/dapp_system.move +82 -42
  102. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/scene_session_test.move +269 -0
  103. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/typed_scene_test.move +25 -9
  104. package/templates/nextjs/sui-farm/packages/client/src/app/hooks/useSessionKey.ts +0 -248
@@ -310,13 +310,15 @@ fun test_accept_invitation_moves_invitee_to_participants() {
310
310
  assert!(!dapp_service::is_participant_in_scene_permit(&permit, alice), 0);
311
311
  assert!(dapp_service::is_scene_invitee(dapp_service::scene_permit_meta(&permit), alice), 1);
312
312
 
313
- dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, ctx);
313
+ let us_alice = make_us(alice, ctx);
314
+ dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, &us_alice, ctx);
314
315
 
315
316
  assert!(dapp_service::is_participant_in_scene_permit(&permit, alice), 2);
316
317
  assert!(!dapp_service::is_scene_invitee(dapp_service::scene_permit_meta(&permit), alice), 3);
317
318
  assert!(dapp_service::is_scene_invitee(dapp_service::scene_permit_meta(&permit), bob), 4);
318
319
  assert!(!dapp_service::is_participant_in_scene_permit(&permit, bob), 5);
319
320
 
321
+ dapp_service::destroy_user_storage(us_alice);
320
322
  dapp_service::destroy_scene_permit_for_testing(permit);
321
323
  }
322
324
 
@@ -332,9 +334,11 @@ fun test_accept_invitation_with_expiry_in_window() {
332
334
  ctx,
333
335
  );
334
336
 
335
- dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, ctx);
337
+ let us_alice = make_us(alice, ctx);
338
+ dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, &us_alice, ctx);
336
339
  assert!(dapp_service::is_participant_in_scene_permit(&permit, alice), 0);
337
340
 
341
+ dapp_service::destroy_user_storage(us_alice);
338
342
  dapp_service::destroy_scene_permit_for_testing(permit);
339
343
  }
340
344
 
@@ -351,7 +355,9 @@ fun test_accept_invitation_expired_aborts() {
351
355
  ctx,
352
356
  );
353
357
 
354
- dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, ctx);
358
+ let us_alice = make_us(alice, ctx);
359
+ dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, &us_alice, ctx);
360
+ dapp_service::destroy_user_storage(us_alice);
355
361
  dapp_service::destroy_scene_permit_for_testing(permit);
356
362
  }
357
363
 
@@ -368,7 +374,9 @@ fun test_accept_invitation_not_invited_aborts() {
368
374
  ctx,
369
375
  );
370
376
 
371
- dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, ctx);
377
+ let us_charlie = make_us(charlie, ctx);
378
+ dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, &us_charlie, ctx);
379
+ dapp_service::destroy_user_storage(us_charlie);
372
380
  dapp_service::destroy_scene_permit_for_testing(permit);
373
381
  }
374
382
 
@@ -385,16 +393,20 @@ fun test_all_invitees_accept_flow() {
385
393
  ctx_a,
386
394
  );
387
395
 
388
- dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, ctx_a);
396
+ let us_alice = make_us(alice, ctx_a);
397
+ dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, &us_alice, ctx_a);
389
398
 
390
399
  let ctx_b = &mut tx_context::new_from_hint(bob, 0, 0, 0, 0);
391
- dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, ctx_b);
400
+ let us_bob = make_us(bob, ctx_b);
401
+ dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, &us_bob, ctx_b);
392
402
 
393
403
  assert!(dapp_service::is_participant_in_scene_permit(&permit, alice), 0);
394
404
  assert!(dapp_service::is_participant_in_scene_permit(&permit, bob), 1);
395
405
  assert!(dapp_service::scene_invitees(dapp_service::scene_permit_meta(&permit)).is_empty(), 2);
396
406
  assert!(dapp_service::scene_participant_count(dapp_service::scene_permit_meta(&permit)) == 2, 3);
397
407
 
408
+ dapp_service::destroy_user_storage(us_alice);
409
+ dapp_service::destroy_user_storage(us_bob);
398
410
  dapp_service::destroy_scene_permit_for_testing(permit);
399
411
  }
400
412
 
@@ -411,8 +423,10 @@ fun test_accept_invitation_twice_aborts() {
411
423
  ctx,
412
424
  );
413
425
 
414
- dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, ctx);
415
- dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, ctx);
426
+ let us_alice = make_us(alice, ctx);
427
+ dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, &us_alice, ctx);
428
+ dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, &us_alice, ctx);
429
+ dapp_service::destroy_user_storage(us_alice);
416
430
  dapp_service::destroy_scene_permit_for_testing(permit);
417
431
  }
418
432
 
@@ -430,7 +444,9 @@ fun test_accept_invitation_on_expired_scene_aborts() {
430
444
  ctx,
431
445
  );
432
446
 
433
- dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, ctx);
447
+ let us_alice = make_us(alice, ctx);
448
+ dapp_system::accept_scene_permit_invitation<SceneKey, SceneKey>(SceneKey {}, &mut permit, &us_alice, ctx);
449
+ dapp_service::destroy_user_storage(us_alice);
434
450
  dapp_service::destroy_scene_permit_for_testing(permit);
435
451
  }
436
452
 
@@ -39,9 +39,3 @@ source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-f
39
39
  use_environment = "testnet"
40
40
  manifest_digest = "7AFB66695545775FBFBB2D3078ADFD084244D5002392E837FDE21D9EA1C6D01C"
41
41
  deps = { MoveStdlib = "MoveStdlib" }
42
-
43
- [env.mainnet]
44
- chain-id = "35834a8a"
45
- original-published-id = "0x6cb54e8bbcf59cc976584330689073500af755b12d85bf4f56d7eea9f232b8aa"
46
- latest-published-id = "0x6cb54e8bbcf59cc976584330689073500af755b12d85bf4f56d7eea9f232b8aa"
47
- published-version = "1"
@@ -278,9 +278,10 @@ public fun delete_field<DappKey: copy + drop>(
278
278
  //
279
279
  // Reactive writes allow one participant to modify another participant's UserStorage
280
280
  // within a shared scene context. Four-layer security:
281
- // 1. ctx.sender() must be from.canonical_owner (only owner can initiate)
282
- // 2. from must be a registered scene participant
283
- // 3. target must be a registered scene participant
281
+ // 1. ctx.sender() must be authorized to write `from` (canonical owner or its
282
+ // active session key sessions are delegated proxies for in-scene actions)
283
+ // 2. from's canonical owner must be a registered scene participant
284
+ // 3. target's canonical owner must be a registered scene participant
284
285
  // 4. scene must be active (not expired)
285
286
  //
286
287
  // Write fees are charged to the initiator (`from`) under the initiator-pays model.
@@ -304,8 +305,8 @@ public fun set_record_reactive<DappKey: copy + drop, PermType>(
304
305
  let scene_id = dapp_service::scene_permit_id(permit);
305
306
  let meta = dapp_service::scene_permit_meta(permit);
306
307
 
307
- // 1. Sender must be the initiator's canonical owner.
308
- error::not_canonical_owner(ctx.sender() == dapp_service::canonical_owner(from));
308
+ // 1. Sender must be authorized to write `from` (canonical owner or active session key).
309
+ error::no_permission(dapp_service::is_write_authorized(from, ctx.sender(), ctx.epoch_timestamp_ms()));
309
310
  // 2. Initiator must be a scene participant (O(1) DF lookup).
310
311
  error::not_scene_participant(dapp_service::is_scene_participant(scene_id, dapp_service::canonical_owner(from)));
311
312
  // 3. Target must be a scene participant (O(1) DF lookup).
@@ -343,7 +344,7 @@ public fun set_field_reactive<DappKey: copy + drop, PermType>(
343
344
  let scene_id = dapp_service::scene_permit_id(permit);
344
345
  let meta = dapp_service::scene_permit_meta(permit);
345
346
 
346
- error::not_canonical_owner(ctx.sender() == dapp_service::canonical_owner(from));
347
+ error::no_permission(dapp_service::is_write_authorized(from, ctx.sender(), ctx.epoch_timestamp_ms()));
347
348
  error::not_scene_participant(dapp_service::is_scene_participant(scene_id, dapp_service::canonical_owner(from)));
348
349
  error::not_scene_participant(dapp_service::is_scene_participant(scene_id, dapp_service::canonical_owner(target)));
349
350
  error::scene_expired(dapp_service::is_scene_active(meta, ctx.epoch_timestamp_ms()));
@@ -504,19 +505,27 @@ public fun remove_object_field<DappKey: copy + drop, ObjType, T: store + copy +
504
505
 
505
506
  /// Remove and return a native-typed field from a permit-bound SceneStorage Bag.
506
507
  /// Emits a Dubhe_Scene_DeleteField event so off-chain indexers stay in sync.
508
+ /// The caller identifies as `user_storage`'s canonical owner; the transaction may be
509
+ /// signed by the canonical owner or its active session key (is_write_authorized).
507
510
  public fun remove_scene_field<DappKey: copy + drop, PermType, SceneType, T: store + copy + drop>(
508
- _auth: DappKey,
509
- permit: &ScenePermit<PermType>,
510
- storage: &mut SceneStorage<SceneType>,
511
- field_name: vector<u8>,
512
- ctx: &TxContext,
511
+ _auth: DappKey,
512
+ permit: &ScenePermit<PermType>,
513
+ storage: &mut SceneStorage<SceneType>,
514
+ user_storage: &UserStorage,
515
+ field_name: vector<u8>,
516
+ ctx: &TxContext,
513
517
  ): T {
514
518
  let dapp_key_str = type_info::get_type_name_string<DappKey>();
515
519
  error::dapp_key_mismatch(dapp_service::scene_storage_dapp_key(storage) == dapp_key_str);
516
520
  error::dapp_key_mismatch(dapp_service::scene_permit_dapp_key(permit) == dapp_key_str);
521
+ error::dapp_key_mismatch(dapp_service::user_storage_dapp_key(user_storage) == dapp_key_str);
517
522
  assert_scene_storage_bound_to_permit(permit, storage);
518
- error::scene_expired(dapp_service::is_scene_active(dapp_service::scene_permit_meta(permit), ctx.epoch_timestamp_ms()));
519
- error::not_scene_participant(dapp_service::is_participant_in_scene_permit(permit, ctx.sender()));
523
+ let now_ms = ctx.epoch_timestamp_ms();
524
+ error::scene_expired(dapp_service::is_scene_active(dapp_service::scene_permit_meta(permit), now_ms));
525
+ error::no_permission(dapp_service::is_write_authorized(user_storage, ctx.sender(), now_ms));
526
+ error::not_scene_participant(dapp_service::is_participant_in_scene_permit(
527
+ permit, dapp_service::canonical_owner(user_storage)
528
+ ));
520
529
 
521
530
  let scene_type = *dapp_service::scene_storage_type(storage);
522
531
  let scene_id = sui::object::uid_to_address(dapp_service::scene_storage_id(storage));
@@ -729,20 +738,28 @@ public fun share_scene_storage<DappKey: copy + drop, SceneType>(
729
738
  }
730
739
 
731
740
  /// Write a native-typed field into a permit-bound SceneStorage Bag and emit an event.
741
+ /// The caller identifies as `user_storage`'s canonical owner; the transaction may be
742
+ /// signed by the canonical owner or its active session key (is_write_authorized).
732
743
  public fun set_scene_field<DappKey: copy + drop, PermType, SceneType, T: store + copy + drop>(
733
- _auth: DappKey,
734
- permit: &ScenePermit<PermType>,
735
- storage: &mut SceneStorage<SceneType>,
736
- field_name: vector<u8>,
737
- value: T,
738
- ctx: &TxContext,
744
+ _auth: DappKey,
745
+ permit: &ScenePermit<PermType>,
746
+ storage: &mut SceneStorage<SceneType>,
747
+ user_storage: &UserStorage,
748
+ field_name: vector<u8>,
749
+ value: T,
750
+ ctx: &TxContext,
739
751
  ) {
740
752
  let dapp_key_str = type_info::get_type_name_string<DappKey>();
741
753
  error::dapp_key_mismatch(dapp_service::scene_storage_dapp_key(storage) == dapp_key_str);
742
754
  error::dapp_key_mismatch(dapp_service::scene_permit_dapp_key(permit) == dapp_key_str);
755
+ error::dapp_key_mismatch(dapp_service::user_storage_dapp_key(user_storage) == dapp_key_str);
743
756
  assert_scene_storage_bound_to_permit(permit, storage);
744
- error::scene_expired(dapp_service::is_scene_active(dapp_service::scene_permit_meta(permit), ctx.epoch_timestamp_ms()));
745
- error::not_scene_participant(dapp_service::is_participant_in_scene_permit(permit, ctx.sender()));
757
+ let now_ms = ctx.epoch_timestamp_ms();
758
+ error::scene_expired(dapp_service::is_scene_active(dapp_service::scene_permit_meta(permit), now_ms));
759
+ error::no_permission(dapp_service::is_write_authorized(user_storage, ctx.sender(), now_ms));
760
+ error::not_scene_participant(dapp_service::is_participant_in_scene_permit(
761
+ permit, dapp_service::canonical_owner(user_storage)
762
+ ));
746
763
 
747
764
  let event_bytes = sui::bcs::to_bytes(&value);
748
765
  dapp_service::set_scene_field(storage, field_name, value);
@@ -829,19 +846,25 @@ public fun destroy_scene_permit<DappKey: copy + drop, PermType>(
829
846
  }
830
847
 
831
848
  /// Helper: accept a scene invitation for a ScenePermit-backed scene.
832
- /// Moves ctx.sender() from the invitees list to confirmed participants.
849
+ /// Moves the caller's canonical owner from the invitees list to confirmed
850
+ /// participants. The transaction may be signed by the canonical owner or its
851
+ /// active session key (is_write_authorized) — the registered identity is
852
+ /// always the canonical owner address.
833
853
  /// Guards: scene must be active AND the invitation window must not have expired.
834
854
  public fun accept_scene_permit_invitation<DappKey: copy + drop, PermType>(
835
- _auth: DappKey,
836
- permit: &mut ScenePermit<PermType>,
837
- ctx: &TxContext,
855
+ _auth: DappKey,
856
+ permit: &mut ScenePermit<PermType>,
857
+ user_storage: &UserStorage,
858
+ ctx: &TxContext,
838
859
  ) {
839
860
  let dapp_key_str = type_info::get_type_name_string<DappKey>();
840
861
  error::dapp_key_mismatch(dapp_service::scene_permit_dapp_key(permit) == dapp_key_str);
862
+ error::dapp_key_mismatch(dapp_service::user_storage_dapp_key(user_storage) == dapp_key_str);
841
863
 
842
864
  // Check both permit activity and invitation window before mutating.
843
865
  // The immutable borrow of `meta` is released at the end of this block.
844
866
  let now_ms = ctx.epoch_timestamp_ms();
867
+ error::no_permission(dapp_service::is_write_authorized(user_storage, ctx.sender(), now_ms));
845
868
  {
846
869
  let meta = dapp_service::scene_permit_meta(permit);
847
870
  error::scene_expired(dapp_service::is_scene_active(meta, now_ms));
@@ -851,54 +874,71 @@ public fun accept_scene_permit_invitation<DappKey: copy + drop, PermType>(
851
874
  };
852
875
  };
853
876
 
854
- dapp_service::accept_invitation_in_scene_permit(permit, ctx.sender());
877
+ let owner = dapp_service::canonical_owner(user_storage);
878
+ dapp_service::accept_invitation_in_scene_permit(permit, owner);
855
879
  dubhe_events::emit_scene_permit_accept(
856
880
  dapp_key_str,
857
881
  *dapp_service::scene_permit_type(permit),
858
882
  sui::object::uid_to_address(dapp_service::scene_permit_id(permit)),
859
- ctx.sender(),
883
+ owner,
860
884
  );
861
885
  }
862
886
 
863
- /// Helper: add the caller as a confirmed participant in a ScenePermit.
887
+ /// Helper: add the caller's canonical owner as a confirmed participant in a
888
+ /// ScenePermit. The transaction may be signed by the canonical owner or its
889
+ /// active session key (is_write_authorized) — the registered identity is
890
+ /// always the canonical owner address.
864
891
  /// The permit must still be active — joining an expired permit is meaningless and
865
892
  /// wastes gas on a DF write that can never be used for reactive writes.
866
893
  public fun join_scene_permit<DappKey: copy + drop, PermType>(
867
- _auth: DappKey,
868
- permit: &mut ScenePermit<PermType>,
869
- ctx: &TxContext,
894
+ _auth: DappKey,
895
+ permit: &mut ScenePermit<PermType>,
896
+ user_storage: &UserStorage,
897
+ ctx: &TxContext,
870
898
  ) {
871
899
  let dapp_key_str = type_info::get_type_name_string<DappKey>();
872
900
  error::dapp_key_mismatch(dapp_service::scene_permit_dapp_key(permit) == dapp_key_str);
873
- error::scene_expired(dapp_service::is_scene_active(dapp_service::scene_permit_meta(permit), ctx.epoch_timestamp_ms()));
874
- let was_participant = dapp_service::is_participant_in_scene_permit(permit, ctx.sender());
875
- dapp_service::add_participant_in_scene_permit(permit, ctx.sender());
901
+ error::dapp_key_mismatch(dapp_service::user_storage_dapp_key(user_storage) == dapp_key_str);
902
+ let now_ms = ctx.epoch_timestamp_ms();
903
+ error::scene_expired(dapp_service::is_scene_active(dapp_service::scene_permit_meta(permit), now_ms));
904
+ error::no_permission(dapp_service::is_write_authorized(user_storage, ctx.sender(), now_ms));
905
+ let owner = dapp_service::canonical_owner(user_storage);
906
+ let was_participant = dapp_service::is_participant_in_scene_permit(permit, owner);
907
+ dapp_service::add_participant_in_scene_permit(permit, owner);
876
908
  if (!was_participant) {
877
909
  dubhe_events::emit_scene_permit_join(
878
910
  dapp_key_str,
879
911
  *dapp_service::scene_permit_type(permit),
880
912
  sui::object::uid_to_address(dapp_service::scene_permit_id(permit)),
881
- ctx.sender(),
913
+ owner,
882
914
  );
883
915
  };
884
916
  }
885
917
 
886
- /// Helper: remove the caller from participants in a SceneStorage scene.
918
+ /// Helper: remove the caller's canonical owner from participants in a
919
+ /// SceneStorage scene. The transaction may be signed by the canonical owner or
920
+ /// its active session key (is_write_authorized).
887
921
  public fun leave_scene_permit<DappKey: copy + drop, PermType>(
888
- _auth: DappKey,
889
- permit: &mut ScenePermit<PermType>,
890
- ctx: &TxContext,
922
+ _auth: DappKey,
923
+ permit: &mut ScenePermit<PermType>,
924
+ user_storage: &UserStorage,
925
+ ctx: &TxContext,
891
926
  ) {
892
927
  let dapp_key_str = type_info::get_type_name_string<DappKey>();
893
928
  error::dapp_key_mismatch(dapp_service::scene_permit_dapp_key(permit) == dapp_key_str);
894
- let was_participant = dapp_service::is_participant_in_scene_permit(permit, ctx.sender());
895
- dapp_service::remove_participant_in_scene_permit(permit, ctx.sender());
929
+ error::dapp_key_mismatch(dapp_service::user_storage_dapp_key(user_storage) == dapp_key_str);
930
+ error::no_permission(dapp_service::is_write_authorized(
931
+ user_storage, ctx.sender(), ctx.epoch_timestamp_ms()
932
+ ));
933
+ let owner = dapp_service::canonical_owner(user_storage);
934
+ let was_participant = dapp_service::is_participant_in_scene_permit(permit, owner);
935
+ dapp_service::remove_participant_in_scene_permit(permit, owner);
896
936
  if (was_participant) {
897
937
  dubhe_events::emit_scene_permit_leave(
898
938
  dapp_key_str,
899
939
  *dapp_service::scene_permit_type(permit),
900
940
  sui::object::uid_to_address(dapp_service::scene_permit_id(permit)),
901
- ctx.sender(),
941
+ owner,
902
942
  );
903
943
  };
904
944
  }
@@ -0,0 +1,269 @@
1
+ /// Unit tests — Session keys acting in scenes
2
+ ///
3
+ /// Sessions are delegated proxies: a transaction signed by an active session
4
+ /// key must be able to perform in-scene actions on behalf of its canonical
5
+ /// owner, while the on-chain identity (permit participants, scene events)
6
+ /// always resolves to the canonical owner address.
7
+ ///
8
+ /// Covers:
9
+ /// reactive writes:
10
+ /// session key may initiate set_record_reactive / set_field_reactive
11
+ /// expired session is rejected
12
+ /// stranger (neither owner nor session) is rejected
13
+ /// permit participation:
14
+ /// join_scene_permit registers the canonical owner, not the session address
15
+ /// accept_scene_permit_invitation moves the canonical owner to participants
16
+ /// leave_scene_permit removes the canonical owner
17
+ /// unauthorized sender cannot join on someone else's behalf
18
+ /// scene field writes:
19
+ /// session key may set_scene_field / remove_scene_field
20
+ /// non-participant canonical owner is rejected
21
+ #[test_only]
22
+ module dubhe::scene_session_test;
23
+
24
+ use dubhe::dapp_service::{Self, UserStorage, ScenePermit};
25
+ use dubhe::dapp_system;
26
+ use sui::bcs::to_bytes;
27
+ use sui::tx_context;
28
+
29
+ public struct SceneSessionKey has copy, drop {}
30
+
31
+ const OWNER: address = @0xA1;
32
+ const SESSION: address = @0xA2;
33
+ const PEER: address = @0xB1;
34
+ const OTHER: address = @0xC1;
35
+
36
+ const NOW: u64 = 1_000;
37
+ const SESSION_EXPIRES: u64 = 10_000;
38
+
39
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
40
+
41
+ fun make_us(owner: address, ctx: &mut TxContext): UserStorage {
42
+ dapp_service::create_user_storage_for_testing<SceneSessionKey>(owner, ctx)
43
+ }
44
+
45
+ /// UserStorage owned by `owner` with an active session key bound to SESSION.
46
+ fun make_us_with_session(owner: address, ctx: &mut TxContext): UserStorage {
47
+ let mut us = make_us(owner, ctx);
48
+ dapp_service::set_session_key_for_testing(&mut us, SESSION, SESSION_EXPIRES);
49
+ us
50
+ }
51
+
52
+ fun make_permit(
53
+ participants: vector<address>,
54
+ ctx: &mut TxContext,
55
+ ): ScenePermit<SceneSessionKey> {
56
+ dapp_service::create_scene_permit_for_testing<SceneSessionKey, SceneSessionKey>(
57
+ participants, std::option::none(), std::option::none(), ctx,
58
+ )
59
+ }
60
+
61
+ /// Transaction context whose sender is `sender` at epoch time NOW.
62
+ fun ctx_for(sender: address): TxContext {
63
+ tx_context::new_from_hint(sender, 0, 0, NOW, 0)
64
+ }
65
+
66
+ fun key_for(name: vector<u8>): vector<vector<u8>> { vector[name] }
67
+
68
+ // ═══════════════════════════════════════════════════════════════════════════════
69
+ // Reactive writes initiated by a session key
70
+ // ═══════════════════════════════════════════════════════════════════════════════
71
+
72
+ #[test]
73
+ fun test_session_can_initiate_reactive_write() {
74
+ let mut ctx = ctx_for(SESSION);
75
+
76
+ let permit = make_permit(vector[OWNER, PEER], &mut ctx);
77
+ let mut from = make_us_with_session(OWNER, &mut ctx);
78
+ let mut target = make_us(PEER, &mut ctx);
79
+
80
+ dapp_system::set_record_reactive<SceneSessionKey, SceneSessionKey>(
81
+ SceneSessionKey {}, &permit, &mut from, &mut target,
82
+ key_for(b"hp"), vector[b"v"], vector[to_bytes(&100u64)], &mut ctx,
83
+ );
84
+ dapp_system::set_field_reactive<SceneSessionKey, SceneSessionKey>(
85
+ SceneSessionKey {}, &permit, &mut from, &mut target,
86
+ key_for(b"hp"), b"v", to_bytes(&80u64), &mut ctx,
87
+ );
88
+
89
+ assert!(dapp_service::has_user_record<SceneSessionKey>(&target, key_for(b"hp")), 0);
90
+ // Fees are charged to the initiator's storage, not the target's.
91
+ assert!(dapp_service::write_count(&from) == 2, 1);
92
+ assert!(dapp_service::write_count(&target) == 0, 2);
93
+
94
+ dapp_service::destroy_user_storage(from);
95
+ dapp_service::destroy_user_storage(target);
96
+ dapp_service::destroy_scene_permit_for_testing(permit);
97
+ }
98
+
99
+ #[test]
100
+ #[expected_failure]
101
+ fun test_expired_session_reactive_aborts() {
102
+ // Session expires exactly at SESSION_EXPIRES — a transaction at that time is rejected.
103
+ let mut ctx = tx_context::new_from_hint(SESSION, 0, 0, SESSION_EXPIRES, 0);
104
+
105
+ let permit = make_permit(vector[OWNER, PEER], &mut ctx);
106
+ let mut from = make_us_with_session(OWNER, &mut ctx);
107
+ let mut target = make_us(PEER, &mut ctx);
108
+
109
+ dapp_system::set_record_reactive<SceneSessionKey, SceneSessionKey>(
110
+ SceneSessionKey {}, &permit, &mut from, &mut target,
111
+ key_for(b"hp"), vector[b"v"], vector[to_bytes(&100u64)], &mut ctx,
112
+ );
113
+
114
+ dapp_service::destroy_user_storage(from);
115
+ dapp_service::destroy_user_storage(target);
116
+ dapp_service::destroy_scene_permit_for_testing(permit);
117
+ }
118
+
119
+ #[test]
120
+ #[expected_failure]
121
+ fun test_stranger_reactive_aborts() {
122
+ let mut ctx = ctx_for(OTHER);
123
+
124
+ let permit = make_permit(vector[OWNER, PEER], &mut ctx);
125
+ let mut from = make_us_with_session(OWNER, &mut ctx);
126
+ let mut target = make_us(PEER, &mut ctx);
127
+
128
+ dapp_system::set_record_reactive<SceneSessionKey, SceneSessionKey>(
129
+ SceneSessionKey {}, &permit, &mut from, &mut target,
130
+ key_for(b"hp"), vector[b"v"], vector[to_bytes(&100u64)], &mut ctx,
131
+ );
132
+
133
+ dapp_service::destroy_user_storage(from);
134
+ dapp_service::destroy_user_storage(target);
135
+ dapp_service::destroy_scene_permit_for_testing(permit);
136
+ }
137
+
138
+ // ═══════════════════════════════════════════════════════════════════════════════
139
+ // Permit participation through a session key registers the canonical owner
140
+ // ═══════════════════════════════════════════════════════════════════════════════
141
+
142
+ #[test]
143
+ fun test_session_join_registers_canonical_owner() {
144
+ let mut ctx = ctx_for(SESSION);
145
+
146
+ let mut permit = make_permit(vector[], &mut ctx);
147
+ let us = make_us_with_session(OWNER, &mut ctx);
148
+
149
+ dapp_system::join_scene_permit<SceneSessionKey, SceneSessionKey>(
150
+ SceneSessionKey {}, &mut permit, &us, &ctx,
151
+ );
152
+
153
+ assert!(dapp_service::is_participant_in_scene_permit(&permit, OWNER), 0);
154
+ assert!(!dapp_service::is_participant_in_scene_permit(&permit, SESSION), 1);
155
+
156
+ dapp_service::destroy_user_storage(us);
157
+ dapp_service::destroy_scene_permit_for_testing(permit);
158
+ }
159
+
160
+ #[test]
161
+ fun test_session_accept_invitation_registers_canonical_owner() {
162
+ let mut ctx = ctx_for(SESSION);
163
+
164
+ let mut permit = dapp_service::create_scene_permit_with_invitations_for_testing<
165
+ SceneSessionKey, SceneSessionKey
166
+ >(vector[OWNER], std::option::none(), std::option::none(), &mut ctx);
167
+ let us = make_us_with_session(OWNER, &mut ctx);
168
+
169
+ dapp_system::accept_scene_permit_invitation<SceneSessionKey, SceneSessionKey>(
170
+ SceneSessionKey {}, &mut permit, &us, &ctx,
171
+ );
172
+
173
+ assert!(dapp_service::is_participant_in_scene_permit(&permit, OWNER), 0);
174
+ assert!(!dapp_service::is_participant_in_scene_permit(&permit, SESSION), 1);
175
+
176
+ dapp_service::destroy_user_storage(us);
177
+ dapp_service::destroy_scene_permit_for_testing(permit);
178
+ }
179
+
180
+ #[test]
181
+ fun test_session_leave_removes_canonical_owner() {
182
+ let mut ctx = ctx_for(SESSION);
183
+
184
+ let mut permit = make_permit(vector[OWNER], &mut ctx);
185
+ let us = make_us_with_session(OWNER, &mut ctx);
186
+
187
+ dapp_system::leave_scene_permit<SceneSessionKey, SceneSessionKey>(
188
+ SceneSessionKey {}, &mut permit, &us, &ctx,
189
+ );
190
+
191
+ assert!(!dapp_service::is_participant_in_scene_permit(&permit, OWNER), 0);
192
+
193
+ dapp_service::destroy_user_storage(us);
194
+ dapp_service::destroy_scene_permit_for_testing(permit);
195
+ }
196
+
197
+ #[test]
198
+ #[expected_failure]
199
+ fun test_join_unauthorized_sender_aborts() {
200
+ // OTHER is neither the canonical owner nor the session key of `us`.
201
+ let mut ctx = ctx_for(OTHER);
202
+
203
+ let mut permit = make_permit(vector[], &mut ctx);
204
+ let us = make_us_with_session(OWNER, &mut ctx);
205
+
206
+ dapp_system::join_scene_permit<SceneSessionKey, SceneSessionKey>(
207
+ SceneSessionKey {}, &mut permit, &us, &ctx,
208
+ );
209
+
210
+ dapp_service::destroy_user_storage(us);
211
+ dapp_service::destroy_scene_permit_for_testing(permit);
212
+ }
213
+
214
+ // ═══════════════════════════════════════════════════════════════════════════════
215
+ // Scene field writes through a session key
216
+ // ═══════════════════════════════════════════════════════════════════════════════
217
+
218
+ #[test]
219
+ fun test_session_set_and_remove_scene_field() {
220
+ let mut ctx = ctx_for(SESSION);
221
+
222
+ let ds = dapp_service::create_dapp_storage_for_testing<SceneSessionKey>(&mut ctx);
223
+ let permit = make_permit(vector[OWNER], &mut ctx);
224
+ let us = make_us_with_session(OWNER, &mut ctx);
225
+
226
+ let mut scene = dapp_system::new_typed_scene_with_permit<
227
+ SceneSessionKey, SceneSessionKey, SceneSessionKey
228
+ >(SceneSessionKey {}, &ds, &permit, b"battle", &mut ctx);
229
+
230
+ dapp_system::set_scene_field<SceneSessionKey, SceneSessionKey, SceneSessionKey, u64>(
231
+ SceneSessionKey {}, &permit, &mut scene, &us, b"hp", 100u64, &ctx,
232
+ );
233
+ assert!(dapp_system::get_scene_field<SceneSessionKey, u64>(&scene, b"hp") == 100, 0);
234
+
235
+ let removed: u64 = dapp_system::remove_scene_field<
236
+ SceneSessionKey, SceneSessionKey, SceneSessionKey, u64
237
+ >(SceneSessionKey {}, &permit, &mut scene, &us, b"hp", &ctx);
238
+ assert!(removed == 100, 1);
239
+
240
+ dapp_system::destroy_typed_scene<SceneSessionKey, SceneSessionKey>(SceneSessionKey {}, scene);
241
+ dapp_service::destroy_user_storage(us);
242
+ dapp_service::destroy_scene_permit_for_testing(permit);
243
+ dapp_system::destroy_dapp_storage(ds);
244
+ }
245
+
246
+ #[test]
247
+ #[expected_failure]
248
+ fun test_scene_field_non_participant_aborts() {
249
+ // `us` belongs to OTHER who never joined the permit — write must abort
250
+ // even though OTHER signed the transaction themselves.
251
+ let mut ctx = ctx_for(OTHER);
252
+
253
+ let ds = dapp_service::create_dapp_storage_for_testing<SceneSessionKey>(&mut ctx);
254
+ let permit = make_permit(vector[OWNER], &mut ctx);
255
+ let us = make_us(OTHER, &mut ctx);
256
+
257
+ let mut scene = dapp_system::new_typed_scene_with_permit<
258
+ SceneSessionKey, SceneSessionKey, SceneSessionKey
259
+ >(SceneSessionKey {}, &ds, &permit, b"battle", &mut ctx);
260
+
261
+ dapp_system::set_scene_field<SceneSessionKey, SceneSessionKey, SceneSessionKey, u64>(
262
+ SceneSessionKey {}, &permit, &mut scene, &us, b"hp", 100u64, &ctx,
263
+ );
264
+
265
+ dapp_system::destroy_typed_scene<SceneSessionKey, SceneSessionKey>(SceneSessionKey {}, scene);
266
+ dapp_service::destroy_user_storage(us);
267
+ dapp_service::destroy_scene_permit_for_testing(permit);
268
+ dapp_system::destroy_dapp_storage(ds);
269
+ }