@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.
- package/cli/cli/commands/blockchain/index.js +9 -0
- package/cli/cli/commands/blockchain/index.js.map +1 -0
- package/cli/cli/commands/blockchain/mine.js +45 -0
- package/cli/cli/commands/blockchain/mine.js.map +1 -0
- package/cli/cli/commands/distributions/add.js +55 -31
- package/cli/cli/commands/distributions/add.js.map +1 -1
- package/cli/cli/commands/fellowship/create.js +96 -60
- package/cli/cli/commands/fellowship/create.js.map +1 -1
- package/cli/cli/commands/fellowship/game/cancel.js +45 -0
- package/cli/cli/commands/fellowship/game/cancel.js.map +1 -0
- package/cli/cli/commands/fellowship/game/create.js +91 -0
- package/cli/cli/commands/fellowship/game/create.js.map +1 -0
- package/cli/cli/commands/fellowship/{endTurn.js → game/end-turn.js} +8 -7
- package/cli/cli/commands/fellowship/game/end-turn.js.map +1 -0
- package/cli/cli/commands/fellowship/game/index.js +31 -0
- package/cli/cli/commands/fellowship/game/index.js.map +1 -0
- package/cli/cli/commands/fellowship/game/join.js +131 -0
- package/cli/cli/commands/fellowship/game/join.js.map +1 -0
- package/cli/cli/commands/fellowship/{games.js → game/list.js} +5 -5
- package/cli/cli/commands/fellowship/game/list.js.map +1 -0
- package/cli/cli/commands/fellowship/game/propose.js +178 -0
- package/cli/cli/commands/fellowship/game/propose.js.map +1 -0
- package/cli/cli/commands/fellowship/game/start.js +117 -0
- package/cli/cli/commands/fellowship/game/start.js.map +1 -0
- package/cli/cli/commands/fellowship/game/vote.js +114 -0
- package/cli/cli/commands/fellowship/game/vote.js.map +1 -0
- package/cli/cli/commands/fellowship/index.js +3 -5
- package/cli/cli/commands/fellowship/index.js.map +1 -1
- package/cli/cli/commands/getPk.js +48 -0
- package/cli/cli/commands/getPk.js.map +1 -0
- package/cli/cli/commands/playbook.js +92 -0
- package/cli/cli/commands/playbook.js.map +1 -0
- package/cli/cli/getPk.js +62 -0
- package/cli/cli/getPk.js.map +1 -0
- package/cli/cli/helpers.js +64 -0
- package/cli/cli/helpers.js.map +1 -0
- package/cli/cli/index.js +6 -0
- package/cli/cli/index.js.map +1 -1
- package/cli/cli/utils.js +64 -0
- package/cli/cli/utils.js.map +1 -0
- package/cli/rankify/GameMaster.js +199 -209
- package/cli/rankify/GameMaster.js.map +1 -1
- package/cli/rankify/InstanceBase.js +60 -36
- package/cli/rankify/InstanceBase.js.map +1 -1
- package/cli/rankify/Player.js +355 -0
- package/cli/rankify/Player.js.map +1 -0
- package/cli/utils/blockchain.js +62 -0
- package/cli/utils/blockchain.js.map +1 -0
- package/docs/classes/GameMaster.md +23 -63
- package/docs/classes/InstanceBase.md +21 -27
- package/docs/classes/InstancePlayer.md +25 -31
- package/docs/docs/classes/GameMaster.md +23 -63
- package/docs/docs/classes/InstanceBase.md +21 -27
- package/docs/docs/classes/InstancePlayer.md +25 -31
- package/docs/docs/index.md +1 -1
- package/docs/index.md +1 -1
- package/lib.commonjs/rankify/GameMaster.d.ts +26 -33
- package/lib.commonjs/rankify/GameMaster.d.ts.map +1 -1
- package/lib.commonjs/rankify/GameMaster.js +199 -209
- package/lib.commonjs/rankify/GameMaster.js.map +1 -1
- package/lib.commonjs/rankify/InstanceBase.d.ts +64 -50
- package/lib.commonjs/rankify/InstanceBase.d.ts.map +1 -1
- package/lib.commonjs/rankify/InstanceBase.js +60 -36
- package/lib.commonjs/rankify/InstanceBase.js.map +1 -1
- package/lib.commonjs/utils/blockchain.d.ts +32 -0
- package/lib.commonjs/utils/blockchain.d.ts.map +1 -0
- package/lib.commonjs/utils/blockchain.js +62 -0
- package/lib.commonjs/utils/blockchain.js.map +1 -0
- package/lib.esm/rankify/GameMaster.d.ts +26 -33
- package/lib.esm/rankify/GameMaster.d.ts.map +1 -1
- package/lib.esm/rankify/GameMaster.js +199 -209
- package/lib.esm/rankify/GameMaster.js.map +1 -1
- package/lib.esm/rankify/InstanceBase.d.ts +64 -50
- package/lib.esm/rankify/InstanceBase.d.ts.map +1 -1
- package/lib.esm/rankify/InstanceBase.js +60 -36
- package/lib.esm/rankify/InstanceBase.js.map +1 -1
- package/lib.esm/utils/blockchain.d.ts +32 -0
- package/lib.esm/utils/blockchain.d.ts.map +1 -0
- package/lib.esm/utils/blockchain.js +58 -0
- package/lib.esm/utils/blockchain.js.map +1 -0
- package/package.json +1 -1
- package/cli/cli/commands/fellowship/endTurn.js.map +0 -1
- 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,
|
|
78
|
-
(0, log_1.logger)(`Getting proposals for instance ${instanceAddress}, game ${gameId}, turn ${turn.toString()}
|
|
79
|
-
const
|
|
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
|
|
84
|
+
args: { gameId: gameId, turn: turn },
|
|
84
85
|
fromBlock: 0n,
|
|
85
86
|
});
|
|
86
|
-
(0, log_1.logger)(`Found ${
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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 <
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
305
|
-
|
|
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
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
|
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 ${
|
|
613
|
-
if (
|
|
632
|
+
(0, log_1.logger)(`Found ${VoteSubmittedEvents.length} events`);
|
|
633
|
+
if (VoteSubmittedEvents.length === 0)
|
|
614
634
|
return [];
|
|
615
|
-
//
|
|
635
|
+
//Decrypting votes from events
|
|
616
636
|
const votes = [];
|
|
617
|
-
|
|
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
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
-
|
|
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
|
-
|
|
713
|
-
|
|
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
|
-
(
|
|
749
|
-
players.
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
});
|
|
755
|
-
|
|
756
|
-
|
|
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
|
-
|
|
765
|
-
|
|
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,
|
|
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
|
|
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,
|
|
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
|
-
|
|
991
|
-
|
|
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,
|
|
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
|
-
|
|
1012
|
+
prevTurnPermutation: prevTurnPermutation.map((p) => BigInt(p)),
|
|
1022
1013
|
proposalsNotPermuted: proposals.map((proposal) => proposal.proposal),
|
|
1023
|
-
|
|
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(
|
|
1036
|
-
const randomnesses = Array(
|
|
1037
|
-
const permutedProposals = Array(
|
|
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 <
|
|
1036
|
+
for (let i = 0; i < this.maxSlotSizeForProofs; i++) {
|
|
1047
1037
|
if (i < numActive) {
|
|
1048
1038
|
// Active slots
|
|
1049
1039
|
const proposal = proposals[i];
|