@peeramid-labs/sdk 3.7.3 → 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 (182) hide show
  1. package/cli/abis/MAODistribution.js +10 -0
  2. package/cli/abis/MAODistribution.js.map +1 -1
  3. package/cli/abis/RankToken.js +84 -0
  4. package/cli/abis/RankToken.js.map +1 -1
  5. package/cli/abis/superinterface.js +9 -3
  6. package/cli/abis/superinterface.js.map +1 -1
  7. package/cli/cli/commands/blockchain/index.js +9 -0
  8. package/cli/cli/commands/blockchain/index.js.map +1 -0
  9. package/cli/cli/commands/blockchain/mine.js +45 -0
  10. package/cli/cli/commands/blockchain/mine.js.map +1 -0
  11. package/cli/cli/commands/distributions/add.js +57 -31
  12. package/cli/cli/commands/distributions/add.js.map +1 -1
  13. package/cli/cli/commands/distributions/index.js +3 -1
  14. package/cli/cli/commands/distributions/index.js.map +1 -1
  15. package/cli/cli/commands/distributions/remove.js +67 -0
  16. package/cli/cli/commands/distributions/remove.js.map +1 -0
  17. package/cli/cli/commands/fellowship/create.js +97 -54
  18. package/cli/cli/commands/fellowship/create.js.map +1 -1
  19. package/cli/cli/commands/fellowship/game/cancel.js +45 -0
  20. package/cli/cli/commands/fellowship/game/cancel.js.map +1 -0
  21. package/cli/cli/commands/fellowship/game/create.js +91 -0
  22. package/cli/cli/commands/fellowship/game/create.js.map +1 -0
  23. package/cli/cli/commands/fellowship/game/end-turn.js +43 -0
  24. package/cli/cli/commands/fellowship/game/end-turn.js.map +1 -0
  25. package/cli/cli/commands/fellowship/game/index.js +31 -0
  26. package/cli/cli/commands/fellowship/game/index.js.map +1 -0
  27. package/cli/cli/commands/fellowship/game/join.js +131 -0
  28. package/cli/cli/commands/fellowship/game/join.js.map +1 -0
  29. package/cli/cli/commands/fellowship/{games.js → game/list.js} +6 -6
  30. package/cli/cli/commands/fellowship/game/list.js.map +1 -0
  31. package/cli/cli/commands/fellowship/game/propose.js +178 -0
  32. package/cli/cli/commands/fellowship/game/propose.js.map +1 -0
  33. package/cli/cli/commands/fellowship/game/start.js +117 -0
  34. package/cli/cli/commands/fellowship/game/start.js.map +1 -0
  35. package/cli/cli/commands/fellowship/game/vote.js +114 -0
  36. package/cli/cli/commands/fellowship/game/vote.js.map +1 -0
  37. package/cli/cli/commands/fellowship/index.js +4 -2
  38. package/cli/cli/commands/fellowship/index.js.map +1 -1
  39. package/cli/cli/commands/fellowship/params.js +49 -0
  40. package/cli/cli/commands/fellowship/params.js.map +1 -0
  41. package/cli/cli/commands/getPk.js +48 -0
  42. package/cli/cli/commands/getPk.js.map +1 -0
  43. package/cli/cli/commands/playbook.js +92 -0
  44. package/cli/cli/commands/playbook.js.map +1 -0
  45. package/cli/cli/getPk.js +62 -0
  46. package/cli/cli/getPk.js.map +1 -0
  47. package/cli/cli/helpers.js +64 -0
  48. package/cli/cli/helpers.js.map +1 -0
  49. package/cli/cli/index.js +6 -0
  50. package/cli/cli/index.js.map +1 -1
  51. package/cli/cli/utils.js +64 -0
  52. package/cli/cli/utils.js.map +1 -0
  53. package/cli/rankify/GameMaster.js +1066 -0
  54. package/cli/rankify/GameMaster.js.map +1 -0
  55. package/cli/rankify/InstanceBase.js +61 -36
  56. package/cli/rankify/InstanceBase.js.map +1 -1
  57. package/cli/rankify/MAODistributor.js +28 -0
  58. package/cli/rankify/MAODistributor.js.map +1 -1
  59. package/cli/rankify/Player.js +355 -0
  60. package/cli/rankify/Player.js.map +1 -0
  61. package/cli/utils/ApiError.js +11 -6
  62. package/cli/utils/ApiError.js.map +1 -1
  63. package/cli/utils/blockchain.js +62 -0
  64. package/cli/utils/blockchain.js.map +1 -0
  65. package/docs/classes/GameMaster.md +23 -63
  66. package/docs/classes/InstanceBase.md +23 -29
  67. package/docs/classes/InstancePlayer.md +27 -33
  68. package/docs/classes/MAODistributorClient.md +18 -1
  69. package/docs/docs/classes/GameMaster.md +23 -63
  70. package/docs/docs/classes/InstanceBase.md +23 -29
  71. package/docs/docs/classes/InstancePlayer.md +27 -33
  72. package/docs/docs/classes/MAODistributorClient.md +18 -1
  73. package/docs/docs/index.md +4 -4
  74. package/docs/index.md +4 -4
  75. package/lib.commonjs/abis/MAODistribution.d.ts +8 -0
  76. package/lib.commonjs/abis/MAODistribution.d.ts.map +1 -1
  77. package/lib.commonjs/abis/MAODistribution.js +10 -0
  78. package/lib.commonjs/abis/MAODistribution.js.map +1 -1
  79. package/lib.commonjs/abis/RankToken.d.ts +65 -0
  80. package/lib.commonjs/abis/RankToken.d.ts.map +1 -1
  81. package/lib.commonjs/abis/RankToken.js +84 -0
  82. package/lib.commonjs/abis/RankToken.js.map +1 -1
  83. package/lib.commonjs/abis/index.d.ts +74 -1
  84. package/lib.commonjs/abis/index.d.ts.map +1 -1
  85. package/lib.commonjs/abis/superinterface.d.ts +1 -1
  86. package/lib.commonjs/abis/superinterface.d.ts.map +1 -1
  87. package/lib.commonjs/abis/superinterface.js +9 -3
  88. package/lib.commonjs/abis/superinterface.js.map +1 -1
  89. package/lib.commonjs/multipass/MultipassBase.d.ts.map +1 -1
  90. package/lib.commonjs/multipass/Registrar.d.ts.map +1 -1
  91. package/lib.commonjs/rankify/GameMaster.d.ts +27 -34
  92. package/lib.commonjs/rankify/GameMaster.d.ts.map +1 -1
  93. package/lib.commonjs/rankify/GameMaster.js +218 -217
  94. package/lib.commonjs/rankify/GameMaster.js.map +1 -1
  95. package/lib.commonjs/rankify/InstanceBase.d.ts +65 -51
  96. package/lib.commonjs/rankify/InstanceBase.d.ts.map +1 -1
  97. package/lib.commonjs/rankify/InstanceBase.js +61 -36
  98. package/lib.commonjs/rankify/InstanceBase.js.map +1 -1
  99. package/lib.commonjs/rankify/MAODistributor.d.ts +1036 -0
  100. package/lib.commonjs/rankify/MAODistributor.d.ts.map +1 -1
  101. package/lib.commonjs/rankify/MAODistributor.js +28 -0
  102. package/lib.commonjs/rankify/MAODistributor.js.map +1 -1
  103. package/lib.commonjs/rankify/Player.d.ts.map +1 -1
  104. package/lib.commonjs/rankify/RankToken.d.ts.map +1 -1
  105. package/lib.commonjs/utils/ApiError.d.ts.map +1 -1
  106. package/lib.commonjs/utils/ApiError.js +11 -6
  107. package/lib.commonjs/utils/ApiError.js.map +1 -1
  108. package/lib.commonjs/utils/artifacts.d.ts.map +1 -1
  109. package/lib.commonjs/utils/blockchain.d.ts +32 -0
  110. package/lib.commonjs/utils/blockchain.d.ts.map +1 -0
  111. package/lib.commonjs/utils/blockchain.js +62 -0
  112. package/lib.commonjs/utils/blockchain.js.map +1 -0
  113. package/lib.commonjs/utils/index.d.ts.map +1 -1
  114. package/lib.commonjs/utils/permutations.d.ts.map +1 -1
  115. package/lib.esm/abis/MAODistribution.d.ts +8 -0
  116. package/lib.esm/abis/MAODistribution.d.ts.map +1 -1
  117. package/lib.esm/abis/MAODistribution.js +10 -0
  118. package/lib.esm/abis/MAODistribution.js.map +1 -1
  119. package/lib.esm/abis/RankToken.d.ts +65 -0
  120. package/lib.esm/abis/RankToken.d.ts.map +1 -1
  121. package/lib.esm/abis/RankToken.js +84 -0
  122. package/lib.esm/abis/RankToken.js.map +1 -1
  123. package/lib.esm/abis/index.d.ts +74 -1
  124. package/lib.esm/abis/index.d.ts.map +1 -1
  125. package/lib.esm/abis/superinterface.d.ts +1 -1
  126. package/lib.esm/abis/superinterface.d.ts.map +1 -1
  127. package/lib.esm/abis/superinterface.js +9 -3
  128. package/lib.esm/abis/superinterface.js.map +1 -1
  129. package/lib.esm/multipass/MultipassBase.d.ts.map +1 -1
  130. package/lib.esm/multipass/Registrar.d.ts.map +1 -1
  131. package/lib.esm/rankify/GameMaster.d.ts +27 -34
  132. package/lib.esm/rankify/GameMaster.d.ts.map +1 -1
  133. package/lib.esm/rankify/GameMaster.js +219 -218
  134. package/lib.esm/rankify/GameMaster.js.map +1 -1
  135. package/lib.esm/rankify/InstanceBase.d.ts +65 -51
  136. package/lib.esm/rankify/InstanceBase.d.ts.map +1 -1
  137. package/lib.esm/rankify/InstanceBase.js +61 -36
  138. package/lib.esm/rankify/InstanceBase.js.map +1 -1
  139. package/lib.esm/rankify/MAODistributor.d.ts +1036 -0
  140. package/lib.esm/rankify/MAODistributor.d.ts.map +1 -1
  141. package/lib.esm/rankify/MAODistributor.js +28 -0
  142. package/lib.esm/rankify/MAODistributor.js.map +1 -1
  143. package/lib.esm/rankify/Player.d.ts.map +1 -1
  144. package/lib.esm/rankify/RankToken.d.ts.map +1 -1
  145. package/lib.esm/utils/ApiError.d.ts.map +1 -1
  146. package/lib.esm/utils/ApiError.js +11 -6
  147. package/lib.esm/utils/ApiError.js.map +1 -1
  148. package/lib.esm/utils/artifacts.d.ts.map +1 -1
  149. package/lib.esm/utils/blockchain.d.ts +32 -0
  150. package/lib.esm/utils/blockchain.d.ts.map +1 -0
  151. package/lib.esm/utils/blockchain.js +58 -0
  152. package/lib.esm/utils/blockchain.js.map +1 -0
  153. package/lib.esm/utils/index.d.ts.map +1 -1
  154. package/lib.esm/utils/permutations.d.ts.map +1 -1
  155. package/package.json +8 -8
  156. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.groth16.vkey.json +1 -0
  157. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.groth16.zkey +0 -0
  158. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.r1cs +0 -0
  159. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.sym +19202 -0
  160. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_artifacts.json +84 -0
  161. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_js/ProposalsIntegrity15.wasm +0 -0
  162. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_js/generate_witness.js +21 -0
  163. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_js/witness_calculator.js +384 -0
  164. package/zk_artifacts/types/core/ProposalsIntegrity15.ts +163 -0
  165. package/zk_artifacts/types/core/index.ts +6 -0
  166. package/zk_artifacts/types/hardhat.d.ts +14 -0
  167. package/zk_artifacts/types/helpers.ts +44 -0
  168. package/zk_artifacts/types/index.ts +8 -0
  169. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.groth16.vkey.json +1 -0
  170. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.groth16.zkey +0 -0
  171. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.r1cs +0 -0
  172. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.sym +19202 -0
  173. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_artifacts.json +84 -0
  174. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_js/ProposalsIntegrity15.wasm +0 -0
  175. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_js/generate_witness.js +21 -0
  176. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_js/witness_calculator.js +384 -0
  177. package/zk_artifacts/zk_artifacts/types/core/ProposalsIntegrity15.ts +163 -0
  178. package/zk_artifacts/zk_artifacts/types/core/index.ts +6 -0
  179. package/zk_artifacts/zk_artifacts/types/hardhat.d.ts +14 -0
  180. package/zk_artifacts/zk_artifacts/types/helpers.ts +44 -0
  181. package/zk_artifacts/zk_artifacts/types/index.ts +8 -0
  182. 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,37 +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
- 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({
79
81
  abi: abis_1.RankifyDiamondInstanceAbi,
80
82
  address: instanceAddress,
81
83
  eventName: "ProposalSubmitted",
82
- args: { gameId: gameId, turn: turn, proposer: proposer },
84
+ args: { gameId: gameId, turn: turn },
83
85
  fromBlock: 0n,
84
86
  });
87
+ (0, log_1.logger)(`Found ${ProposalSubmittedEvents.length} proposals`);
85
88
  const instance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
86
- if (evts.length == 0)
87
- return [];
88
- (0, log_1.logger)(`Decrypting ${evts.length} proposals`);
89
- const proposals = await Promise.all(evts.map(async (log) => {
90
- (0, log_1.logger)(`Decrypting proposal ${log.args.proposer}`);
91
- if (!log.args.proposer)
92
- throw new Error("No proposer");
93
- if (!log.args.encryptedProposal)
94
- throw new Error("No proposalEncryptedByGM");
95
- return {
96
- proposer: log.args.proposer,
97
- proposal: await this.decryptProposal({
98
- proposal: log.args.encryptedProposal,
99
- turn: turn,
100
- instanceAddress: instanceAddress,
101
- 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 {
102
105
  proposer: log.args.proposer,
103
- instance,
104
- }),
105
- };
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
+ }
106
116
  }));
107
- 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;
108
122
  };
109
123
  /**
110
124
  * Generates a deterministic permutation for a specific game turn
@@ -115,11 +129,10 @@ class GameMaster {
115
129
  * @returns The generated permutation, secret, and commitment
116
130
  */
117
131
  getPermutation = async ({ gameId, turn, size, verifierAddress, }) => {
118
- const maxSize = 15;
119
132
  const turnSalt = await this.getTurnSalt({ gameId, turn, verifierAddress });
120
133
  // Create deterministic seed from game parameters and GM's signature
121
134
  // Use the seed to generate permutation
122
- const permutation = Array.from({ length: maxSize }, (_, i) => i);
135
+ const permutation = Array.from({ length: this.maxSlotSizeForProofs }, (_, i) => i);
123
136
  // Fisher-Yates shuffle with deterministic randomness
124
137
  for (let i = size - 1; i >= 0; i--) {
125
138
  // Generate deterministic random number for this position
@@ -130,7 +143,7 @@ class GameMaster {
130
143
  [permutation[i], permutation[j]] = [permutation[j], permutation[i]];
131
144
  }
132
145
  // Ensure inactive slots map to themselves
133
- for (let i = size; i < maxSize; i++) {
146
+ for (let i = size; i < this.maxSlotSizeForProofs; i++) {
134
147
  permutation[i] = i;
135
148
  }
136
149
  return { permutation, turnSalt };
@@ -155,7 +168,7 @@ class GameMaster {
155
168
  const commitment = BigInt(poseidon.F.toObject(poseidon([PoseidonThird, turnSalt])));
156
169
  return {
157
170
  permutation,
158
- secret: turnSalt,
171
+ turnSalt,
159
172
  commitment,
160
173
  };
161
174
  };
@@ -224,58 +237,11 @@ class GameMaster {
224
237
  verifierAddress,
225
238
  size,
226
239
  }).then((perm) => {
227
- 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]));
228
241
  });
229
242
  (0, log_1.logger)(`Generated vote salt for player ${player}`);
230
243
  return result;
231
244
  };
232
- getProposalsVotedUpon = async ({ instanceAddress, gameId, turn, }) => {
233
- const oldProposals = [];
234
- //Proposals sequence is directly corresponding to proposers sequence
235
- if (turn != 1n) {
236
- const endedEvents = await this.publicClient.getContractEvents({
237
- address: instanceAddress,
238
- abi: abis_1.RankifyDiamondInstanceAbi,
239
- eventName: "TurnEnded",
240
- args: { gameId, turn: turn - 1n },
241
- fromBlock: 0n,
242
- });
243
- const evt = endedEvents[0];
244
- if (endedEvents.length > 1)
245
- throw new Error("Multiple turns ended");
246
- const args = evt.args;
247
- const decryptedProposals = await this.decryptProposals({ instanceAddress, gameId, turn: turn - 1n });
248
- if (args.newProposals) {
249
- args.newProposals.slice(0, args?.players?.length).forEach((proposal, idx) => {
250
- if (proposal !== "") {
251
- const proposer = decryptedProposals.find((p) => p.proposal === proposal)?.proposer;
252
- if (!proposer)
253
- throw new Error("No proposer found for proposal");
254
- oldProposals[idx] = {
255
- proposer,
256
- proposal: proposal,
257
- };
258
- }
259
- });
260
- }
261
- else {
262
- const _players = await this.publicClient.readContract({
263
- address: instanceAddress,
264
- abi: abis_1.RankifyDiamondInstanceAbi,
265
- functionName: "getPlayers",
266
- args: [gameId],
267
- });
268
- // Boundary case if no-one proposed a thing
269
- _players.forEach((p, idx) => {
270
- oldProposals[idx] = {
271
- proposer: p,
272
- proposal: "",
273
- };
274
- });
275
- }
276
- }
277
- return oldProposals;
278
- };
279
245
  /**
280
246
  * Finds the index of a player's ongoing proposal
281
247
  * @param gameId - ID of the game
@@ -283,31 +249,17 @@ class GameMaster {
283
249
  * @returns Index of the player's proposal, -1 if not found
284
250
  */
285
251
  findPlayerOngoingProposalIndex = async ({ instanceAddress, gameId, player, turn, }) => {
286
- const baseInstance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
287
- let currentTurn = 0n;
288
252
  if (!turn) {
289
- const r = await baseInstance.getOngoingProposals(gameId);
290
- currentTurn = r.currentTurn;
291
- if (currentTurn == 0n) {
292
- console.error("No proposals in turn 0");
293
- return -1;
294
- }
295
- }
296
- else {
297
- currentTurn = turn;
253
+ turn = await this.currentTurn({ instanceAddress, gameId });
298
254
  }
299
- const proposalsVotedUpon = await this.getProposalsVotedUpon({ instanceAddress, gameId, turn: currentTurn });
300
- 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);
301
258
  };
302
259
  validateJoinGame = async (props) => {
303
260
  const { gameId, participant, instanceAddress } = props;
304
261
  try {
305
- const baseInstance = new InstanceBase_1.default({
306
- instanceAddress,
307
- publicClient: this.publicClient,
308
- chainId: this.chainId,
309
- });
310
- const gameState = await baseInstance.getGameStateDetails(gameId);
262
+ const gameState = await this.getGameState({ gameId, instanceAddress });
311
263
  if (gameState.gamePhase !== types_1.gameStatusEnum.open) {
312
264
  return { result: false, errorMessage: "Game is not open for registration" };
313
265
  }
@@ -335,6 +287,10 @@ class GameMaster {
335
287
  throw new Error("No account");
336
288
  (0, log_1.logger)(`Signing joining game..`);
337
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
+ }
338
294
  const baseInstance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
339
295
  const eip712 = await baseInstance.getEIP712Domain();
340
296
  (0, log_1.logger)({
@@ -398,19 +354,12 @@ class GameMaster {
398
354
  * @returns Transaction hash
399
355
  */
400
356
  submitVote = async ({ instanceAddress, gameId, vote, voter, voterSignature, ballotHash, ballotId, }) => {
401
- if (!gameId)
402
- throw new Error("No gameId");
403
- if (!vote)
404
- throw new Error("No votesHidden");
405
- if (!voter)
406
- throw new Error("No voter");
407
- const proposerIdx = await this.findPlayerOngoingProposalIndex({ instanceAddress, gameId, player: voter });
408
- if (proposerIdx == -1)
409
- throw new Error("You are not a proposer in this game");
410
- if (vote[proposerIdx] !== 0n)
411
- throw new Error("You cannot vote for your own proposal");
412
- if (!this.walletClient?.account?.address)
413
- 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
+ }
414
363
  try {
415
364
  const { request } = await this.publicClient.simulateContract({
416
365
  account: this.walletClient.account,
@@ -425,6 +374,84 @@ class GameMaster {
425
374
  throw await (0, utils_1.handleRPCError)(e);
426
375
  }
427
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
+ };
428
455
  /**
429
456
  * Types for proposal submission
430
457
  */
@@ -567,8 +594,6 @@ class GameMaster {
567
594
  * @returns Transaction hash
568
595
  */
569
596
  submitProposal = async ({ instanceAddress, submissionParams, proposerSignature, }) => {
570
- // let proposalData: GetAbiItemParameters<typeof RankifyDiamondInstanceAbi, "submitProposal">["args"];
571
- // proposalData[0].
572
597
  const txParams = [
573
598
  {
574
599
  ...submissionParams,
@@ -595,19 +620,23 @@ class GameMaster {
595
620
  * @param turn - Turn number
596
621
  * @returns Array of decrypted votes with player addresses
597
622
  */
598
- decryptTurnVotes = async ({ instanceAddress, gameId, turn, }) => {
599
- const evts = await this.publicClient.getContractEvents({
623
+ decryptTurnVotes = async ({ instanceAddress, gameId, turn, players = [], }) => {
624
+ (0, log_1.logger)(`Decrypting votes for game ${BigInt(gameId)} turn ${turn} at address ${instanceAddress} at ${await this.publicClient.getBlockNumber()} block`);
625
+ const VoteSubmittedEvents = await this.publicClient.getContractEvents({
600
626
  address: instanceAddress,
601
627
  abi: abis_1.RankifyDiamondInstanceAbi,
602
628
  eventName: "VoteSubmitted",
603
- args: { gameId, turn },
629
+ fromBlock: 0n,
630
+ args: { turn, gameId },
604
631
  });
605
- (0, log_1.logger)(`Found ${evts.length} events`);
606
- if (evts.length === 0)
632
+ (0, log_1.logger)(`Found ${VoteSubmittedEvents.length} events`);
633
+ if (VoteSubmittedEvents.length === 0)
607
634
  return [];
608
- // Process events one by one to allow errors to propagate
635
+ //Decrypting votes from events
609
636
  const votes = [];
610
- for (const event of evts) {
637
+ console.log("Events:", VoteSubmittedEvents);
638
+ for (const event of VoteSubmittedEvents) {
639
+ console.log("Event:", event);
611
640
  if (!event.args.player)
612
641
  throw new Error("No player in event");
613
642
  if (!event.args.sealedBallotId)
@@ -624,26 +653,13 @@ class GameMaster {
624
653
  votes: decryptedVotes,
625
654
  });
626
655
  }
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
- 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;
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;
647
663
  };
648
664
  /**
649
665
  * Checks if the current turn can be ended
@@ -651,12 +667,24 @@ class GameMaster {
651
667
  * @returns Boolean indicating if turn can be ended
652
668
  */
653
669
  canEndTurn = async ({ instanceAddress, gameId }) => {
654
- return this.publicClient.readContract({
670
+ const canEndTurn = await this.publicClient.readContract({
655
671
  address: instanceAddress,
656
672
  abi: abis_1.RankifyDiamondInstanceAbi,
657
673
  functionName: "canEndTurn",
658
674
  args: [gameId],
659
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;
660
688
  };
661
689
  /**
662
690
  * Gets the current turn number
@@ -700,49 +728,24 @@ class GameMaster {
700
728
  * @returns Transaction hash
701
729
  */
702
730
  endTurn = async ({ instanceAddress, gameId }) => {
731
+ (0, log_1.logger)(`Ending turn for game ${gameId}`, 2);
703
732
  try {
704
- const turn = await this.publicClient.readContract({
705
- address: instanceAddress,
706
- abi: abis_1.RankifyDiamondInstanceAbi,
707
- functionName: "getTurn",
708
- args: [gameId],
709
- });
710
- const players = (await this.publicClient.readContract({
711
- address: instanceAddress,
712
- abi: abis_1.RankifyDiamondInstanceAbi,
713
- functionName: "getPlayers",
714
- args: [gameId],
715
- }));
716
- if (!Array.isArray(players)) {
717
- throw new Error("Expected players to be an array");
733
+ if (!(await this.canEndTurn({ instanceAddress, gameId }))) {
734
+ throw new Error("Cannot end turn");
718
735
  }
719
- const proposerIndices = [];
720
- const oldProposals = await this.getProposalsVotedUpon({ instanceAddress, gameId, turn });
721
- const decryptedProposals = await this.decryptProposals({ instanceAddress, gameId, turn });
722
- const proposals = players.map((player) => {
723
- const proposal = decryptedProposals.find((p) => p.proposer === player)?.proposal ?? "";
724
- return {
725
- proposer: player,
726
- proposal,
727
- };
728
- });
729
- players.forEach((player) => {
730
- let proposerIdx = oldProposals.findIndex((p) => player === p.proposer);
731
- if (proposerIdx === -1)
732
- proposerIdx = players.length; //Did not propose
733
- proposerIndices.push(BigInt(proposerIdx));
734
- });
735
- const voteDecrypted = await this.decryptTurnVotes({ instanceAddress, gameId, turn });
736
- const votes = await Promise.all(players.map(async (player) => {
737
- const vote = voteDecrypted.find((v) => v.player === player);
738
- if (!vote?.votes)
739
- return players.map(() => 0n);
740
- return vote.votes;
741
- }));
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);
742
745
  const tableData = players.map((player, idx) => ({
743
746
  player,
744
- proposerIndex: proposerIndices[idx],
745
- 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",
746
749
  }));
747
750
  console.table(tableData);
748
751
  const attested = await this.getProposalsIntegrity({
@@ -750,16 +753,14 @@ class GameMaster {
750
753
  turn,
751
754
  verifierAddress: instanceAddress,
752
755
  size: players.length,
753
- proposals,
756
+ proposals: newPaddedDecryptedProposals,
754
757
  });
755
- (0, log_1.logger)(`votes:`);
756
- (0, log_1.logger)(votes);
757
758
  const { request } = await this.publicClient.simulateContract({
758
759
  abi: abis_1.RankifyDiamondInstanceAbi,
759
760
  account: this.walletClient.account,
760
761
  address: instanceAddress,
761
762
  functionName: "endTurn",
762
- args: [gameId, votes, attested.newProposals, attested.permutation, attested.nullifier],
763
+ args: [gameId, votesDecrypted, attested.newProposals, attested.prevTurnPermutation, attested.prevTurnSalt],
763
764
  });
764
765
  return this.walletClient.writeContract(request);
765
766
  }
@@ -787,6 +788,8 @@ class GameMaster {
787
788
  const instance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
788
789
  const playerPubKey = await instance.getPlayerPubKey({ instanceAddress, gameId, player });
789
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 }));
790
793
  return instance.sharedSigner({
791
794
  publicKey: playerPubKey,
792
795
  privateKey: await this.gameKey({ gameId, contractAddress: instanceAddress }),
@@ -819,7 +822,12 @@ class GameMaster {
819
822
  */
820
823
  attestVote = async ({ voter, gameId, turn, vote, verifierAddress, }) => {
821
824
  (0, log_1.logger)(`Attesting vote for player ${voter} in game ${gameId}, turn ${turn}`);
822
- 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;
823
831
  const instance = new InstanceBase_1.default({
824
832
  instanceAddress: verifierAddress,
825
833
  publicClient: this.publicClient,
@@ -911,14 +919,14 @@ class GameMaster {
911
919
  * @returns Integrity data including permutation, secret, and proof
912
920
  */
913
921
  generateEndTurnIntegrity = async ({ gameId, turn, verifierAddress, size = 15, proposals, }) => {
914
- const maxSize = 15;
915
- const { permutation, secret: nullifier } = await this.generateDeterministicPermutation({
922
+ let _proposals = [...proposals];
923
+ const { permutation: prevTurnPermutation, turnSalt: prevTurnSalt } = await this.generateDeterministicPermutation({
916
924
  gameId,
917
925
  turn: turn - 1n,
918
926
  verifierAddress,
919
927
  size,
920
928
  });
921
- const values = await Promise.all(proposals.map((p) => p.proposal === ""
929
+ const values = await Promise.all(_proposals.map((p) => p.proposal === ""
922
930
  ? {
923
931
  proposalValue: 0n,
924
932
  randomnessValue: 0n,
@@ -931,6 +939,8 @@ class GameMaster {
931
939
  turn,
932
940
  proposer: p.proposer,
933
941
  })));
942
+ (0, log_1.logger)("proposals with added empty proposals:", 3);
943
+ (0, log_1.logger)(_proposals, 3);
934
944
  const inputs = await this.createInputs({
935
945
  numActive: size,
936
946
  proposals: values.map((v) => v.proposalValue),
@@ -939,36 +949,26 @@ class GameMaster {
939
949
  turn,
940
950
  verifierAddress,
941
951
  });
942
- (0, log_1.logger)(inputs, 2);
952
+ (0, log_1.logger)("inputs:", 3);
953
+ (0, log_1.logger)(inputs, 3);
943
954
  // Apply permutation to proposals array
944
- const permutedProposals = [...proposals];
945
- for (let i = 0; i < maxSize; i++) {
946
- if (i < size) {
947
- permutedProposals[Number(inputs.permutation[i])] = proposals[i];
948
- }
949
- }
955
+ console.log("permutation used on new proposals:", inputs.permutation);
956
+ console.log("revealed permutation for prevTurn proposals:", prevTurnPermutation);
957
+ const permutedProposals = (0, permutations_1.permuteArray)({ array: _proposals, permutation: inputs.permutation });
958
+ console.log("permutedProposals:", permutedProposals);
950
959
  const config = {
951
960
  circuitName: "ProposalsIntegrity15",
952
- // node_modules/rankify-contracts/zk_artifacts
953
- circuitArtifactsPath: path_1.default.join(__dirname, "../../node_modules/rankify-contracts/zk_artifacts/circuits/proposals_integrity_15.circom/"),
954
- verifierDirPath: path_1.default.join(__dirname, "../../node_modules/rankify-contracts/src/verifiers"),
961
+ circuitArtifactsPath: path_1.default.join(__dirname, "../../zk_artifacts/circuits/proposals_integrity_15.circom/"),
962
+ verifierDirPath: path_1.default.join(__dirname, "../../zk_artifacts/verifiers"),
955
963
  };
956
964
  const implementer = new zkit_1.Groth16Implementer();
957
965
  const circuit = new zkit_1.CircuitZKit(config, implementer);
958
966
  const proof = await circuit.generateProof(inputs);
967
+ (0, log_1.logger)("proof:", 3);
968
+ (0, log_1.logger)(proof, 3);
959
969
  const callData = await circuit.generateCalldata(proof);
960
- // const circuit = await hre.zkit.getCircuit("ProposalsIntegrity15");
961
- // const inputsKey = ethers.utils.solidityKeccak256(["string"], [JSON.stringify(inputs) + "groth16"]);
962
- // let cached = loadFromCache(inputsKey);
963
- // if (cached) {
964
- // log(`Loaded proof from cache for inputsKey ${inputsKey}`, 2);
965
- // } else {
966
- // log(`Generating proof for inputsKey ${inputsKey}`, 2);
967
- // const proof = await circuit.generateProof(inputs);
968
- // saveToCache(inputsKey, proof);
969
- // cached = proof;
970
- // }
971
- // const proof = "0x00";
970
+ (0, log_1.logger)("callData:", 3);
971
+ (0, log_1.logger)(callData, 3);
972
972
  if (!proof) {
973
973
  throw new Error("Proof not found");
974
974
  }
@@ -978,8 +978,8 @@ class GameMaster {
978
978
  const c = callData[2].map((c) => BigInt(c));
979
979
  return {
980
980
  commitment: inputs.permutationCommitment,
981
- nullifier,
982
- permutation: permutation.slice(0, size),
981
+ prevTurnSalt,
982
+ prevTurnPermutation,
983
983
  permutedProposals: permutedProposals.map((proposal) => proposal.proposal),
984
984
  a,
985
985
  b,
@@ -993,7 +993,7 @@ class GameMaster {
993
993
  */
994
994
  async getProposalsIntegrity({ size, gameId, turn, proposals, verifierAddress, }) {
995
995
  (0, log_1.logger)(`Generating proposals integrity for game ${gameId}, turn ${turn} with ${size} players.`);
996
- const { commitment, nullifier, permutation, permutedProposals, a, b, c } = await this.generateEndTurnIntegrity({
996
+ const { commitment, prevTurnSalt, prevTurnPermutation, permutedProposals, a, b, c } = await this.generateEndTurnIntegrity({
997
997
  gameId,
998
998
  turn,
999
999
  verifierAddress,
@@ -1009,9 +1009,9 @@ class GameMaster {
1009
1009
  proposals: permutedProposals,
1010
1010
  permutationCommitment: commitment,
1011
1011
  },
1012
- permutation: permutation.map((p) => BigInt(p)),
1012
+ prevTurnPermutation: prevTurnPermutation.map((p) => BigInt(p)),
1013
1013
  proposalsNotPermuted: proposals.map((proposal) => proposal.proposal),
1014
- nullifier,
1014
+ prevTurnSalt,
1015
1015
  };
1016
1016
  }
1017
1017
  /**
@@ -1021,20 +1021,19 @@ class GameMaster {
1021
1021
  */
1022
1022
  createInputs = async ({ numActive, proposals, commitmentRandomnesses, gameId, turn, verifierAddress, }) => {
1023
1023
  const poseidon = await (0, circomlibjs_1.buildPoseidon)();
1024
- const maxSize = 15;
1025
1024
  // Initialize arrays with zeros
1026
- const commitments = Array(maxSize).fill(0n);
1027
- const randomnesses = Array(maxSize).fill(0n);
1028
- 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);
1029
1028
  // Generate deterministic permutation
1030
- const { permutation, secret, commitment } = await this.generateDeterministicPermutation({
1029
+ const { permutation, turnSalt: secret, commitment } = await this.generateDeterministicPermutation({
1031
1030
  gameId,
1032
1031
  turn,
1033
1032
  verifierAddress,
1034
1033
  size: numActive,
1035
1034
  });
1036
1035
  // Fill arrays with values
1037
- for (let i = 0; i < maxSize; i++) {
1036
+ for (let i = 0; i < this.maxSlotSizeForProofs; i++) {
1038
1037
  if (i < numActive) {
1039
1038
  // Active slots
1040
1039
  const proposal = proposals[i];
@@ -1046,6 +1045,7 @@ class GameMaster {
1046
1045
  permutedProposals[permutation[i]] = proposal;
1047
1046
  }
1048
1047
  else {
1048
+ (0, log_1.logger)(`Inactive slot ${i}`, 3);
1049
1049
  // Inactive slots
1050
1050
  const hash = poseidon([0n, 0n]);
1051
1051
  commitments[i] = BigInt(poseidon.F.toObject(hash));
@@ -1065,4 +1065,5 @@ class GameMaster {
1065
1065
  };
1066
1066
  }
1067
1067
  exports.GameMaster = GameMaster;
1068
+ exports.default = GameMaster;
1068
1069
  //# sourceMappingURL=GameMaster.js.map