@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.
- 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 +65 -40
- package/cli/rankify/InstanceBase.js.map +1 -1
- package/cli/rankify/MAODistributor.js +1 -1
- package/cli/rankify/MAODistributor.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 +22 -29
- package/docs/classes/InstancePlayer.md +26 -33
- package/docs/docs/classes/GameMaster.md +23 -63
- package/docs/docs/classes/InstanceBase.md +22 -29
- package/docs/docs/classes/InstancePlayer.md +26 -33
- 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 +65 -51
- package/lib.commonjs/rankify/InstanceBase.d.ts.map +1 -1
- package/lib.commonjs/rankify/InstanceBase.js +65 -40
- package/lib.commonjs/rankify/InstanceBase.js.map +1 -1
- package/lib.commonjs/rankify/MAODistributor.d.ts.map +1 -1
- package/lib.commonjs/rankify/MAODistributor.js +1 -1
- package/lib.commonjs/rankify/MAODistributor.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 +65 -51
- package/lib.esm/rankify/InstanceBase.d.ts.map +1 -1
- package/lib.esm/rankify/InstanceBase.js +65 -40
- package/lib.esm/rankify/InstanceBase.js.map +1 -1
- package/lib.esm/rankify/MAODistributor.d.ts.map +1 -1
- package/lib.esm/rankify/MAODistributor.js +1 -1
- package/lib.esm/rankify/MAODistributor.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
|
@@ -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,
|
|
71
|
-
(0, log_1.logger)(`Getting proposals for instance ${instanceAddress}, game ${gameId}, turn ${turn.toString()}
|
|
72
|
-
const
|
|
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
|
|
77
|
+
args: { gameId: gameId, turn: turn },
|
|
77
78
|
fromBlock: 0n,
|
|
78
79
|
});
|
|
79
|
-
(0, log_1.logger)(`Found ${
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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 <
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
298
|
-
|
|
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
|
|
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
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
|
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 ${
|
|
606
|
-
if (
|
|
613
|
+
(0, log_1.logger)(`Found ${VoteSubmittedEvents.length} events`);
|
|
614
|
+
if (VoteSubmittedEvents.length === 0)
|
|
607
615
|
return [];
|
|
608
|
-
//
|
|
616
|
+
//Decrypting votes from events
|
|
609
617
|
const votes = [];
|
|
610
|
-
|
|
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
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
|
|
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
|
-
|
|
706
|
-
|
|
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
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
(
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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
|
-
|
|
758
|
-
|
|
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,
|
|
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
|
|
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,
|
|
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
|
-
|
|
984
|
-
|
|
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(
|
|
1001
|
-
const randomnesses = Array(
|
|
1002
|
-
const permutedProposals = Array(
|
|
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 <
|
|
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,
|
|
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
|
-
|
|
1058
|
+
prevTurnPermutation: prevTurnPermutation.map((p) => BigInt(p)),
|
|
1069
1059
|
proposalsNotPermuted: proposals.map((proposal) => proposal.proposal),
|
|
1070
|
-
|
|
1060
|
+
prevTurnSalt,
|
|
1071
1061
|
};
|
|
1072
1062
|
}
|
|
1073
1063
|
}
|