@peeramid-labs/sdk 3.7.4 → 3.9.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 (91) 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 +65 -40
  44. package/cli/rankify/InstanceBase.js.map +1 -1
  45. package/cli/rankify/MAODistributor.js +1 -1
  46. package/cli/rankify/MAODistributor.js.map +1 -1
  47. package/cli/rankify/Player.js +355 -0
  48. package/cli/rankify/Player.js.map +1 -0
  49. package/cli/utils/blockchain.js +62 -0
  50. package/cli/utils/blockchain.js.map +1 -0
  51. package/docs/classes/GameMaster.md +23 -63
  52. package/docs/classes/InstanceBase.md +22 -29
  53. package/docs/classes/InstancePlayer.md +26 -33
  54. package/docs/docs/classes/GameMaster.md +23 -63
  55. package/docs/docs/classes/InstanceBase.md +22 -29
  56. package/docs/docs/classes/InstancePlayer.md +26 -33
  57. package/docs/docs/index.md +1 -1
  58. package/docs/index.md +1 -1
  59. package/lib.commonjs/rankify/GameMaster.d.ts +26 -33
  60. package/lib.commonjs/rankify/GameMaster.d.ts.map +1 -1
  61. package/lib.commonjs/rankify/GameMaster.js +199 -209
  62. package/lib.commonjs/rankify/GameMaster.js.map +1 -1
  63. package/lib.commonjs/rankify/InstanceBase.d.ts +65 -51
  64. package/lib.commonjs/rankify/InstanceBase.d.ts.map +1 -1
  65. package/lib.commonjs/rankify/InstanceBase.js +65 -40
  66. package/lib.commonjs/rankify/InstanceBase.js.map +1 -1
  67. package/lib.commonjs/rankify/MAODistributor.d.ts.map +1 -1
  68. package/lib.commonjs/rankify/MAODistributor.js +1 -1
  69. package/lib.commonjs/rankify/MAODistributor.js.map +1 -1
  70. package/lib.commonjs/utils/blockchain.d.ts +32 -0
  71. package/lib.commonjs/utils/blockchain.d.ts.map +1 -0
  72. package/lib.commonjs/utils/blockchain.js +62 -0
  73. package/lib.commonjs/utils/blockchain.js.map +1 -0
  74. package/lib.esm/rankify/GameMaster.d.ts +26 -33
  75. package/lib.esm/rankify/GameMaster.d.ts.map +1 -1
  76. package/lib.esm/rankify/GameMaster.js +199 -209
  77. package/lib.esm/rankify/GameMaster.js.map +1 -1
  78. package/lib.esm/rankify/InstanceBase.d.ts +65 -51
  79. package/lib.esm/rankify/InstanceBase.d.ts.map +1 -1
  80. package/lib.esm/rankify/InstanceBase.js +65 -40
  81. package/lib.esm/rankify/InstanceBase.js.map +1 -1
  82. package/lib.esm/rankify/MAODistributor.d.ts.map +1 -1
  83. package/lib.esm/rankify/MAODistributor.js +1 -1
  84. package/lib.esm/rankify/MAODistributor.js.map +1 -1
  85. package/lib.esm/utils/blockchain.d.ts +32 -0
  86. package/lib.esm/utils/blockchain.d.ts.map +1 -0
  87. package/lib.esm/utils/blockchain.js +58 -0
  88. package/lib.esm/utils/blockchain.js.map +1 -0
  89. package/package.json +1 -1
  90. package/cli/cli/commands/fellowship/endTurn.js.map +0 -1
  91. package/cli/cli/commands/fellowship/games.js.map +0 -1
@@ -31,6 +31,7 @@ class GameMaster {
31
31
  * @param chainId - Chain ID of the network
32
32
  */
33
33
  constructor({ walletClient, chainId, publicClient, }) {
34
+ this.maxSlotSizeForProofs = 15;
34
35
  /**
35
36
  * Decrypts a proposal for a specific game turn
36
37
  * @param proposal - The encrypted proposal
@@ -67,39 +68,50 @@ class GameMaster {
67
68
  * @param proposer - Optional proposer address to filter proposals
68
69
  * @returns Array of decrypted proposals with proposer addresses
69
70
  */
70
- this.decryptProposals = async ({ instanceAddress, gameId, turn, proposer, }) => {
71
- (0, log_1.logger)(`Getting proposals for instance ${instanceAddress}, game ${gameId}, turn ${turn.toString()}, proposer ${proposer}`);
72
- const evts = await this.publicClient.getContractEvents({
71
+ this.decryptProposals = async ({ instanceAddress, gameId, turn, players, padToMaxSize = false, permute = false, }) => {
72
+ (0, log_1.logger)(`Getting proposals for instance ${instanceAddress}, game ${gameId}, turn ${turn.toString()}`);
73
+ const ProposalSubmittedEvents = await this.publicClient.getContractEvents({
73
74
  abi: abis_1.RankifyDiamondInstanceAbi,
74
75
  address: instanceAddress,
75
76
  eventName: "ProposalSubmitted",
76
- args: { gameId: gameId, turn: turn, proposer: proposer },
77
+ args: { gameId: gameId, turn: turn },
77
78
  fromBlock: 0n,
78
79
  });
79
- (0, log_1.logger)(`Found ${evts.length} proposals`);
80
+ (0, log_1.logger)(`Found ${ProposalSubmittedEvents.length} proposals`);
80
81
  const instance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
81
- if (evts.length == 0)
82
- return [];
83
- (0, log_1.logger)(`Decrypting ${evts.length} proposals`);
84
- const proposals = await Promise.all(evts.map(async (log) => {
85
- (0, log_1.logger)(`Decrypting proposal ${log.args.proposer}`);
86
- if (!log.args.proposer)
87
- throw new Error("No proposer");
88
- if (!log.args.encryptedProposal)
89
- throw new Error("No proposalEncryptedByGM");
90
- return {
91
- proposer: log.args.proposer,
92
- proposal: await this.decryptProposal({
93
- proposal: log.args.encryptedProposal,
94
- turn: turn,
95
- instanceAddress: instanceAddress,
96
- gameId: gameId,
82
+ (0, log_1.logger)(`Decrypting ${ProposalSubmittedEvents.length} proposals`);
83
+ const proposalsForPlayers = await Promise.all((players)?.map(async (player) => {
84
+ const log = ProposalSubmittedEvents.find((log) => log.args.proposer === player);
85
+ if (!log) {
86
+ return {
87
+ proposer: player,
88
+ proposal: "",
89
+ };
90
+ }
91
+ else {
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 {
97
98
  proposer: log.args.proposer,
98
- instance,
99
- }),
100
- };
99
+ proposal: await this.decryptProposal({
100
+ proposal: log.args.encryptedProposal,
101
+ turn: turn,
102
+ instanceAddress: instanceAddress,
103
+ gameId: gameId,
104
+ proposer: log.args.proposer,
105
+ instance,
106
+ }),
107
+ };
108
+ }
101
109
  }));
102
- return proposals;
110
+ if (permute) {
111
+ const proposalsPermuted = await this.permuteArray({ array: proposalsForPlayers, gameId, turn, verifierAddress: instanceAddress });
112
+ return padToMaxSize ? this.padProposalsArrayWithZeroAddress(proposalsPermuted) : proposalsPermuted;
113
+ }
114
+ return padToMaxSize ? this.padProposalsArrayWithZeroAddress(proposalsForPlayers) : proposalsForPlayers;
103
115
  };
104
116
  /**
105
117
  * Generates a deterministic permutation for a specific game turn
@@ -110,11 +122,10 @@ class GameMaster {
110
122
  * @returns The generated permutation, secret, and commitment
111
123
  */
112
124
  this.getPermutation = async ({ gameId, turn, size, verifierAddress, }) => {
113
- const maxSize = 15;
114
125
  const turnSalt = await this.getTurnSalt({ gameId, turn, verifierAddress });
115
126
  // Create deterministic seed from game parameters and GM's signature
116
127
  // Use the seed to generate permutation
117
- const permutation = Array.from({ length: maxSize }, (_, i) => i);
128
+ const permutation = Array.from({ length: this.maxSlotSizeForProofs }, (_, i) => i);
118
129
  // Fisher-Yates shuffle with deterministic randomness
119
130
  for (let i = size - 1; i >= 0; i--) {
120
131
  // Generate deterministic random number for this position
@@ -125,7 +136,7 @@ class GameMaster {
125
136
  [permutation[i], permutation[j]] = [permutation[j], permutation[i]];
126
137
  }
127
138
  // Ensure inactive slots map to themselves
128
- for (let i = size; i < maxSize; i++) {
139
+ for (let i = size; i < this.maxSlotSizeForProofs; i++) {
129
140
  permutation[i] = i;
130
141
  }
131
142
  return { permutation, turnSalt };
@@ -150,7 +161,7 @@ class GameMaster {
150
161
  const commitment = BigInt(poseidon.F.toObject(poseidon([PoseidonThird, turnSalt])));
151
162
  return {
152
163
  permutation,
153
- secret: turnSalt,
164
+ turnSalt,
154
165
  commitment,
155
166
  };
156
167
  };
@@ -219,61 +230,11 @@ class GameMaster {
219
230
  verifierAddress,
220
231
  size,
221
232
  }).then((perm) => {
222
- return (0, viem_1.keccak256)((0, viem_1.encodePacked)(["address", "uint256"], [player, perm.secret]));
233
+ return (0, viem_1.keccak256)((0, viem_1.encodePacked)(["address", "uint256"], [player, perm.turnSalt]));
223
234
  });
224
235
  (0, log_1.logger)(`Generated vote salt for player ${player}`);
225
236
  return result;
226
237
  };
227
- this.getProposalsVotedUpon = async ({ instanceAddress, gameId, turn, }) => {
228
- const oldProposals = [];
229
- //Proposals sequence is directly corresponding to proposers sequence
230
- if (turn != 1n) {
231
- (0, log_1.logger)(`Getting proposals voted upon for game ${gameId.toString()}, turn ${(turn - 1n).toString()}`, 3);
232
- const endedEvents = await this.publicClient.getContractEvents({
233
- address: instanceAddress,
234
- abi: abis_1.RankifyDiamondInstanceAbi,
235
- eventName: "TurnEnded",
236
- args: { gameId, turn: turn - 1n },
237
- fromBlock: 0n,
238
- });
239
- const evt = endedEvents[0];
240
- (0, log_1.logger)("evt:", 3);
241
- (0, log_1.logger)(evt, 3);
242
- if (endedEvents.length > 1)
243
- throw new Error("Multiple turns ended");
244
- const args = evt.args;
245
- const decryptedProposals = await this.decryptProposals({ instanceAddress, gameId, turn: turn - 1n });
246
- if (args.newProposals) {
247
- args.newProposals.slice(0, args?.players?.length).forEach((proposal, idx) => {
248
- if (proposal !== "") {
249
- const proposer = decryptedProposals.find((p) => p.proposal === proposal)?.proposer;
250
- if (!proposer)
251
- throw new Error("No proposer found for proposal");
252
- oldProposals[idx] = {
253
- proposer,
254
- proposal: proposal,
255
- };
256
- }
257
- });
258
- }
259
- else {
260
- const _players = await this.publicClient.readContract({
261
- address: instanceAddress,
262
- abi: abis_1.RankifyDiamondInstanceAbi,
263
- functionName: "getPlayers",
264
- args: [gameId],
265
- });
266
- // Boundary case if no-one proposed a thing
267
- _players.forEach((p, idx) => {
268
- oldProposals[idx] = {
269
- proposer: p,
270
- proposal: "",
271
- };
272
- });
273
- }
274
- }
275
- return oldProposals;
276
- };
277
238
  /**
278
239
  * Finds the index of a player's ongoing proposal
279
240
  * @param gameId - ID of the game
@@ -281,31 +242,17 @@ class GameMaster {
281
242
  * @returns Index of the player's proposal, -1 if not found
282
243
  */
283
244
  this.findPlayerOngoingProposalIndex = async ({ instanceAddress, gameId, player, turn, }) => {
284
- const baseInstance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
285
- let currentTurn = 0n;
286
245
  if (!turn) {
287
- const r = await baseInstance.getOngoingProposals(gameId);
288
- currentTurn = r.currentTurn;
289
- if (currentTurn == 0n) {
290
- console.error("No proposals in turn 0");
291
- return -1;
292
- }
293
- }
294
- else {
295
- currentTurn = turn;
246
+ turn = await this.currentTurn({ instanceAddress, gameId });
296
247
  }
297
- const proposalsVotedUpon = await this.getProposalsVotedUpon({ instanceAddress, gameId, turn: currentTurn });
298
- return proposalsVotedUpon.findIndex((p) => p.proposer === player);
248
+ const players = await this.getPlayers({ instanceAddress, gameId });
249
+ const decryptedProposalsPermuted = await this.decryptProposals({ instanceAddress, gameId, turn: turn - 1n, players: [...players], permute: true });
250
+ return decryptedProposalsPermuted.findIndex((p) => p?.proposer === player);
299
251
  };
300
252
  this.validateJoinGame = async (props) => {
301
253
  const { gameId, participant, instanceAddress } = props;
302
254
  try {
303
- const baseInstance = new InstanceBase_1.default({
304
- instanceAddress,
305
- publicClient: this.publicClient,
306
- chainId: this.chainId,
307
- });
308
- const gameState = await baseInstance.getGameStateDetails(gameId);
255
+ const gameState = await this.getGameState({ gameId, instanceAddress });
309
256
  if (gameState.gamePhase !== types_1.gameStatusEnum.open) {
310
257
  return { result: false, errorMessage: "Game is not open for registration" };
311
258
  }
@@ -333,6 +280,10 @@ class GameMaster {
333
280
  throw new Error("No account");
334
281
  (0, log_1.logger)(`Signing joining game..`);
335
282
  const { gameId, participant, instanceAddress, participantPubKeyHash } = props;
283
+ const { result: isValid, errorMessage } = await this.validateJoinGame({ gameId, participant, instanceAddress });
284
+ if (!isValid) {
285
+ throw new Error(errorMessage);
286
+ }
336
287
  const baseInstance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
337
288
  const eip712 = await baseInstance.getEIP712Domain();
338
289
  (0, log_1.logger)({
@@ -396,19 +347,12 @@ class GameMaster {
396
347
  * @returns Transaction hash
397
348
  */
398
349
  this.submitVote = async ({ instanceAddress, gameId, vote, voter, voterSignature, ballotHash, ballotId, }) => {
399
- if (!gameId)
400
- throw new Error("No gameId");
401
- if (!vote)
402
- throw new Error("No votesHidden");
403
- if (!voter)
404
- throw new Error("No voter");
405
- const proposerIdx = await this.findPlayerOngoingProposalIndex({ instanceAddress, gameId, player: voter });
406
- if (proposerIdx == -1)
407
- throw new Error("You are not a proposer in this game");
408
- if (vote[proposerIdx] !== 0n)
409
- throw new Error("You cannot vote for your own proposal");
410
- if (!this.walletClient?.account?.address)
411
- throw new Error("No account address found");
350
+ const players = await this.getPlayers({ instanceAddress, gameId });
351
+ const turn = await this.currentTurn({ instanceAddress, gameId });
352
+ const validationResult = await this.validateVote({ gameId, turn, voter, vote, instanceAddress, players: [...players] });
353
+ if (!validationResult.result) {
354
+ throw new Error('Vote validation failed: ' + validationResult.reason);
355
+ }
412
356
  try {
413
357
  const { request } = await this.publicClient.simulateContract({
414
358
  account: this.walletClient.account,
@@ -423,6 +367,72 @@ class GameMaster {
423
367
  throw await (0, utils_1.handleRPCError)(e);
424
368
  }
425
369
  };
370
+ /**
371
+ * Gets the current turn progress in percent value
372
+ * @param instanceAddress - Address of the instance
373
+ * @param gameState - Current game state
374
+ * @param gameId - ID of the game
375
+ * @returns Current turn progress
376
+ */
377
+ this.getTurnProgress = async ({ instanceAddress, gameState, gameId }) => {
378
+ const prevTurnProposals = await this.decryptProposals({ instanceAddress, gameId, turn: gameState.currentTurn - 1n, players: [...gameState.players] });
379
+ const proposalCountInPrevTurn = prevTurnProposals.filter(p => p.proposal !== "").length;
380
+ const proposalsMadeInCurrentTurn = await this.decryptProposals({ instanceAddress, gameId, turn: gameState.currentTurn, players: [...gameState.players] });
381
+ const proposalCountInCurrentTurn = proposalsMadeInCurrentTurn.filter(p => p.proposal !== "").length;
382
+ if (proposalCountInPrevTurn === 0) {
383
+ return (proposalCountInCurrentTurn / gameState.players.length) * 100;
384
+ }
385
+ else {
386
+ const votesMadeInCurrentTurn = await this.decryptTurnVotes({ instanceAddress, gameId, turn: gameState.currentTurn, players: [...gameState.players] });
387
+ const votesCount = votesMadeInCurrentTurn.filter(v => this.hasVoted({ vote: v })).length;
388
+ return ((votesCount + proposalCountInCurrentTurn) / (gameState.players.length * 2)) * 100;
389
+ }
390
+ };
391
+ this.validateVote = async ({ gameId, turn, voter, vote, instanceAddress, players }) => {
392
+ const decryptedProposals = await this.decryptProposals({ instanceAddress, gameId, turn: turn - 1n, players, permute: true });
393
+ //Invalid vote length
394
+ if (vote.length !== decryptedProposals.length) {
395
+ return { result: false, reason: "Invalid vote length" };
396
+ }
397
+ // Check if points used are correct (Quadratic voting system)
398
+ let pointsUsed = 0n;
399
+ for (let i = 0; i < vote.length; i++) {
400
+ if (vote[i] > 0n) {
401
+ pointsUsed += 1n ** vote[i];
402
+ }
403
+ }
404
+ const gameState = await this.getGameState({ gameId, instanceAddress });
405
+ if (pointsUsed > gameState.voteCredits) {
406
+ return { result: false, reason: "Too many points used" };
407
+ }
408
+ if (pointsUsed < gameState.voteCredits) {
409
+ return { result: false, reason: "Not all points used" };
410
+ }
411
+ // Check if voter voted for a non-proposed player or their own proposal
412
+ for (let i = 0; i < vote.length; i++) {
413
+ if (vote[i] === 0n)
414
+ continue;
415
+ if (decryptedProposals[i].proposal === "" || decryptedProposals[i].proposer === viem_1.zeroAddress) {
416
+ return { result: false, reason: "Vote for non existing proposal" };
417
+ }
418
+ if (decryptedProposals[i].proposer === voter) {
419
+ return { result: false, reason: "Voter cannot vote for their own proposal" };
420
+ }
421
+ }
422
+ // Valid vote
423
+ return { result: true, reason: "" };
424
+ };
425
+ this.padProposalsArrayWithZeroAddress = (proposals) => {
426
+ if (proposals.length < this.maxSlotSizeForProofs) {
427
+ for (let i = proposals.length; i < this.maxSlotSizeForProofs; i++) {
428
+ proposals.push({
429
+ proposer: viem_1.zeroAddress,
430
+ proposal: "",
431
+ });
432
+ }
433
+ }
434
+ return proposals;
435
+ };
426
436
  /**
427
437
  * Types for proposal submission
428
438
  */
@@ -565,8 +575,6 @@ class GameMaster {
565
575
  * @returns Transaction hash
566
576
  */
567
577
  this.submitProposal = async ({ instanceAddress, submissionParams, proposerSignature, }) => {
568
- // let proposalData: GetAbiItemParameters<typeof RankifyDiamondInstanceAbi, "submitProposal">["args"];
569
- // proposalData[0].
570
578
  const txParams = [
571
579
  {
572
580
  ...submissionParams,
@@ -593,21 +601,23 @@ class GameMaster {
593
601
  * @param turn - Turn number
594
602
  * @returns Array of decrypted votes with player addresses
595
603
  */
596
- this.decryptTurnVotes = async ({ instanceAddress, gameId, turn, }) => {
604
+ this.decryptTurnVotes = async ({ instanceAddress, gameId, turn, players = [], }) => {
597
605
  (0, log_1.logger)(`Decrypting votes for game ${BigInt(gameId)} turn ${turn} at address ${instanceAddress} at ${await this.publicClient.getBlockNumber()} block`);
598
- const evts = await this.publicClient.getContractEvents({
606
+ const VoteSubmittedEvents = await this.publicClient.getContractEvents({
599
607
  address: instanceAddress,
600
608
  abi: abis_1.RankifyDiamondInstanceAbi,
601
609
  eventName: "VoteSubmitted",
602
610
  fromBlock: 0n,
603
611
  args: { turn, gameId },
604
612
  });
605
- (0, log_1.logger)(`Found ${evts.length} events`);
606
- if (evts.length === 0)
613
+ (0, log_1.logger)(`Found ${VoteSubmittedEvents.length} events`);
614
+ if (VoteSubmittedEvents.length === 0)
607
615
  return [];
608
- // Process events one by one to allow errors to propagate
616
+ //Decrypting votes from events
609
617
  const votes = [];
610
- for (const event of evts) {
618
+ console.log("Events:", VoteSubmittedEvents);
619
+ for (const event of VoteSubmittedEvents) {
620
+ console.log("Event:", event);
611
621
  if (!event.args.player)
612
622
  throw new Error("No player in event");
613
623
  if (!event.args.sealedBallotId)
@@ -624,26 +634,13 @@ class GameMaster {
624
634
  votes: decryptedVotes,
625
635
  });
626
636
  }
627
- return votes;
628
- };
629
- /**
630
- * Decrypts all votes for the current game turn
631
- * @param gameId - ID of the game
632
- * @returns Array of decrypted votes with player addresses
633
- */
634
- this.decryptVotes = async ({ instanceAddress, gameId }) => {
635
- const currentTurn = await this.publicClient.readContract({
636
- address: instanceAddress,
637
- abi: abis_1.RankifyDiamondInstanceAbi,
638
- functionName: "getTurn",
639
- args: [gameId],
640
- });
641
- if (currentTurn === 0n) {
642
- console.error("No proposals in turn 0");
643
- return -1;
644
- }
645
- const votes = await this.decryptTurnVotes({ instanceAddress, gameId, turn: currentTurn });
646
- return votes.length === 0 ? -1 : votes;
637
+ const votesForEachPlayer = await Promise.all(players.map(async (player) => {
638
+ const vote = votes.find((v) => v.player === player);
639
+ if (!vote?.votes)
640
+ return players.map(() => 0n);
641
+ return vote.votes;
642
+ }));
643
+ return votesForEachPlayer;
647
644
  };
648
645
  /**
649
646
  * Checks if the current turn can be ended
@@ -651,12 +648,24 @@ class GameMaster {
651
648
  * @returns Boolean indicating if turn can be ended
652
649
  */
653
650
  this.canEndTurn = async ({ instanceAddress, gameId }) => {
654
- return this.publicClient.readContract({
651
+ const canEndTurn = await this.publicClient.readContract({
655
652
  address: instanceAddress,
656
653
  abi: abis_1.RankifyDiamondInstanceAbi,
657
654
  functionName: "canEndTurn",
658
655
  args: [gameId],
659
656
  });
657
+ if (!canEndTurn)
658
+ return false;
659
+ //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!)
660
+ // TODO: if fixed in contracts, remove this check
661
+ const gameState = await this.getGameState({ instanceAddress, gameId });
662
+ const lastBlock = await this.publicClient.getBlock({ blockNumber: BigInt(await this.publicClient.getBlockNumber()) });
663
+ if (gameState.currentPhaseTimeoutAt > lastBlock.timestamp) {
664
+ const turnProgress = await this.getTurnProgress({ instanceAddress, gameState, gameId });
665
+ if (turnProgress <= 100)
666
+ return false;
667
+ }
668
+ return true;
660
669
  };
661
670
  /**
662
671
  * Gets the current turn number
@@ -702,60 +711,22 @@ class GameMaster {
702
711
  this.endTurn = async ({ instanceAddress, gameId }) => {
703
712
  (0, log_1.logger)(`Ending turn for game ${gameId}`, 2);
704
713
  try {
705
- const turn = await this.publicClient.readContract({
706
- address: instanceAddress,
707
- abi: abis_1.RankifyDiamondInstanceAbi,
708
- functionName: "getTurn",
709
- args: [gameId],
710
- });
711
- const players = (await this.publicClient.readContract({
712
- address: instanceAddress,
713
- abi: abis_1.RankifyDiamondInstanceAbi,
714
- functionName: "getPlayers",
715
- args: [gameId],
716
- }));
717
- (0, log_1.logger)(`Current turn: ${turn}, Players count: ${players.length}`, 2);
718
- if (!Array.isArray(players)) {
719
- throw new Error("Expected players to be an array");
714
+ if (!(await this.canEndTurn({ instanceAddress, gameId }))) {
715
+ throw new Error("Cannot end turn");
720
716
  }
721
- const proposerIndices = [];
722
- const oldProposals = await this.getProposalsVotedUpon({ instanceAddress, gameId, turn });
723
- const decryptedProposals = await this.decryptProposals({ instanceAddress, gameId, turn });
724
- (0, log_1.logger)(decryptedProposals);
725
- const proposals = players.map((player) => {
726
- const proposal = decryptedProposals.find((p) => p.proposer === player)?.proposal ?? "";
727
- return {
728
- proposer: player,
729
- proposal,
730
- };
731
- });
732
- const maxSize = 15;
733
- if (proposals.length < maxSize) {
734
- for (let i = proposals.length; i < maxSize; i++) {
735
- proposals.push({
736
- proposer: viem_1.zeroAddress,
737
- proposal: "",
738
- });
739
- }
740
- }
741
- (0, log_1.logger)(proposals);
742
- players.forEach((player) => {
743
- let proposerIdx = oldProposals.findIndex((p) => player === p.proposer);
744
- if (proposerIdx === -1)
745
- proposerIdx = players.length; //Did not propose
746
- proposerIndices.push(BigInt(proposerIdx));
747
- });
748
- const voteDecrypted = await this.decryptTurnVotes({ instanceAddress, gameId, turn });
749
- const votes = await Promise.all(players.map(async (player) => {
750
- const vote = voteDecrypted.find((v) => v.player === player);
751
- if (!vote?.votes)
752
- return players.map(() => 0n);
753
- return vote.votes;
754
- }));
717
+ const turn = await this.currentTurn({ instanceAddress, gameId });
718
+ const players = await this.getPlayers({ instanceAddress, gameId });
719
+ (0, log_1.logger)(`Current turn: ${turn}, Players count: ${players.length}`, 2);
720
+ const newPaddedDecryptedProposals = await this.decryptProposals({ instanceAddress, gameId, turn, players: [...players], padToMaxSize: true });
721
+ (0, log_1.logger)(`newPaddedDecryptedProposals:`);
722
+ (0, log_1.logger)(newPaddedDecryptedProposals);
723
+ const votesDecrypted = await this.decryptTurnVotes({ instanceAddress, gameId, turn, players: [...players] });
724
+ (0, log_1.logger)(`votesDecrypted:`);
725
+ (0, log_1.logger)(votesDecrypted);
755
726
  const tableData = players.map((player, idx) => ({
756
727
  player,
757
- proposerIndex: proposerIndices[idx],
758
- proposer: oldProposals[Number(proposerIndices[idx])]?.proposer || "not-proposed",
728
+ voted: this.hasVoted({ vote: votesDecrypted[idx] }),
729
+ proposal: newPaddedDecryptedProposals[idx]?.proposal.substring(0, 50) || "not-proposed",
759
730
  }));
760
731
  console.table(tableData);
761
732
  const attested = await this.getProposalsIntegrity({
@@ -763,16 +734,14 @@ class GameMaster {
763
734
  turn,
764
735
  verifierAddress: instanceAddress,
765
736
  size: players.length,
766
- proposals,
737
+ proposals: newPaddedDecryptedProposals,
767
738
  });
768
- (0, log_1.logger)(`votes:`);
769
- (0, log_1.logger)(votes);
770
739
  const { request } = await this.publicClient.simulateContract({
771
740
  abi: abis_1.RankifyDiamondInstanceAbi,
772
741
  account: this.walletClient.account,
773
742
  address: instanceAddress,
774
743
  functionName: "endTurn",
775
- args: [gameId, votes, attested.newProposals, attested.permutation, attested.nullifier],
744
+ args: [gameId, votesDecrypted, attested.newProposals, attested.prevTurnPermutation, attested.prevTurnSalt],
776
745
  });
777
746
  return this.walletClient.writeContract(request);
778
747
  }
@@ -800,6 +769,8 @@ class GameMaster {
800
769
  const instance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
801
770
  const playerPubKey = await instance.getPlayerPubKey({ instanceAddress, gameId, player });
802
771
  (0, log_1.logger)(`Player public key: ${playerPubKey}`, 2);
772
+ console.log("Player public key:", playerPubKey);
773
+ console.log("Game key:", await this.gameKey({ gameId, contractAddress: instanceAddress }));
803
774
  return instance.sharedSigner({
804
775
  publicKey: playerPubKey,
805
776
  privateKey: await this.gameKey({ gameId, contractAddress: instanceAddress }),
@@ -832,7 +803,12 @@ class GameMaster {
832
803
  */
833
804
  this.attestVote = async ({ voter, gameId, turn, vote, verifierAddress, }) => {
834
805
  (0, log_1.logger)(`Attesting vote for player ${voter} in game ${gameId}, turn ${turn}`);
835
- const gameSize = (await this.getPlayers({ instanceAddress: verifierAddress, gameId })).length;
806
+ const players = await this.getPlayers({ instanceAddress: verifierAddress, gameId });
807
+ const validationResult = await this.validateVote({ gameId, turn, voter, vote, instanceAddress: verifierAddress, players: [...players] });
808
+ if (!validationResult.result) {
809
+ throw new Error('Vote validation failed: ' + validationResult.reason);
810
+ }
811
+ const gameSize = players.length;
836
812
  const instance = new InstanceBase_1.default({
837
813
  instanceAddress: verifierAddress,
838
814
  publicClient: this.publicClient,
@@ -925,7 +901,7 @@ class GameMaster {
925
901
  */
926
902
  this.generateEndTurnIntegrity = async ({ gameId, turn, verifierAddress, size = 15, proposals, }) => {
927
903
  let _proposals = [...proposals];
928
- const { permutation, secret: nullifier } = await this.generateDeterministicPermutation({
904
+ const { permutation: prevTurnPermutation, turnSalt: prevTurnSalt } = await this.generateDeterministicPermutation({
929
905
  gameId,
930
906
  turn: turn - 1n,
931
907
  verifierAddress,
@@ -957,7 +933,10 @@ class GameMaster {
957
933
  (0, log_1.logger)("inputs:", 3);
958
934
  (0, log_1.logger)(inputs, 3);
959
935
  // Apply permutation to proposals array
936
+ console.log("permutation used on new proposals:", inputs.permutation);
937
+ console.log("revealed permutation for prevTurn proposals:", prevTurnPermutation);
960
938
  const permutedProposals = (0, permutations_1.permuteArray)({ array: _proposals, permutation: inputs.permutation });
939
+ console.log("permutedProposals:", permutedProposals);
961
940
  const config = {
962
941
  circuitName: "ProposalsIntegrity15",
963
942
  circuitArtifactsPath: path_1.default.join(__dirname, "../../zk_artifacts/circuits/proposals_integrity_15.circom/"),
@@ -980,8 +959,8 @@ class GameMaster {
980
959
  const c = callData[2].map((c) => BigInt(c));
981
960
  return {
982
961
  commitment: inputs.permutationCommitment,
983
- nullifier,
984
- permutation: permutation,
962
+ prevTurnSalt,
963
+ prevTurnPermutation,
985
964
  permutedProposals: permutedProposals.map((proposal) => proposal.proposal),
986
965
  a,
987
966
  b,
@@ -995,20 +974,19 @@ class GameMaster {
995
974
  */
996
975
  this.createInputs = async ({ numActive, proposals, commitmentRandomnesses, gameId, turn, verifierAddress, }) => {
997
976
  const poseidon = await (0, circomlibjs_1.buildPoseidon)();
998
- const maxSize = 15;
999
977
  // Initialize arrays with zeros
1000
- const commitments = Array(maxSize).fill(0n);
1001
- const randomnesses = Array(maxSize).fill(0n);
1002
- const permutedProposals = Array(maxSize).fill(0n);
978
+ const commitments = Array(this.maxSlotSizeForProofs).fill(0n);
979
+ const randomnesses = Array(this.maxSlotSizeForProofs).fill(0n);
980
+ const permutedProposals = Array(this.maxSlotSizeForProofs).fill(0n);
1003
981
  // Generate deterministic permutation
1004
- const { permutation, secret, commitment } = await this.generateDeterministicPermutation({
982
+ const { permutation, turnSalt: secret, commitment } = await this.generateDeterministicPermutation({
1005
983
  gameId,
1006
984
  turn,
1007
985
  verifierAddress,
1008
986
  size: numActive,
1009
987
  });
1010
988
  // Fill arrays with values
1011
- for (let i = 0; i < maxSize; i++) {
989
+ for (let i = 0; i < this.maxSlotSizeForProofs; i++) {
1012
990
  if (i < numActive) {
1013
991
  // Active slots
1014
992
  const proposal = proposals[i];
@@ -1042,6 +1020,18 @@ class GameMaster {
1042
1020
  this.publicClient = publicClient;
1043
1021
  this.walletClient = walletClient;
1044
1022
  }
1023
+ hasVoted({ vote }) {
1024
+ return vote?.reduce((a, b) => a + b, 0n) !== 0n;
1025
+ }
1026
+ ;
1027
+ getGameState({ gameId, instanceAddress }) {
1028
+ const baseInstance = new InstanceBase_1.default({
1029
+ instanceAddress,
1030
+ publicClient: this.publicClient,
1031
+ chainId: this.chainId,
1032
+ });
1033
+ return baseInstance.getGameStateDetails(gameId);
1034
+ }
1045
1035
  /**
1046
1036
  * Gets proposal integrity data for testing
1047
1037
  * @param params - Parameters including game info and proposal data
@@ -1049,7 +1039,7 @@ class GameMaster {
1049
1039
  */
1050
1040
  async getProposalsIntegrity({ size, gameId, turn, proposals, verifierAddress, }) {
1051
1041
  (0, log_1.logger)(`Generating proposals integrity for game ${gameId}, turn ${turn} with ${size} players.`);
1052
- const { commitment, nullifier, permutation, permutedProposals, a, b, c } = await this.generateEndTurnIntegrity({
1042
+ const { commitment, prevTurnSalt, prevTurnPermutation, permutedProposals, a, b, c } = await this.generateEndTurnIntegrity({
1053
1043
  gameId,
1054
1044
  turn,
1055
1045
  verifierAddress,
@@ -1065,9 +1055,9 @@ class GameMaster {
1065
1055
  proposals: permutedProposals,
1066
1056
  permutationCommitment: commitment,
1067
1057
  },
1068
- permutation: permutation.map((p) => BigInt(p)),
1058
+ prevTurnPermutation: prevTurnPermutation.map((p) => BigInt(p)),
1069
1059
  proposalsNotPermuted: proposals.map((proposal) => proposal.proposal),
1070
- nullifier,
1060
+ prevTurnSalt,
1071
1061
  };
1072
1062
  }
1073
1063
  }