From 0cde2dc140168d8a196afa6a8272ff8e28b7f77a Mon Sep 17 00:00:00 2001 From: Eshaan Bansal Date: Sat, 3 Aug 2024 15:05:04 +0530 Subject: [PATCH] fixes #21: use merkelize in getRootHash (#24) * use merkle tree for root hash * other fixes * chore: changes --------- Co-authored-by: Aashutosh Rathi --- rollup/stackr/machine.ts | 16 +++++++-------- rollup/stackr/transitions.ts | 40 +++++++++++++++--------------------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/rollup/stackr/machine.ts b/rollup/stackr/machine.ts index 4d5b4d2..ad1f659 100644 --- a/rollup/stackr/machine.ts +++ b/rollup/stackr/machine.ts @@ -1,5 +1,6 @@ import { State, StateMachine } from "@stackr/sdk/machine"; -import { solidityPackedKeccak256 } from "ethers"; +import { merklize } from "@stackr/sdk"; +import { solidityPacked } from "ethers"; import genesisState from "../genesis-state.json"; import { transitions } from "./transitions"; @@ -24,14 +25,13 @@ export class AppState extends State { transformer() { return { wrap: () => { - const games = this.state.games.reduce((acc, game) => { + const games = this.state.games.reduce((acc, game) => { const { id, ...rest } = game; acc[id] = { ...rest }; return acc; }, {}); return { games }; }, - unwrap: (wrappedState: WrappedState) => { const games = Object.keys(wrappedState.games).map((id) => ({ id, @@ -43,12 +43,12 @@ export class AppState extends State { }; } - // TODO: change this to MerkleTree - getRootHash() { - return solidityPackedKeccak256( - ["string"], - [JSON.stringify(this.state.games)] + getRootHash(): string { + const leaves = this.state.games.map( + ({ id, player, score }) => + solidityPacked(["string", "address", "uint256"], [id, player, score]) ); + return merklize(leaves); } } diff --git a/rollup/stackr/transitions.ts b/rollup/stackr/transitions.ts index eb9eb8b..87be457 100644 --- a/rollup/stackr/transitions.ts +++ b/rollup/stackr/transitions.ts @@ -1,29 +1,29 @@ -import { STF, Transitions } from "@stackr/sdk/machine"; +import { REQUIRE, STF, Transitions } from "@stackr/sdk/machine"; import { hashMessage } from "ethers"; import { ACTIONS, GameMode } from "../../client/game/gameMode"; import { World } from "../../client/game/world"; import { AppState } from "./machine"; -export type CreateGame = { +export type StartGameInput = { timestamp: number; }; -export type ValidateGameInput = { - gameId: number; +export type EndGameInput = { + gameId: string; + timestamp: number; score: number; gameInputs: string; }; -const startGame: STF = { +const startGame: STF = { handler: ({ state, msgSender, block, emit }) => { const gameId = hashMessage( `${msgSender}::${block.timestamp}::${Object.keys(state.games).length}` ); state.games[gameId] = { - id: gameId, score: 0, - player: msgSender, + player: String(msgSender), }; emit({ @@ -34,22 +34,15 @@ const startGame: STF = { }, }; -const endGame: STF = { +const endGame: STF = { handler: ({ state, inputs, msgSender }) => { const { gameInputs, gameId, score } = inputs; const { games } = state; - if (!games[gameId]) { - throw new Error("Game not found"); - } - - if (games[gameId].score > 0) { - throw new Error("Game already ended"); - } - - if (games[gameId].player !== msgSender) { - throw new Error("Unauthorized to end game"); - } - + // validation checks + REQUIRE(!!games[gameId], "GAME_NOT_FOUND"); + REQUIRE(games[gameId].score === 0, "GAME_ALREADY_ENDED"); + REQUIRE(games[gameId].player === String(msgSender), "UNAUTHORIZED"); + // rerun game loop const world = new World(); const gameMode = new GameMode(world, { gameId }); const ticks = gameInputs @@ -60,9 +53,10 @@ const endGame: STF = { gameMode.deserializeAndUpdate(1 / 60, ticks[i]); } - if (world.score !== score) { - throw new Error(`Failed to replay: ${world.score} !== ${score}`); - } + REQUIRE( + world.score === score, + `FAILED_TO_REPLAY: ${world.score} !== ${score}` + ); games[gameId].score = score;