From eab7eff3e1cabe45a2be7cfeac6a6f3646cc91a4 Mon Sep 17 00:00:00 2001 From: arjanjohan Date: Sun, 28 Apr 2024 09:53:44 +0200 Subject: [PATCH] Initialize subgraph --- README.md | 4 +- packages/graph/schema.graphql | 10 +- packages/graph/src/contract.ts | 43 ++- packages/graph2/.gitignore | 34 ++ packages/graph2/abis/ScrollFighter.json | 344 ++++++++++++++++++ packages/graph2/docker-compose.yml | 50 +++ packages/graph2/networks.json | 8 + packages/graph2/package.json | 18 + packages/graph2/schema.graphql | 61 ++++ packages/graph2/src/scroll-fighter.ts | 101 +++++ packages/graph2/subgraph.yaml | 38 ++ packages/graph2/tests/scroll-fighter-utils.ts | 197 ++++++++++ packages/graph2/tests/scroll-fighter.test.ts | 100 +++++ packages/graph2/tsconfig.json | 4 + packages/hardhat/contracts/ScrollFighter.sol | 24 +- .../nextjs/contracts/deployedContracts.ts | 42 ++- 16 files changed, 1057 insertions(+), 21 deletions(-) create mode 100644 packages/graph2/.gitignore create mode 100644 packages/graph2/abis/ScrollFighter.json create mode 100644 packages/graph2/docker-compose.yml create mode 100644 packages/graph2/networks.json create mode 100644 packages/graph2/package.json create mode 100644 packages/graph2/schema.graphql create mode 100644 packages/graph2/src/scroll-fighter.ts create mode 100644 packages/graph2/subgraph.yaml create mode 100644 packages/graph2/tests/scroll-fighter-utils.ts create mode 100644 packages/graph2/tests/scroll-fighter.test.ts create mode 100644 packages/graph2/tsconfig.json diff --git a/README.md b/README.md index 15703de..1ada5b5 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ Due to the limited time, I could not complete everything I set out to do. Additi ### Verified smart contracts -[ScrollFighter](https://sepolia.scrollscan.com/address/0x841b974730da5501C9A6d4CA4479d789375e220E#code) -[ScrollFighterV2](https://sepolia.scrollscan.com/address/0xC9728eD87F9fF73da7e27e18FEB560E52aa4bB3F#code) +[ScrollFighter](https://sepolia.scrollscan.com/address/0x0f567534623A5715a8271235db4Ba58461154307#code) +[ScrollFighterV2 (for testing only)](https://sepolia.scrollscan.com/address/0xC9728eD87F9fF73da7e27e18FEB560E52aa4bB3F#code) [FighterCoins](https://sepolia.scrollscan.com/address/0x64CDeB6CD5ecfB002bdaFabc98B5C883C5C06B27#code) [UltraVerifier](https://sepolia.scrollscan.com/address/0x4a2B33A77de8F69b8Cf913aafc6357f4Ce176105#code) diff --git a/packages/graph/schema.graphql b/packages/graph/schema.graphql index 7dee64f..86cb83c 100644 --- a/packages/graph/schema.graphql +++ b/packages/graph/schema.graphql @@ -55,10 +55,10 @@ type GameProposed @entity(immutable: true) { } type Game @entity { - id: Bytes! # uint256 + id: Bytes! + gameId: BigInt! # uint256 wageredAmount: BigInt! # uint256 - player1: Bytes! # address - player2: Bytes! # address + players: [Bytes!]! # address winner: Bytes! # address fighterIds: [BigInt!]! # uint256 moves: [[BigInt!]!]! # uint256 @@ -70,6 +70,6 @@ type Game @entity { type User @entity { id: Bytes! # address - games_started: [Game!]! @derivedFrom(field: "player1") - games_joined: [Game!]! @derivedFrom(field: "player2") + # games_started: [Game!]! @derivedFrom(field: "player1") + # games_joined: [Game!]! @derivedFrom(field: "player2") } diff --git a/packages/graph/src/contract.ts b/packages/graph/src/contract.ts index efe434b..f472331 100644 --- a/packages/graph/src/contract.ts +++ b/packages/graph/src/contract.ts @@ -3,15 +3,15 @@ import { GameAccepted as GameAcceptedEvent, GameEnded as GameEndedEvent, GameEndedByTimeout as GameEndedByTimeoutEvent, - GameProposed as GameProposedEvent, - Game as GameEntity + GameProposed as GameProposedEvent } from "../generated/Contract/Contract" import { FightersRevealed, GameAccepted, GameEnded, GameEndedByTimeout, - GameProposed + GameProposed, + Game as GameEntity } from "../generated/schema" export function handleFightersRevealed(event: FightersRevealedEvent): void { @@ -27,6 +27,17 @@ export function handleFightersRevealed(event: FightersRevealedEvent): void { entity.transactionHash = event.transaction.hash entity.save() + + // TODO: Retrieve game entity and update state + let gameEntity = new GameEntity( + event.transaction.hash.concatI32(event.logIndex.toI32()) + ) + gameEntity.state = "STARTED" + gameEntity.blockNumber = event.block.number + gameEntity.fighterIds = [] + gameEntity.moves = [] + + gameEntity.save() } export function handleGameAccepted(event: GameAcceptedEvent): void { @@ -43,6 +54,15 @@ export function handleGameAccepted(event: GameAcceptedEvent): void { entity.transactionHash = event.transaction.hash entity.save() + + // TODO: Retrieve game entity and update state + let gameEntity = new GameEntity( + event.transaction.hash.concatI32(event.logIndex.toI32()) + ) + gameEntity.state = "ACCEPTED" + gameEntity.blockNumber = event.block.number + + gameEntity.save() } export function handleGameEnded(event: GameEndedEvent): void { @@ -83,10 +103,6 @@ export function handleGameProposed(event: GameProposedEvent): void { let entity = new GameProposed( event.transaction.hash.concatI32(event.logIndex.toI32()) ) - let game = new GameEntity( - event.transaction.hash.concatI32(event.logIndex.toI32()) - ) - // todo: add game entity fields entity.gameId = event.params.gameId entity.player1 = event.params.player1 entity.player2 = event.params.player2 @@ -95,8 +111,17 @@ export function handleGameProposed(event: GameProposedEvent): void { entity.blockNumber = event.block.number entity.blockTimestamp = event.block.timestamp entity.transactionHash = event.transaction.hash - entity.save() - game.save() + + let gameEntity = new GameEntity( + event.transaction.hash.concatI32(event.logIndex.toI32()) + ) + gameEntity.gameId = event.params.gameId + gameEntity.players = [event.params.player1, event.params.player2] + gameEntity.wageredAmount = event.params.wageredAmount + gameEntity.state = "PROPOSED" + gameEntity.blockNumber = event.block.number + + gameEntity.save() } diff --git a/packages/graph2/.gitignore b/packages/graph2/.gitignore new file mode 100644 index 0000000..b542c60 --- /dev/null +++ b/packages/graph2/.gitignore @@ -0,0 +1,34 @@ +# Graph CLI generated artifacts +build/ +generated/ + +# Dependency directories +node_modules/ +jspm_packages/ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# dotenv environment variables file +.env + +# Testing +coverage +coverage.json + +# Typechain +typechain +typechain-types + +# Hardhat files +cache diff --git a/packages/graph2/abis/ScrollFighter.json b/packages/graph2/abis/ScrollFighter.json new file mode 100644 index 0000000..54cb499 --- /dev/null +++ b/packages/graph2/abis/ScrollFighter.json @@ -0,0 +1,344 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_verifier", "type": "address" }, + { "internalType": "address", "name": "_token", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "gameId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "player1", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "player2", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fighter1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fighter2", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[3]", + "name": "moves1", + "type": "uint256[3]" + }, + { + "indexed": false, + "internalType": "uint256[3]", + "name": "moves2", + "type": "uint256[3]" + } + ], + "name": "FightersRevealed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "gameId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "player1", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "player2", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wageredAmount", + "type": "uint256" + } + ], + "name": "GameAccepted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "gameId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "player1", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "player2", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "winner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[3]", + "name": "pain1", + "type": "uint256[3]" + }, + { + "indexed": false, + "internalType": "uint256[3]", + "name": "pain2", + "type": "uint256[3]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wageredAmount", + "type": "uint256" + } + ], + "name": "GameEnded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "gameId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "player1", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "player2", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "winner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wageredAmount", + "type": "uint256" + } + ], + "name": "GameEndedByTimeout", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "gameId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "player1", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "player2", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wageredAmount", + "type": "uint256" + } + ], + "name": "GameProposed", + "type": "event" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_gameId", "type": "uint256" }, + { "internalType": "uint256", "name": "_fighterId", "type": "uint256" }, + { "internalType": "uint256[3]", "name": "moves", "type": "uint256[3]" } + ], + "name": "acceptGame", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "fighters", + "outputs": [ + { "internalType": "uint256", "name": "id", "type": "uint256" }, + { "internalType": "uint256", "name": "health", "type": "uint256" }, + { "internalType": "uint256", "name": "attack", "type": "uint256" }, + { "internalType": "uint256", "name": "defense", "type": "uint256" }, + { "internalType": "uint256", "name": "special", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fightingTokens", + "outputs": [ + { "internalType": "contract IERC20", "name": "", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "gameId", "type": "uint256" } + ], + "name": "finishByTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "gameId", "type": "uint256" } + ], + "name": "getGame", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "id", "type": "uint256" }, + { + "internalType": "uint256", + "name": "wageredAmount", + "type": "uint256" + }, + { + "internalType": "address[2]", + "name": "players", + "type": "address[2]" + }, + { + "internalType": "bytes", + "name": "challengerCommitment", + "type": "bytes" + }, + { + "internalType": "uint256[2]", + "name": "fighterIds", + "type": "uint256[2]" + }, + { + "internalType": "uint256[3][2]", + "name": "moves", + "type": "uint256[3][2]" + }, + { + "internalType": "uint256[3][2]", + "name": "pain", + "type": "uint256[3][2]" + }, + { + "internalType": "enum ScrollFighter.GameState", + "name": "gameState", + "type": "uint8" + }, + { "internalType": "address", "name": "winner", "type": "address" }, + { + "internalType": "uint256", + "name": "lastActionBlock", + "type": "uint256" + }, + { + "internalType": "uint256[2]", + "name": "currentHealth", + "type": "uint256[2]" + } + ], + "internalType": "struct ScrollFighter.Game", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nextGameId", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_opponent", "type": "address" }, + { "internalType": "bytes", "name": "proof", "type": "bytes" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" } + ], + "name": "proposeGame", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "gameId", "type": "uint256" }, + { "internalType": "uint256", "name": "fighterID", "type": "uint256" }, + { "internalType": "uint256[3]", "name": "moves", "type": "uint256[3]" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "bytes", "name": "proof", "type": "bytes" } + ], + "name": "revealFight", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/packages/graph2/docker-compose.yml b/packages/graph2/docker-compose.yml new file mode 100644 index 0000000..a008fc9 --- /dev/null +++ b/packages/graph2/docker-compose.yml @@ -0,0 +1,50 @@ +version: "3" +services: + graph-node: + image: graphprotocol/graph-node + ports: + - "8000:8000" + - "8001:8001" + - "8020:8020" + - "8030:8030" + - "8040:8040" + depends_on: + - ipfs + - postgres + extra_hosts: + - host.docker.internal:host-gateway + environment: + postgres_host: postgres + postgres_user: graph-node + postgres_pass: let-me-in + postgres_db: graph-node + ipfs: "ipfs:5001" + ethereum: "mainnet:http://host.docker.internal:8545" + GRAPH_LOG: info + ipfs: + image: ipfs/kubo:v0.17.0 + ports: + - "5001:5001" + volumes: + - ./data/ipfs:/data/ipfs + postgres: + image: postgres:14 + ports: + - "5432:5432" + command: + [ + "postgres", + "-cshared_preload_libraries=pg_stat_statements", + "-cmax_connections=200", + ] + environment: + POSTGRES_USER: graph-node + POSTGRES_PASSWORD: let-me-in + POSTGRES_DB: graph-node + # FIXME: remove this env. var. which we shouldn't need. Introduced by + # , maybe as a + # workaround for https://github.com/docker/for-mac/issues/6270? + PGDATA: "/var/lib/postgresql/data" + POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C" + volumes: + - ./data/postgres:/var/lib/postgresql/data diff --git a/packages/graph2/networks.json b/packages/graph2/networks.json new file mode 100644 index 0000000..8fdd876 --- /dev/null +++ b/packages/graph2/networks.json @@ -0,0 +1,8 @@ +{ + "scroll-sepolia": { + "ScrollFighter": { + "address": "0x0f567534623A5715a8271235db4Ba58461154307", + "startBlock": 4114939 + } + } +} \ No newline at end of file diff --git a/packages/graph2/package.json b/packages/graph2/package.json new file mode 100644 index 0000000..5431d80 --- /dev/null +++ b/packages/graph2/package.json @@ -0,0 +1,18 @@ +{ + "name": "scrollfighter", + "license": "UNLICENSED", + "scripts": { + "codegen": "graph codegen", + "build": "graph build", + "deploy": "graph deploy --node https://api.studio.thegraph.com/deploy/ scrollfighter", + "create-local": "graph create --node http://localhost:8020/ scrollfighter", + "remove-local": "graph remove --node http://localhost:8020/ scrollfighter", + "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 scrollfighter", + "test": "graph test" + }, + "dependencies": { + "@graphprotocol/graph-cli": "0.71.0", + "@graphprotocol/graph-ts": "0.32.0" + }, + "devDependencies": { "matchstick-as": "0.5.0" } +} diff --git a/packages/graph2/schema.graphql b/packages/graph2/schema.graphql new file mode 100644 index 0000000..d6666d5 --- /dev/null +++ b/packages/graph2/schema.graphql @@ -0,0 +1,61 @@ +type FightersRevealed @entity(immutable: true) { + id: Bytes! + gameId: BigInt! # uint256 + player1: Bytes! # address + player2: Bytes! # address + fighter1: BigInt! # uint256 + fighter2: BigInt! # uint256 + moves1: [BigInt!]! # uint256[3] + moves2: [BigInt!]! # uint256[3] + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +type GameAccepted @entity(immutable: true) { + id: Bytes! + gameId: BigInt! # uint256 + player1: Bytes! # address + player2: Bytes! # address + wageredAmount: BigInt! # uint256 + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +type GameEnded @entity(immutable: true) { + id: Bytes! + gameId: BigInt! # uint256 + player1: Bytes! # address + player2: Bytes! # address + winner: Bytes! # address + pain1: [BigInt!]! # uint256[3] + pain2: [BigInt!]! # uint256[3] + wageredAmount: BigInt! # uint256 + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +type GameEndedByTimeout @entity(immutable: true) { + id: Bytes! + gameId: BigInt! # uint256 + player1: Bytes! # address + player2: Bytes! # address + winner: Bytes! # address + wageredAmount: BigInt! # uint256 + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +type GameProposed @entity(immutable: true) { + id: Bytes! + gameId: BigInt! # uint256 + player1: Bytes! # address + player2: Bytes! # address + wageredAmount: BigInt! # uint256 + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} diff --git a/packages/graph2/src/scroll-fighter.ts b/packages/graph2/src/scroll-fighter.ts new file mode 100644 index 0000000..6056fdc --- /dev/null +++ b/packages/graph2/src/scroll-fighter.ts @@ -0,0 +1,101 @@ +import { + FightersRevealed as FightersRevealedEvent, + GameAccepted as GameAcceptedEvent, + GameEnded as GameEndedEvent, + GameEndedByTimeout as GameEndedByTimeoutEvent, + GameProposed as GameProposedEvent +} from "../generated/ScrollFighter/ScrollFighter" +import { + FightersRevealed, + GameAccepted, + GameEnded, + GameEndedByTimeout, + GameProposed +} from "../generated/schema" + +export function handleFightersRevealed(event: FightersRevealedEvent): void { + let entity = new FightersRevealed( + event.transaction.hash.concatI32(event.logIndex.toI32()) + ) + entity.gameId = event.params.gameId + entity.player1 = event.params.player1 + entity.player2 = event.params.player2 + entity.fighter1 = event.params.fighter1 + entity.fighter2 = event.params.fighter2 + entity.moves1 = event.params.moves1 + entity.moves2 = event.params.moves2 + + entity.blockNumber = event.block.number + entity.blockTimestamp = event.block.timestamp + entity.transactionHash = event.transaction.hash + + entity.save() +} + +export function handleGameAccepted(event: GameAcceptedEvent): void { + let entity = new GameAccepted( + event.transaction.hash.concatI32(event.logIndex.toI32()) + ) + entity.gameId = event.params.gameId + entity.player1 = event.params.player1 + entity.player2 = event.params.player2 + entity.wageredAmount = event.params.wageredAmount + + entity.blockNumber = event.block.number + entity.blockTimestamp = event.block.timestamp + entity.transactionHash = event.transaction.hash + + entity.save() +} + +export function handleGameEnded(event: GameEndedEvent): void { + let entity = new GameEnded( + event.transaction.hash.concatI32(event.logIndex.toI32()) + ) + entity.gameId = event.params.gameId + entity.player1 = event.params.player1 + entity.player2 = event.params.player2 + entity.winner = event.params.winner + entity.pain1 = event.params.pain1 + entity.pain2 = event.params.pain2 + entity.wageredAmount = event.params.wageredAmount + + entity.blockNumber = event.block.number + entity.blockTimestamp = event.block.timestamp + entity.transactionHash = event.transaction.hash + + entity.save() +} + +export function handleGameEndedByTimeout(event: GameEndedByTimeoutEvent): void { + let entity = new GameEndedByTimeout( + event.transaction.hash.concatI32(event.logIndex.toI32()) + ) + entity.gameId = event.params.gameId + entity.player1 = event.params.player1 + entity.player2 = event.params.player2 + entity.winner = event.params.winner + entity.wageredAmount = event.params.wageredAmount + + entity.blockNumber = event.block.number + entity.blockTimestamp = event.block.timestamp + entity.transactionHash = event.transaction.hash + + entity.save() +} + +export function handleGameProposed(event: GameProposedEvent): void { + let entity = new GameProposed( + event.transaction.hash.concatI32(event.logIndex.toI32()) + ) + entity.gameId = event.params.gameId + entity.player1 = event.params.player1 + entity.player2 = event.params.player2 + entity.wageredAmount = event.params.wageredAmount + + entity.blockNumber = event.block.number + entity.blockTimestamp = event.block.timestamp + entity.transactionHash = event.transaction.hash + + entity.save() +} diff --git a/packages/graph2/subgraph.yaml b/packages/graph2/subgraph.yaml new file mode 100644 index 0000000..9ac1aed --- /dev/null +++ b/packages/graph2/subgraph.yaml @@ -0,0 +1,38 @@ +specVersion: 1.0.0 +indexerHints: + prune: auto +schema: + file: ./schema.graphql +dataSources: + - kind: ethereum + name: ScrollFighter + network: scroll-sepolia + source: + address: "0x0f567534623A5715a8271235db4Ba58461154307" + abi: ScrollFighter + startBlock: 4114939 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - FightersRevealed + - GameAccepted + - GameEnded + - GameEndedByTimeout + - GameProposed + abis: + - name: ScrollFighter + file: ./abis/ScrollFighter.json + eventHandlers: + - event: FightersRevealed(uint256,address,address,uint256,uint256,uint256[3],uint256[3]) + handler: handleFightersRevealed + - event: GameAccepted(uint256,address,address,uint256) + handler: handleGameAccepted + - event: GameEnded(uint256,address,address,address,uint256[3],uint256[3],uint256) + handler: handleGameEnded + - event: GameEndedByTimeout(uint256,address,address,address,uint256) + handler: handleGameEndedByTimeout + - event: GameProposed(uint256,address,address,uint256) + handler: handleGameProposed + file: ./src/scroll-fighter.ts diff --git a/packages/graph2/tests/scroll-fighter-utils.ts b/packages/graph2/tests/scroll-fighter-utils.ts new file mode 100644 index 0000000..0e3f442 --- /dev/null +++ b/packages/graph2/tests/scroll-fighter-utils.ts @@ -0,0 +1,197 @@ +import { newMockEvent } from "matchstick-as" +import { ethereum, BigInt, Address } from "@graphprotocol/graph-ts" +import { + FightersRevealed, + GameAccepted, + GameEnded, + GameEndedByTimeout, + GameProposed +} from "../generated/ScrollFighter/ScrollFighter" + +export function createFightersRevealedEvent( + gameId: BigInt, + player1: Address, + player2: Address, + fighter1: BigInt, + fighter2: BigInt, + moves1: Array, + moves2: Array +): FightersRevealed { + let fightersRevealedEvent = changetype(newMockEvent()) + + fightersRevealedEvent.parameters = new Array() + + fightersRevealedEvent.parameters.push( + new ethereum.EventParam("gameId", ethereum.Value.fromUnsignedBigInt(gameId)) + ) + fightersRevealedEvent.parameters.push( + new ethereum.EventParam("player1", ethereum.Value.fromAddress(player1)) + ) + fightersRevealedEvent.parameters.push( + new ethereum.EventParam("player2", ethereum.Value.fromAddress(player2)) + ) + fightersRevealedEvent.parameters.push( + new ethereum.EventParam( + "fighter1", + ethereum.Value.fromUnsignedBigInt(fighter1) + ) + ) + fightersRevealedEvent.parameters.push( + new ethereum.EventParam( + "fighter2", + ethereum.Value.fromUnsignedBigInt(fighter2) + ) + ) + fightersRevealedEvent.parameters.push( + new ethereum.EventParam( + "moves1", + ethereum.Value.fromUnsignedBigIntArray(moves1) + ) + ) + fightersRevealedEvent.parameters.push( + new ethereum.EventParam( + "moves2", + ethereum.Value.fromUnsignedBigIntArray(moves2) + ) + ) + + return fightersRevealedEvent +} + +export function createGameAcceptedEvent( + gameId: BigInt, + player1: Address, + player2: Address, + wageredAmount: BigInt +): GameAccepted { + let gameAcceptedEvent = changetype(newMockEvent()) + + gameAcceptedEvent.parameters = new Array() + + gameAcceptedEvent.parameters.push( + new ethereum.EventParam("gameId", ethereum.Value.fromUnsignedBigInt(gameId)) + ) + gameAcceptedEvent.parameters.push( + new ethereum.EventParam("player1", ethereum.Value.fromAddress(player1)) + ) + gameAcceptedEvent.parameters.push( + new ethereum.EventParam("player2", ethereum.Value.fromAddress(player2)) + ) + gameAcceptedEvent.parameters.push( + new ethereum.EventParam( + "wageredAmount", + ethereum.Value.fromUnsignedBigInt(wageredAmount) + ) + ) + + return gameAcceptedEvent +} + +export function createGameEndedEvent( + gameId: BigInt, + player1: Address, + player2: Address, + winner: Address, + pain1: Array, + pain2: Array, + wageredAmount: BigInt +): GameEnded { + let gameEndedEvent = changetype(newMockEvent()) + + gameEndedEvent.parameters = new Array() + + gameEndedEvent.parameters.push( + new ethereum.EventParam("gameId", ethereum.Value.fromUnsignedBigInt(gameId)) + ) + gameEndedEvent.parameters.push( + new ethereum.EventParam("player1", ethereum.Value.fromAddress(player1)) + ) + gameEndedEvent.parameters.push( + new ethereum.EventParam("player2", ethereum.Value.fromAddress(player2)) + ) + gameEndedEvent.parameters.push( + new ethereum.EventParam("winner", ethereum.Value.fromAddress(winner)) + ) + gameEndedEvent.parameters.push( + new ethereum.EventParam( + "pain1", + ethereum.Value.fromUnsignedBigIntArray(pain1) + ) + ) + gameEndedEvent.parameters.push( + new ethereum.EventParam( + "pain2", + ethereum.Value.fromUnsignedBigIntArray(pain2) + ) + ) + gameEndedEvent.parameters.push( + new ethereum.EventParam( + "wageredAmount", + ethereum.Value.fromUnsignedBigInt(wageredAmount) + ) + ) + + return gameEndedEvent +} + +export function createGameEndedByTimeoutEvent( + gameId: BigInt, + player1: Address, + player2: Address, + winner: Address, + wageredAmount: BigInt +): GameEndedByTimeout { + let gameEndedByTimeoutEvent = changetype(newMockEvent()) + + gameEndedByTimeoutEvent.parameters = new Array() + + gameEndedByTimeoutEvent.parameters.push( + new ethereum.EventParam("gameId", ethereum.Value.fromUnsignedBigInt(gameId)) + ) + gameEndedByTimeoutEvent.parameters.push( + new ethereum.EventParam("player1", ethereum.Value.fromAddress(player1)) + ) + gameEndedByTimeoutEvent.parameters.push( + new ethereum.EventParam("player2", ethereum.Value.fromAddress(player2)) + ) + gameEndedByTimeoutEvent.parameters.push( + new ethereum.EventParam("winner", ethereum.Value.fromAddress(winner)) + ) + gameEndedByTimeoutEvent.parameters.push( + new ethereum.EventParam( + "wageredAmount", + ethereum.Value.fromUnsignedBigInt(wageredAmount) + ) + ) + + return gameEndedByTimeoutEvent +} + +export function createGameProposedEvent( + gameId: BigInt, + player1: Address, + player2: Address, + wageredAmount: BigInt +): GameProposed { + let gameProposedEvent = changetype(newMockEvent()) + + gameProposedEvent.parameters = new Array() + + gameProposedEvent.parameters.push( + new ethereum.EventParam("gameId", ethereum.Value.fromUnsignedBigInt(gameId)) + ) + gameProposedEvent.parameters.push( + new ethereum.EventParam("player1", ethereum.Value.fromAddress(player1)) + ) + gameProposedEvent.parameters.push( + new ethereum.EventParam("player2", ethereum.Value.fromAddress(player2)) + ) + gameProposedEvent.parameters.push( + new ethereum.EventParam( + "wageredAmount", + ethereum.Value.fromUnsignedBigInt(wageredAmount) + ) + ) + + return gameProposedEvent +} diff --git a/packages/graph2/tests/scroll-fighter.test.ts b/packages/graph2/tests/scroll-fighter.test.ts new file mode 100644 index 0000000..8fcd1fb --- /dev/null +++ b/packages/graph2/tests/scroll-fighter.test.ts @@ -0,0 +1,100 @@ +import { + assert, + describe, + test, + clearStore, + beforeAll, + afterAll +} from "matchstick-as/assembly/index" +import { BigInt, Address } from "@graphprotocol/graph-ts" +import { FightersRevealed } from "../generated/schema" +import { FightersRevealed as FightersRevealedEvent } from "../generated/ScrollFighter/ScrollFighter" +import { handleFightersRevealed } from "../src/scroll-fighter" +import { createFightersRevealedEvent } from "./scroll-fighter-utils" + +// Tests structure (matchstick-as >=0.5.0) +// https://thegraph.com/docs/en/developer/matchstick/#tests-structure-0-5-0 + +describe("Describe entity assertions", () => { + beforeAll(() => { + let gameId = BigInt.fromI32(234) + let player1 = Address.fromString( + "0x0000000000000000000000000000000000000001" + ) + let player2 = Address.fromString( + "0x0000000000000000000000000000000000000001" + ) + let fighter1 = BigInt.fromI32(234) + let fighter2 = BigInt.fromI32(234) + let moves1 = [BigInt.fromI32(234)] + let moves2 = [BigInt.fromI32(234)] + let newFightersRevealedEvent = createFightersRevealedEvent( + gameId, + player1, + player2, + fighter1, + fighter2, + moves1, + moves2 + ) + handleFightersRevealed(newFightersRevealedEvent) + }) + + afterAll(() => { + clearStore() + }) + + // For more test scenarios, see: + // https://thegraph.com/docs/en/developer/matchstick/#write-a-unit-test + + test("FightersRevealed created and stored", () => { + assert.entityCount("FightersRevealed", 1) + + // 0xa16081f360e3847006db660bae1c6d1b2e17ec2a is the default address used in newMockEvent() function + assert.fieldEquals( + "FightersRevealed", + "0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1", + "gameId", + "234" + ) + assert.fieldEquals( + "FightersRevealed", + "0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1", + "player1", + "0x0000000000000000000000000000000000000001" + ) + assert.fieldEquals( + "FightersRevealed", + "0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1", + "player2", + "0x0000000000000000000000000000000000000001" + ) + assert.fieldEquals( + "FightersRevealed", + "0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1", + "fighter1", + "234" + ) + assert.fieldEquals( + "FightersRevealed", + "0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1", + "fighter2", + "234" + ) + assert.fieldEquals( + "FightersRevealed", + "0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1", + "moves1", + "[234]" + ) + assert.fieldEquals( + "FightersRevealed", + "0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1", + "moves2", + "[234]" + ) + + // More assert options: + // https://thegraph.com/docs/en/developer/matchstick/#asserts + }) +}) diff --git a/packages/graph2/tsconfig.json b/packages/graph2/tsconfig.json new file mode 100644 index 0000000..4e86672 --- /dev/null +++ b/packages/graph2/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@graphprotocol/graph-ts/types/tsconfig.base.json", + "include": ["src", "tests"] +} diff --git a/packages/hardhat/contracts/ScrollFighter.sol b/packages/hardhat/contracts/ScrollFighter.sol index ec5a085..03c8ac0 100644 --- a/packages/hardhat/contracts/ScrollFighter.sol +++ b/packages/hardhat/contracts/ScrollFighter.sol @@ -23,13 +23,23 @@ contract ScrollFighter { address player2, uint wageredAmount ); - event FightersRevealed(uint gameId, address player1, address player2); + event FightersRevealed( + uint gameId, + address player1, + address player2, + uint fighter1, + uint fighter2, + uint[3] moves1, + uint[3] moves2 + ); event GameEnded( uint gameId, address player1, address player2, address winner, + uint[3] pain1, + uint[3] pain2, uint wageredAmount ); event GameEndedByTimeout( @@ -209,7 +219,15 @@ contract ScrollFighter { game.fighterIds[0] = fighterID; game.moves[0] = moves; game.gameState = GameState.STARTED; - emit FightersRevealed(gameId, game.players[0], game.players[1]); + emit FightersRevealed( + gameId, + game.players[0], + game.players[1], + fighterID, + game.fighterIds[1], + moves, + game.moves[1] + ); playGame(gameId); } @@ -263,6 +281,8 @@ contract ScrollFighter { game.winner, game.players[0], game.players[1], + game.pain[0], + game.pain[1], game.wageredAmount ); } diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts index 2865d74..d57b52a 100644 --- a/packages/nextjs/contracts/deployedContracts.ts +++ b/packages/nextjs/contracts/deployedContracts.ts @@ -517,7 +517,7 @@ const deployedContracts = { }, }, ScrollFighter: { - address: "0x58F83E4F3F693eBc4C4cf6bDae01b9725638Ea63", + address: "0x0f567534623A5715a8271235db4Ba58461154307", abi: [ { inputs: [ @@ -556,6 +556,30 @@ const deployedContracts = { name: "player2", type: "address", }, + { + indexed: false, + internalType: "uint256", + name: "fighter1", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "fighter2", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256[3]", + name: "moves1", + type: "uint256[3]", + }, + { + indexed: false, + internalType: "uint256[3]", + name: "moves2", + type: "uint256[3]", + }, ], name: "FightersRevealed", type: "event", @@ -618,6 +642,18 @@ const deployedContracts = { name: "winner", type: "address", }, + { + indexed: false, + internalType: "uint256[3]", + name: "pain1", + type: "uint256[3]", + }, + { + indexed: false, + internalType: "uint256[3]", + name: "pain2", + type: "uint256[3]", + }, { indexed: false, internalType: "uint256", @@ -933,7 +969,7 @@ const deployedContracts = { inheritedFunctions: {}, }, ScrollFighterV2: { - address: "0x4250f996882e56b0d0a2daddf3ea0bf9904fec5e", + address: "0xC9728eD87F9fF73da7e27e18FEB560E52aa4bB3F", abi: [ { inputs: [ @@ -1321,7 +1357,7 @@ const deployedContracts = { inheritedFunctions: {}, }, UltraVerifier: { - address: "0xc15BC025d57bec9FA39e18701b4f0b3b5a067B6C", + address: "0x4a2B33A77de8F69b8Cf913aafc6357f4Ce176105", abi: [ { inputs: [],