@peeramid-labs/sdk 3.7.4 → 3.8.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.
Files changed (83) hide show
  1. package/cli/cli/commands/blockchain/index.js +9 -0
  2. package/cli/cli/commands/blockchain/index.js.map +1 -0
  3. package/cli/cli/commands/blockchain/mine.js +45 -0
  4. package/cli/cli/commands/blockchain/mine.js.map +1 -0
  5. package/cli/cli/commands/distributions/add.js +55 -31
  6. package/cli/cli/commands/distributions/add.js.map +1 -1
  7. package/cli/cli/commands/fellowship/create.js +96 -60
  8. package/cli/cli/commands/fellowship/create.js.map +1 -1
  9. package/cli/cli/commands/fellowship/game/cancel.js +45 -0
  10. package/cli/cli/commands/fellowship/game/cancel.js.map +1 -0
  11. package/cli/cli/commands/fellowship/game/create.js +91 -0
  12. package/cli/cli/commands/fellowship/game/create.js.map +1 -0
  13. package/cli/cli/commands/fellowship/{endTurn.js → game/end-turn.js} +8 -7
  14. package/cli/cli/commands/fellowship/game/end-turn.js.map +1 -0
  15. package/cli/cli/commands/fellowship/game/index.js +31 -0
  16. package/cli/cli/commands/fellowship/game/index.js.map +1 -0
  17. package/cli/cli/commands/fellowship/game/join.js +131 -0
  18. package/cli/cli/commands/fellowship/game/join.js.map +1 -0
  19. package/cli/cli/commands/fellowship/{games.js → game/list.js} +5 -5
  20. package/cli/cli/commands/fellowship/game/list.js.map +1 -0
  21. package/cli/cli/commands/fellowship/game/propose.js +178 -0
  22. package/cli/cli/commands/fellowship/game/propose.js.map +1 -0
  23. package/cli/cli/commands/fellowship/game/start.js +117 -0
  24. package/cli/cli/commands/fellowship/game/start.js.map +1 -0
  25. package/cli/cli/commands/fellowship/game/vote.js +114 -0
  26. package/cli/cli/commands/fellowship/game/vote.js.map +1 -0
  27. package/cli/cli/commands/fellowship/index.js +3 -5
  28. package/cli/cli/commands/fellowship/index.js.map +1 -1
  29. package/cli/cli/commands/getPk.js +48 -0
  30. package/cli/cli/commands/getPk.js.map +1 -0
  31. package/cli/cli/commands/playbook.js +92 -0
  32. package/cli/cli/commands/playbook.js.map +1 -0
  33. package/cli/cli/getPk.js +62 -0
  34. package/cli/cli/getPk.js.map +1 -0
  35. package/cli/cli/helpers.js +64 -0
  36. package/cli/cli/helpers.js.map +1 -0
  37. package/cli/cli/index.js +6 -0
  38. package/cli/cli/index.js.map +1 -1
  39. package/cli/cli/utils.js +64 -0
  40. package/cli/cli/utils.js.map +1 -0
  41. package/cli/rankify/GameMaster.js +199 -209
  42. package/cli/rankify/GameMaster.js.map +1 -1
  43. package/cli/rankify/InstanceBase.js +60 -36
  44. package/cli/rankify/InstanceBase.js.map +1 -1
  45. package/cli/rankify/Player.js +355 -0
  46. package/cli/rankify/Player.js.map +1 -0
  47. package/cli/utils/blockchain.js +62 -0
  48. package/cli/utils/blockchain.js.map +1 -0
  49. package/docs/classes/GameMaster.md +23 -63
  50. package/docs/classes/InstanceBase.md +21 -27
  51. package/docs/classes/InstancePlayer.md +25 -31
  52. package/docs/docs/classes/GameMaster.md +23 -63
  53. package/docs/docs/classes/InstanceBase.md +21 -27
  54. package/docs/docs/classes/InstancePlayer.md +25 -31
  55. package/docs/docs/index.md +1 -1
  56. package/docs/index.md +1 -1
  57. package/lib.commonjs/rankify/GameMaster.d.ts +26 -33
  58. package/lib.commonjs/rankify/GameMaster.d.ts.map +1 -1
  59. package/lib.commonjs/rankify/GameMaster.js +199 -209
  60. package/lib.commonjs/rankify/GameMaster.js.map +1 -1
  61. package/lib.commonjs/rankify/InstanceBase.d.ts +64 -50
  62. package/lib.commonjs/rankify/InstanceBase.d.ts.map +1 -1
  63. package/lib.commonjs/rankify/InstanceBase.js +60 -36
  64. package/lib.commonjs/rankify/InstanceBase.js.map +1 -1
  65. package/lib.commonjs/utils/blockchain.d.ts +32 -0
  66. package/lib.commonjs/utils/blockchain.d.ts.map +1 -0
  67. package/lib.commonjs/utils/blockchain.js +62 -0
  68. package/lib.commonjs/utils/blockchain.js.map +1 -0
  69. package/lib.esm/rankify/GameMaster.d.ts +26 -33
  70. package/lib.esm/rankify/GameMaster.d.ts.map +1 -1
  71. package/lib.esm/rankify/GameMaster.js +199 -209
  72. package/lib.esm/rankify/GameMaster.js.map +1 -1
  73. package/lib.esm/rankify/InstanceBase.d.ts +64 -50
  74. package/lib.esm/rankify/InstanceBase.d.ts.map +1 -1
  75. package/lib.esm/rankify/InstanceBase.js +60 -36
  76. package/lib.esm/rankify/InstanceBase.js.map +1 -1
  77. package/lib.esm/utils/blockchain.d.ts +32 -0
  78. package/lib.esm/utils/blockchain.d.ts.map +1 -0
  79. package/lib.esm/utils/blockchain.js +58 -0
  80. package/lib.esm/utils/blockchain.js.map +1 -0
  81. package/package.json +1 -1
  82. package/cli/cli/commands/fellowship/endTurn.js.map +0 -1
  83. package/cli/cli/commands/fellowship/games.js.map +0 -1
@@ -26,6 +26,7 @@ class GameMaster {
26
26
  walletClient;
27
27
  publicClient;
28
28
  chainId;
29
+ maxSlotSizeForProofs = 15;
29
30
  /**
30
31
  * Creates a new GameMaster instance
31
32
 
@@ -74,39 +75,50 @@ class GameMaster {
74
75
  * @param proposer - Optional proposer address to filter proposals
75
76
  * @returns Array of decrypted proposals with proposer addresses
76
77
  */
77
- decryptProposals = async ({ instanceAddress, gameId, turn, proposer, }) => {
78
- (0, log_1.logger)(`Getting proposals for instance ${instanceAddress}, game ${gameId}, turn ${turn.toString()}, proposer ${proposer}`);
79
- const evts = await this.publicClient.getContractEvents({
78
+ decryptProposals = async ({ instanceAddress, gameId, turn, players, padToMaxSize = false, permute = false, }) => {
79
+ (0, log_1.logger)(`Getting proposals for instance ${instanceAddress}, game ${gameId}, turn ${turn.toString()}`);
80
+ const ProposalSubmittedEvents = await this.publicClient.getContractEvents({
80
81
  abi: abis_1.RankifyDiamondInstanceAbi,
81
82
  address: instanceAddress,
82
83
  eventName: "ProposalSubmitted",
83
- args: { gameId: gameId, turn: turn, proposer: proposer },
84
+ args: { gameId: gameId, turn: turn },
84
85
  fromBlock: 0n,
85
86
  });
86
- (0, log_1.logger)(`Found ${evts.length} proposals`);
87
+ (0, log_1.logger)(`Found ${ProposalSubmittedEvents.length} proposals`);
87
88
  const instance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
88
- if (evts.length == 0)
89
- return [];
90
- (0, log_1.logger)(`Decrypting ${evts.length} proposals`);
91
- const proposals = await Promise.all(evts.map(async (log) => {
92
- (0, log_1.logger)(`Decrypting proposal ${log.args.proposer}`);
93
- if (!log.args.proposer)
94
- throw new Error("No proposer");
95
- if (!log.args.encryptedProposal)
96
- throw new Error("No proposalEncryptedByGM");
97
- return {
98
- proposer: log.args.proposer,
99
- proposal: await this.decryptProposal({
100
- proposal: log.args.encryptedProposal,
101
- turn: turn,
102
- instanceAddress: instanceAddress,
103
- gameId: gameId,
89
+ (0, log_1.logger)(`Decrypting ${ProposalSubmittedEvents.length} proposals`);
90
+ const proposalsForPlayers = await Promise.all((players)?.map(async (player) => {
91
+ const log = ProposalSubmittedEvents.find((log) => log.args.proposer === player);
92
+ if (!log) {
93
+ return {
94
+ proposer: player,
95
+ proposal: "",
96
+ };
97
+ }
98
+ else {
99
+ (0, log_1.logger)(`Decrypting proposal ${log.args.proposer}`);
100
+ if (!log.args.proposer)
101
+ throw new Error("No proposer");
102
+ if (!log.args.encryptedProposal)
103
+ throw new Error("No proposalEncryptedByGM");
104
+ return {
104
105
  proposer: log.args.proposer,
105
- instance,
106
- }),
107
- };
106
+ proposal: await this.decryptProposal({
107
+ proposal: log.args.encryptedProposal,
108
+ turn: turn,
109
+ instanceAddress: instanceAddress,
110
+ gameId: gameId,
111
+ proposer: log.args.proposer,
112
+ instance,
113
+ }),
114
+ };
115
+ }
108
116
  }));
109
- return proposals;
117
+ if (permute) {
118
+ const proposalsPermuted = await this.permuteArray({ array: proposalsForPlayers, gameId, turn, verifierAddress: instanceAddress });
119
+ return padToMaxSize ? this.padProposalsArrayWithZeroAddress(proposalsPermuted) : proposalsPermuted;
120
+ }
121
+ return padToMaxSize ? this.padProposalsArrayWithZeroAddress(proposalsForPlayers) : proposalsForPlayers;
110
122
  };
111
123
  /**
112
124
  * Generates a deterministic permutation for a specific game turn
@@ -117,11 +129,10 @@ class GameMaster {
117
129
  * @returns The generated permutation, secret, and commitment
118
130
  */
119
131
  getPermutation = async ({ gameId, turn, size, verifierAddress, }) => {
120
- const maxSize = 15;
121
132
  const turnSalt = await this.getTurnSalt({ gameId, turn, verifierAddress });
122
133
  // Create deterministic seed from game parameters and GM's signature
123
134
  // Use the seed to generate permutation
124
- const permutation = Array.from({ length: maxSize }, (_, i) => i);
135
+ const permutation = Array.from({ length: this.maxSlotSizeForProofs }, (_, i) => i);
125
136
  // Fisher-Yates shuffle with deterministic randomness
126
137
  for (let i = size - 1; i >= 0; i--) {
127
138
  // Generate deterministic random number for this position
@@ -132,7 +143,7 @@ class GameMaster {
132
143
  [permutation[i], permutation[j]] = [permutation[j], permutation[i]];
133
144
  }
134
145
  // Ensure inactive slots map to themselves
135
- for (let i = size; i < maxSize; i++) {
146
+ for (let i = size; i < this.maxSlotSizeForProofs; i++) {
136
147
  permutation[i] = i;
137
148
  }
138
149
  return { permutation, turnSalt };
@@ -157,7 +168,7 @@ class GameMaster {
157
168
  const commitment = BigInt(poseidon.F.toObject(poseidon([PoseidonThird, turnSalt])));
158
169
  return {
159
170
  permutation,
160
- secret: turnSalt,
171
+ turnSalt,
161
172
  commitment,
162
173
  };
163
174
  };
@@ -226,61 +237,11 @@ class GameMaster {
226
237
  verifierAddress,
227
238
  size,
228
239
  }).then((perm) => {
229
- return (0, viem_1.keccak256)((0, viem_1.encodePacked)(["address", "uint256"], [player, perm.secret]));
240
+ return (0, viem_1.keccak256)((0, viem_1.encodePacked)(["address", "uint256"], [player, perm.turnSalt]));
230
241
  });
231
242
  (0, log_1.logger)(`Generated vote salt for player ${player}`);
232
243
  return result;
233
244
  };
234
- getProposalsVotedUpon = async ({ instanceAddress, gameId, turn, }) => {
235
- const oldProposals = [];
236
- //Proposals sequence is directly corresponding to proposers sequence
237
- if (turn != 1n) {
238
- (0, log_1.logger)(`Getting proposals voted upon for game ${gameId.toString()}, turn ${(turn - 1n).toString()}`, 3);
239
- const endedEvents = await this.publicClient.getContractEvents({
240
- address: instanceAddress,
241
- abi: abis_1.RankifyDiamondInstanceAbi,
242
- eventName: "TurnEnded",
243
- args: { gameId, turn: turn - 1n },
244
- fromBlock: 0n,
245
- });
246
- const evt = endedEvents[0];
247
- (0, log_1.logger)("evt:", 3);
248
- (0, log_1.logger)(evt, 3);
249
- if (endedEvents.length > 1)
250
- throw new Error("Multiple turns ended");
251
- const args = evt.args;
252
- const decryptedProposals = await this.decryptProposals({ instanceAddress, gameId, turn: turn - 1n });
253
- if (args.newProposals) {
254
- args.newProposals.slice(0, args?.players?.length).forEach((proposal, idx) => {
255
- if (proposal !== "") {
256
- const proposer = decryptedProposals.find((p) => p.proposal === proposal)?.proposer;
257
- if (!proposer)
258
- throw new Error("No proposer found for proposal");
259
- oldProposals[idx] = {
260
- proposer,
261
- proposal: proposal,
262
- };
263
- }
264
- });
265
- }
266
- else {
267
- const _players = await this.publicClient.readContract({
268
- address: instanceAddress,
269
- abi: abis_1.RankifyDiamondInstanceAbi,
270
- functionName: "getPlayers",
271
- args: [gameId],
272
- });
273
- // Boundary case if no-one proposed a thing
274
- _players.forEach((p, idx) => {
275
- oldProposals[idx] = {
276
- proposer: p,
277
- proposal: "",
278
- };
279
- });
280
- }
281
- }
282
- return oldProposals;
283
- };
284
245
  /**
285
246
  * Finds the index of a player's ongoing proposal
286
247
  * @param gameId - ID of the game
@@ -288,31 +249,17 @@ class GameMaster {
288
249
  * @returns Index of the player's proposal, -1 if not found
289
250
  */
290
251
  findPlayerOngoingProposalIndex = async ({ instanceAddress, gameId, player, turn, }) => {
291
- const baseInstance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
292
- let currentTurn = 0n;
293
252
  if (!turn) {
294
- const r = await baseInstance.getOngoingProposals(gameId);
295
- currentTurn = r.currentTurn;
296
- if (currentTurn == 0n) {
297
- console.error("No proposals in turn 0");
298
- return -1;
299
- }
300
- }
301
- else {
302
- currentTurn = turn;
253
+ turn = await this.currentTurn({ instanceAddress, gameId });
303
254
  }
304
- const proposalsVotedUpon = await this.getProposalsVotedUpon({ instanceAddress, gameId, turn: currentTurn });
305
- return proposalsVotedUpon.findIndex((p) => p.proposer === player);
255
+ const players = await this.getPlayers({ instanceAddress, gameId });
256
+ const decryptedProposalsPermuted = await this.decryptProposals({ instanceAddress, gameId, turn: turn - 1n, players: [...players], permute: true });
257
+ return decryptedProposalsPermuted.findIndex((p) => p?.proposer === player);
306
258
  };
307
259
  validateJoinGame = async (props) => {
308
260
  const { gameId, participant, instanceAddress } = props;
309
261
  try {
310
- const baseInstance = new InstanceBase_1.default({
311
- instanceAddress,
312
- publicClient: this.publicClient,
313
- chainId: this.chainId,
314
- });
315
- const gameState = await baseInstance.getGameStateDetails(gameId);
262
+ const gameState = await this.getGameState({ gameId, instanceAddress });
316
263
  if (gameState.gamePhase !== types_1.gameStatusEnum.open) {
317
264
  return { result: false, errorMessage: "Game is not open for registration" };
318
265
  }
@@ -340,6 +287,10 @@ class GameMaster {
340
287
  throw new Error("No account");
341
288
  (0, log_1.logger)(`Signing joining game..`);
342
289
  const { gameId, participant, instanceAddress, participantPubKeyHash } = props;
290
+ const { result: isValid, errorMessage } = await this.validateJoinGame({ gameId, participant, instanceAddress });
291
+ if (!isValid) {
292
+ throw new Error(errorMessage);
293
+ }
343
294
  const baseInstance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
344
295
  const eip712 = await baseInstance.getEIP712Domain();
345
296
  (0, log_1.logger)({
@@ -403,19 +354,12 @@ class GameMaster {
403
354
  * @returns Transaction hash
404
355
  */
405
356
  submitVote = async ({ instanceAddress, gameId, vote, voter, voterSignature, ballotHash, ballotId, }) => {
406
- if (!gameId)
407
- throw new Error("No gameId");
408
- if (!vote)
409
- throw new Error("No votesHidden");
410
- if (!voter)
411
- throw new Error("No voter");
412
- const proposerIdx = await this.findPlayerOngoingProposalIndex({ instanceAddress, gameId, player: voter });
413
- if (proposerIdx == -1)
414
- throw new Error("You are not a proposer in this game");
415
- if (vote[proposerIdx] !== 0n)
416
- throw new Error("You cannot vote for your own proposal");
417
- if (!this.walletClient?.account?.address)
418
- throw new Error("No account address found");
357
+ const players = await this.getPlayers({ instanceAddress, gameId });
358
+ const turn = await this.currentTurn({ instanceAddress, gameId });
359
+ const validationResult = await this.validateVote({ gameId, turn, voter, vote, instanceAddress, players: [...players] });
360
+ if (!validationResult.result) {
361
+ throw new Error('Vote validation failed: ' + validationResult.reason);
362
+ }
419
363
  try {
420
364
  const { request } = await this.publicClient.simulateContract({
421
365
  account: this.walletClient.account,
@@ -430,6 +374,84 @@ class GameMaster {
430
374
  throw await (0, utils_1.handleRPCError)(e);
431
375
  }
432
376
  };
377
+ /**
378
+ * Gets the current turn progress in percent value
379
+ * @param instanceAddress - Address of the instance
380
+ * @param gameState - Current game state
381
+ * @param gameId - ID of the game
382
+ * @returns Current turn progress
383
+ */
384
+ getTurnProgress = async ({ instanceAddress, gameState, gameId }) => {
385
+ const prevTurnProposals = await this.decryptProposals({ instanceAddress, gameId, turn: gameState.currentTurn - 1n, players: [...gameState.players] });
386
+ const proposalCountInPrevTurn = prevTurnProposals.filter(p => p.proposal !== "").length;
387
+ const proposalsMadeInCurrentTurn = await this.decryptProposals({ instanceAddress, gameId, turn: gameState.currentTurn, players: [...gameState.players] });
388
+ const proposalCountInCurrentTurn = proposalsMadeInCurrentTurn.filter(p => p.proposal !== "").length;
389
+ if (proposalCountInPrevTurn === 0) {
390
+ return (proposalCountInCurrentTurn / gameState.players.length) * 100;
391
+ }
392
+ else {
393
+ const votesMadeInCurrentTurn = await this.decryptTurnVotes({ instanceAddress, gameId, turn: gameState.currentTurn, players: [...gameState.players] });
394
+ const votesCount = votesMadeInCurrentTurn.filter(v => this.hasVoted({ vote: v })).length;
395
+ return ((votesCount + proposalCountInCurrentTurn) / (gameState.players.length * 2)) * 100;
396
+ }
397
+ };
398
+ hasVoted({ vote }) {
399
+ return vote?.reduce((a, b) => a + b, 0n) !== 0n;
400
+ }
401
+ ;
402
+ validateVote = async ({ gameId, turn, voter, vote, instanceAddress, players }) => {
403
+ const decryptedProposals = await this.decryptProposals({ instanceAddress, gameId, turn: turn - 1n, players, permute: true });
404
+ //Invalid vote length
405
+ if (vote.length !== decryptedProposals.length) {
406
+ return { result: false, reason: "Invalid vote length" };
407
+ }
408
+ // Check if points used are correct (Quadratic voting system)
409
+ let pointsUsed = 0n;
410
+ for (let i = 0; i < vote.length; i++) {
411
+ if (vote[i] > 0n) {
412
+ pointsUsed += 1n ** vote[i];
413
+ }
414
+ }
415
+ const gameState = await this.getGameState({ gameId, instanceAddress });
416
+ if (pointsUsed > gameState.voteCredits) {
417
+ return { result: false, reason: "Too many points used" };
418
+ }
419
+ if (pointsUsed < gameState.voteCredits) {
420
+ return { result: false, reason: "Not all points used" };
421
+ }
422
+ // Check if voter voted for a non-proposed player or their own proposal
423
+ for (let i = 0; i < vote.length; i++) {
424
+ if (vote[i] === 0n)
425
+ continue;
426
+ if (decryptedProposals[i].proposal === "" || decryptedProposals[i].proposer === viem_1.zeroAddress) {
427
+ return { result: false, reason: "Vote for non existing proposal" };
428
+ }
429
+ if (decryptedProposals[i].proposer === voter) {
430
+ return { result: false, reason: "Voter cannot vote for their own proposal" };
431
+ }
432
+ }
433
+ // Valid vote
434
+ return { result: true, reason: "" };
435
+ };
436
+ getGameState({ gameId, instanceAddress }) {
437
+ const baseInstance = new InstanceBase_1.default({
438
+ instanceAddress,
439
+ publicClient: this.publicClient,
440
+ chainId: this.chainId,
441
+ });
442
+ return baseInstance.getGameStateDetails(gameId);
443
+ }
444
+ padProposalsArrayWithZeroAddress = (proposals) => {
445
+ if (proposals.length < this.maxSlotSizeForProofs) {
446
+ for (let i = proposals.length; i < this.maxSlotSizeForProofs; i++) {
447
+ proposals.push({
448
+ proposer: viem_1.zeroAddress,
449
+ proposal: "",
450
+ });
451
+ }
452
+ }
453
+ return proposals;
454
+ };
433
455
  /**
434
456
  * Types for proposal submission
435
457
  */
@@ -572,8 +594,6 @@ class GameMaster {
572
594
  * @returns Transaction hash
573
595
  */
574
596
  submitProposal = async ({ instanceAddress, submissionParams, proposerSignature, }) => {
575
- // let proposalData: GetAbiItemParameters<typeof RankifyDiamondInstanceAbi, "submitProposal">["args"];
576
- // proposalData[0].
577
597
  const txParams = [
578
598
  {
579
599
  ...submissionParams,
@@ -600,21 +620,23 @@ class GameMaster {
600
620
  * @param turn - Turn number
601
621
  * @returns Array of decrypted votes with player addresses
602
622
  */
603
- decryptTurnVotes = async ({ instanceAddress, gameId, turn, }) => {
623
+ decryptTurnVotes = async ({ instanceAddress, gameId, turn, players = [], }) => {
604
624
  (0, log_1.logger)(`Decrypting votes for game ${BigInt(gameId)} turn ${turn} at address ${instanceAddress} at ${await this.publicClient.getBlockNumber()} block`);
605
- const evts = await this.publicClient.getContractEvents({
625
+ const VoteSubmittedEvents = await this.publicClient.getContractEvents({
606
626
  address: instanceAddress,
607
627
  abi: abis_1.RankifyDiamondInstanceAbi,
608
628
  eventName: "VoteSubmitted",
609
629
  fromBlock: 0n,
610
630
  args: { turn, gameId },
611
631
  });
612
- (0, log_1.logger)(`Found ${evts.length} events`);
613
- if (evts.length === 0)
632
+ (0, log_1.logger)(`Found ${VoteSubmittedEvents.length} events`);
633
+ if (VoteSubmittedEvents.length === 0)
614
634
  return [];
615
- // Process events one by one to allow errors to propagate
635
+ //Decrypting votes from events
616
636
  const votes = [];
617
- for (const event of evts) {
637
+ console.log("Events:", VoteSubmittedEvents);
638
+ for (const event of VoteSubmittedEvents) {
639
+ console.log("Event:", event);
618
640
  if (!event.args.player)
619
641
  throw new Error("No player in event");
620
642
  if (!event.args.sealedBallotId)
@@ -631,26 +653,13 @@ class GameMaster {
631
653
  votes: decryptedVotes,
632
654
  });
633
655
  }
634
- return votes;
635
- };
636
- /**
637
- * Decrypts all votes for the current game turn
638
- * @param gameId - ID of the game
639
- * @returns Array of decrypted votes with player addresses
640
- */
641
- decryptVotes = async ({ instanceAddress, gameId }) => {
642
- const currentTurn = await this.publicClient.readContract({
643
- address: instanceAddress,
644
- abi: abis_1.RankifyDiamondInstanceAbi,
645
- functionName: "getTurn",
646
- args: [gameId],
647
- });
648
- if (currentTurn === 0n) {
649
- console.error("No proposals in turn 0");
650
- return -1;
651
- }
652
- const votes = await this.decryptTurnVotes({ instanceAddress, gameId, turn: currentTurn });
653
- return votes.length === 0 ? -1 : votes;
656
+ const votesForEachPlayer = await Promise.all(players.map(async (player) => {
657
+ const vote = votes.find((v) => v.player === player);
658
+ if (!vote?.votes)
659
+ return players.map(() => 0n);
660
+ return vote.votes;
661
+ }));
662
+ return votesForEachPlayer;
654
663
  };
655
664
  /**
656
665
  * Checks if the current turn can be ended
@@ -658,12 +667,24 @@ class GameMaster {
658
667
  * @returns Boolean indicating if turn can be ended
659
668
  */
660
669
  canEndTurn = async ({ instanceAddress, gameId }) => {
661
- return this.publicClient.readContract({
670
+ const canEndTurn = await this.publicClient.readContract({
662
671
  address: instanceAddress,
663
672
  abi: abis_1.RankifyDiamondInstanceAbi,
664
673
  functionName: "canEndTurn",
665
674
  args: [gameId],
666
675
  });
676
+ if (!canEndTurn)
677
+ return false;
678
+ //Extra check to not allow to end turn if current phase timeout is not passed and progress is less than 100% (probably must be fixed in contracts!)
679
+ // TODO: if fixed in contracts, remove this check
680
+ const gameState = await this.getGameState({ instanceAddress, gameId });
681
+ const lastBlock = await this.publicClient.getBlock({ blockNumber: BigInt(await this.publicClient.getBlockNumber()) });
682
+ if (gameState.currentPhaseTimeoutAt > lastBlock.timestamp) {
683
+ const turnProgress = await this.getTurnProgress({ instanceAddress, gameState, gameId });
684
+ if (turnProgress <= 100)
685
+ return false;
686
+ }
687
+ return true;
667
688
  };
668
689
  /**
669
690
  * Gets the current turn number
@@ -709,60 +730,22 @@ class GameMaster {
709
730
  endTurn = async ({ instanceAddress, gameId }) => {
710
731
  (0, log_1.logger)(`Ending turn for game ${gameId}`, 2);
711
732
  try {
712
- const turn = await this.publicClient.readContract({
713
- address: instanceAddress,
714
- abi: abis_1.RankifyDiamondInstanceAbi,
715
- functionName: "getTurn",
716
- args: [gameId],
717
- });
718
- const players = (await this.publicClient.readContract({
719
- address: instanceAddress,
720
- abi: abis_1.RankifyDiamondInstanceAbi,
721
- functionName: "getPlayers",
722
- args: [gameId],
723
- }));
724
- (0, log_1.logger)(`Current turn: ${turn}, Players count: ${players.length}`, 2);
725
- if (!Array.isArray(players)) {
726
- throw new Error("Expected players to be an array");
727
- }
728
- const proposerIndices = [];
729
- const oldProposals = await this.getProposalsVotedUpon({ instanceAddress, gameId, turn });
730
- const decryptedProposals = await this.decryptProposals({ instanceAddress, gameId, turn });
731
- (0, log_1.logger)(decryptedProposals);
732
- const proposals = players.map((player) => {
733
- const proposal = decryptedProposals.find((p) => p.proposer === player)?.proposal ?? "";
734
- return {
735
- proposer: player,
736
- proposal,
737
- };
738
- });
739
- const maxSize = 15;
740
- if (proposals.length < maxSize) {
741
- for (let i = proposals.length; i < maxSize; i++) {
742
- proposals.push({
743
- proposer: viem_1.zeroAddress,
744
- proposal: "",
745
- });
746
- }
733
+ if (!(await this.canEndTurn({ instanceAddress, gameId }))) {
734
+ throw new Error("Cannot end turn");
747
735
  }
748
- (0, log_1.logger)(proposals);
749
- players.forEach((player) => {
750
- let proposerIdx = oldProposals.findIndex((p) => player === p.proposer);
751
- if (proposerIdx === -1)
752
- proposerIdx = players.length; //Did not propose
753
- proposerIndices.push(BigInt(proposerIdx));
754
- });
755
- const voteDecrypted = await this.decryptTurnVotes({ instanceAddress, gameId, turn });
756
- const votes = await Promise.all(players.map(async (player) => {
757
- const vote = voteDecrypted.find((v) => v.player === player);
758
- if (!vote?.votes)
759
- return players.map(() => 0n);
760
- return vote.votes;
761
- }));
736
+ const turn = await this.currentTurn({ instanceAddress, gameId });
737
+ const players = await this.getPlayers({ instanceAddress, gameId });
738
+ (0, log_1.logger)(`Current turn: ${turn}, Players count: ${players.length}`, 2);
739
+ const newPaddedDecryptedProposals = await this.decryptProposals({ instanceAddress, gameId, turn, players: [...players], padToMaxSize: true });
740
+ (0, log_1.logger)(`newPaddedDecryptedProposals:`);
741
+ (0, log_1.logger)(newPaddedDecryptedProposals);
742
+ const votesDecrypted = await this.decryptTurnVotes({ instanceAddress, gameId, turn, players: [...players] });
743
+ (0, log_1.logger)(`votesDecrypted:`);
744
+ (0, log_1.logger)(votesDecrypted);
762
745
  const tableData = players.map((player, idx) => ({
763
746
  player,
764
- proposerIndex: proposerIndices[idx],
765
- proposer: oldProposals[Number(proposerIndices[idx])]?.proposer || "not-proposed",
747
+ voted: this.hasVoted({ vote: votesDecrypted[idx] }),
748
+ proposal: newPaddedDecryptedProposals[idx]?.proposal.substring(0, 50) || "not-proposed",
766
749
  }));
767
750
  console.table(tableData);
768
751
  const attested = await this.getProposalsIntegrity({
@@ -770,16 +753,14 @@ class GameMaster {
770
753
  turn,
771
754
  verifierAddress: instanceAddress,
772
755
  size: players.length,
773
- proposals,
756
+ proposals: newPaddedDecryptedProposals,
774
757
  });
775
- (0, log_1.logger)(`votes:`);
776
- (0, log_1.logger)(votes);
777
758
  const { request } = await this.publicClient.simulateContract({
778
759
  abi: abis_1.RankifyDiamondInstanceAbi,
779
760
  account: this.walletClient.account,
780
761
  address: instanceAddress,
781
762
  functionName: "endTurn",
782
- args: [gameId, votes, attested.newProposals, attested.permutation, attested.nullifier],
763
+ args: [gameId, votesDecrypted, attested.newProposals, attested.prevTurnPermutation, attested.prevTurnSalt],
783
764
  });
784
765
  return this.walletClient.writeContract(request);
785
766
  }
@@ -807,6 +788,8 @@ class GameMaster {
807
788
  const instance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
808
789
  const playerPubKey = await instance.getPlayerPubKey({ instanceAddress, gameId, player });
809
790
  (0, log_1.logger)(`Player public key: ${playerPubKey}`, 2);
791
+ console.log("Player public key:", playerPubKey);
792
+ console.log("Game key:", await this.gameKey({ gameId, contractAddress: instanceAddress }));
810
793
  return instance.sharedSigner({
811
794
  publicKey: playerPubKey,
812
795
  privateKey: await this.gameKey({ gameId, contractAddress: instanceAddress }),
@@ -839,7 +822,12 @@ class GameMaster {
839
822
  */
840
823
  attestVote = async ({ voter, gameId, turn, vote, verifierAddress, }) => {
841
824
  (0, log_1.logger)(`Attesting vote for player ${voter} in game ${gameId}, turn ${turn}`);
842
- const gameSize = (await this.getPlayers({ instanceAddress: verifierAddress, gameId })).length;
825
+ const players = await this.getPlayers({ instanceAddress: verifierAddress, gameId });
826
+ const validationResult = await this.validateVote({ gameId, turn, voter, vote, instanceAddress: verifierAddress, players: [...players] });
827
+ if (!validationResult.result) {
828
+ throw new Error('Vote validation failed: ' + validationResult.reason);
829
+ }
830
+ const gameSize = players.length;
843
831
  const instance = new InstanceBase_1.default({
844
832
  instanceAddress: verifierAddress,
845
833
  publicClient: this.publicClient,
@@ -932,7 +920,7 @@ class GameMaster {
932
920
  */
933
921
  generateEndTurnIntegrity = async ({ gameId, turn, verifierAddress, size = 15, proposals, }) => {
934
922
  let _proposals = [...proposals];
935
- const { permutation, secret: nullifier } = await this.generateDeterministicPermutation({
923
+ const { permutation: prevTurnPermutation, turnSalt: prevTurnSalt } = await this.generateDeterministicPermutation({
936
924
  gameId,
937
925
  turn: turn - 1n,
938
926
  verifierAddress,
@@ -964,7 +952,10 @@ class GameMaster {
964
952
  (0, log_1.logger)("inputs:", 3);
965
953
  (0, log_1.logger)(inputs, 3);
966
954
  // Apply permutation to proposals array
955
+ console.log("permutation used on new proposals:", inputs.permutation);
956
+ console.log("revealed permutation for prevTurn proposals:", prevTurnPermutation);
967
957
  const permutedProposals = (0, permutations_1.permuteArray)({ array: _proposals, permutation: inputs.permutation });
958
+ console.log("permutedProposals:", permutedProposals);
968
959
  const config = {
969
960
  circuitName: "ProposalsIntegrity15",
970
961
  circuitArtifactsPath: path_1.default.join(__dirname, "../../zk_artifacts/circuits/proposals_integrity_15.circom/"),
@@ -987,8 +978,8 @@ class GameMaster {
987
978
  const c = callData[2].map((c) => BigInt(c));
988
979
  return {
989
980
  commitment: inputs.permutationCommitment,
990
- nullifier,
991
- permutation: permutation,
981
+ prevTurnSalt,
982
+ prevTurnPermutation,
992
983
  permutedProposals: permutedProposals.map((proposal) => proposal.proposal),
993
984
  a,
994
985
  b,
@@ -1002,7 +993,7 @@ class GameMaster {
1002
993
  */
1003
994
  async getProposalsIntegrity({ size, gameId, turn, proposals, verifierAddress, }) {
1004
995
  (0, log_1.logger)(`Generating proposals integrity for game ${gameId}, turn ${turn} with ${size} players.`);
1005
- const { commitment, nullifier, permutation, permutedProposals, a, b, c } = await this.generateEndTurnIntegrity({
996
+ const { commitment, prevTurnSalt, prevTurnPermutation, permutedProposals, a, b, c } = await this.generateEndTurnIntegrity({
1006
997
  gameId,
1007
998
  turn,
1008
999
  verifierAddress,
@@ -1018,9 +1009,9 @@ class GameMaster {
1018
1009
  proposals: permutedProposals,
1019
1010
  permutationCommitment: commitment,
1020
1011
  },
1021
- permutation: permutation.map((p) => BigInt(p)),
1012
+ prevTurnPermutation: prevTurnPermutation.map((p) => BigInt(p)),
1022
1013
  proposalsNotPermuted: proposals.map((proposal) => proposal.proposal),
1023
- nullifier,
1014
+ prevTurnSalt,
1024
1015
  };
1025
1016
  }
1026
1017
  /**
@@ -1030,20 +1021,19 @@ class GameMaster {
1030
1021
  */
1031
1022
  createInputs = async ({ numActive, proposals, commitmentRandomnesses, gameId, turn, verifierAddress, }) => {
1032
1023
  const poseidon = await (0, circomlibjs_1.buildPoseidon)();
1033
- const maxSize = 15;
1034
1024
  // Initialize arrays with zeros
1035
- const commitments = Array(maxSize).fill(0n);
1036
- const randomnesses = Array(maxSize).fill(0n);
1037
- const permutedProposals = Array(maxSize).fill(0n);
1025
+ const commitments = Array(this.maxSlotSizeForProofs).fill(0n);
1026
+ const randomnesses = Array(this.maxSlotSizeForProofs).fill(0n);
1027
+ const permutedProposals = Array(this.maxSlotSizeForProofs).fill(0n);
1038
1028
  // Generate deterministic permutation
1039
- const { permutation, secret, commitment } = await this.generateDeterministicPermutation({
1029
+ const { permutation, turnSalt: secret, commitment } = await this.generateDeterministicPermutation({
1040
1030
  gameId,
1041
1031
  turn,
1042
1032
  verifierAddress,
1043
1033
  size: numActive,
1044
1034
  });
1045
1035
  // Fill arrays with values
1046
- for (let i = 0; i < maxSize; i++) {
1036
+ for (let i = 0; i < this.maxSlotSizeForProofs; i++) {
1047
1037
  if (i < numActive) {
1048
1038
  // Active slots
1049
1039
  const proposal = proposals[i];