Skip to content

Commit 77ea7d2

Browse files
committed
feat(visualization): Add schema, indexing-functions for top/middle/bottom tournaments.
1 parent 71dba0b commit 77ea7d2

13 files changed

+611
-49
lines changed

visualization/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
44
# Visualization [WIP]
55

6+
> [!IMPORTANT]
7+
> Make sure to be docker logged with a valid GHCR credential [reference here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-with-a-personal-access-token-classic) to avoid failure when trying to run the docker-compose.
8+
69
To be able to run the visualization assuming you are under `/visualization` we have a [justfile](./justfile) to simplify the process to get it up-and-running.
710

811
You can execute the following

visualization/codegen.just

+9-9
Original file line numberDiff line numberDiff line change
@@ -25,39 +25,39 @@ tmpDir := join(cwd, 'tmp')
2525
cd ../{{rollups_contracts_path}} && \
2626
forge inspect \
2727
--json {{input_box_contract}} abi \
28-
| sed '1s/^/export const InputBox = /; $s/$/ as const/' > {{indexer_abis_folder}}/InputBox.ts
28+
| sed '1s/^/export const InputBoxAbi = /; $s/$/ as const/' > {{indexer_abis_folder}}/InputBox.ts
2929
echo "{{ GREEN }} InputBox.sol done!"
3030
cd ../{{rollups_contracts_path}} && \
3131
forge inspect \
3232
--json {{dave_consensus_contract}} abi \
33-
| sed '1s/^/export const DaveConsensus = /; $s/$/ as const/' > {{indexer_abis_folder}}/DaveConsensus.ts
33+
| sed '1s/^/export const DaveConsensusAbi = /; $s/$/ as const/' > {{indexer_abis_folder}}/DaveConsensus.ts
3434
echo "{{ GREEN }} DaveConsensus.sol done!"
3535

3636
[private]
3737
@gen-prt-factory-abis:
3838
cd ../{{prt_contracts_path}} && forge inspect --json {{multi_level_factory_contract}} abi \
39-
| sed '1s/^/export const MultiLevelTournamentFactory = /; $s/$/ as const/' > {{indexer_abis_folder}}/MultiLevelTournamentFactory.ts
39+
| sed '1s/^/export const MultiLevelTournamentFactoryAbi = /; $s/$/ as const/' > {{indexer_abis_folder}}/MultiLevelTournamentFactory.ts
4040
echo "{{ GREEN }} MultiLevelTournamentFactory.sol done!"
4141
cd ../{{prt_contracts_path}} && forge inspect --json {{top_tournament_factory_contract}} abi \
42-
| sed '1s/^/export const TopTournamentFactory = /; $s/$/ as const/' > {{indexer_abis_folder}}/TopTournamentFactory.ts
42+
| sed '1s/^/export const TopTournamentFactoryAbi = /; $s/$/ as const/' > {{indexer_abis_folder}}/TopTournamentFactory.ts
4343
echo "{{ GREEN }} TopTournamentFactory.sol done!"
4444
cd ../{{prt_contracts_path}} && forge inspect --json {{middle_tournament_factory_contract}} abi \
45-
| sed '1s/^/export const MiddleTournamentFactory = /; $s/$/ as const/' > {{indexer_abis_folder}}/MiddleTournamentFactory.ts
45+
| sed '1s/^/export const MiddleTournamentFactoryAbi = /; $s/$/ as const/' > {{indexer_abis_folder}}/MiddleTournamentFactory.ts
4646
echo "{{ GREEN }} MiddleTournamentFactory.sol done!"
4747
cd ../{{prt_contracts_path}} && forge inspect --json {{bottom_tournament_factory_contract}} abi \
48-
| sed '1s/^/export const BottomTournamentFactory = /; $s/$/ as const/' > {{indexer_abis_folder}}/BottomTournamentFactory.ts
48+
| sed '1s/^/export const BottomTournamentFactoryAbi = /; $s/$/ as const/' > {{indexer_abis_folder}}/BottomTournamentFactory.ts
4949
echo "{{ GREEN }} BottomTournamentFactory.sol done!"
5050

5151
[private]
5252
@gen-prt-concrete-abis:
5353
cd ../{{prt_contracts_path}} && forge inspect --json {{top_tournament_contract}} abi \
54-
| sed '1s/^/export const TopTournament = /; $s/$/ as const/' > {{indexer_abis_folder}}/TopTournament.ts
54+
| sed '1s/^/export const TopTournamentAbi = /; $s/$/ as const/' > {{indexer_abis_folder}}/TopTournament.ts
5555
echo "{{ GREEN }} TopTournament.sol done!"
5656
cd ../{{prt_contracts_path}} && forge inspect --json {{middle_tournament_contract}} abi \
57-
| sed '1s/^/export const MiddleTournament = /; $s/$/ as const/' > {{indexer_abis_folder}}/MiddleTournament.ts
57+
| sed '1s/^/export const MiddleTournamentAbi = /; $s/$/ as const/' > {{indexer_abis_folder}}/MiddleTournament.ts
5858
echo "{{ GREEN }} MiddleTournament.sol done!"
5959
cd ../{{prt_contracts_path}} && forge inspect --json {{bottom_tournament_contract}} abi \
60-
| sed '1s/^/export const BottomTournament = /; $s/$/ as const/' > {{indexer_abis_folder}}/BottomTournament.ts
60+
| sed '1s/^/export const BottomTournamentAbi = /; $s/$/ as const/' > {{indexer_abis_folder}}/BottomTournament.ts
6161
echo "{{ GREEN }} BottomTournament.sol done!"
6262

6363
[private]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// TODO: Make that auto-generated in-case any contract change it will change the address.
2+
const addressBook = {
3+
31337: {
4+
topTournamentFactory: '0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0',
5+
middleTournamentFactory: '0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9',
6+
bottomTournamentFactory: '0xdc64a140aa3e981100a9beca4e685f962f0cf6c9',
7+
multiLevelTournamentFactory:
8+
'0x8a791620dd6260079bf849dc5567adc3f2fdc318',
9+
inputBox: '0x5fbdb2315678afecb367f032d93f642f64180aa3',
10+
},
11+
} as const
12+
13+
export default addressBook

visualization/indexer/ponder-env.d.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/// <reference types="ponder/virtual" />
22

3-
declare module 'ponder:internal' {
4-
const config: typeof import('./ponder.config.ts')
5-
const schema: typeof import('./ponder.schema.ts')
3+
declare module "ponder:internal" {
4+
const config: typeof import("./ponder.config.ts");
5+
const schema: typeof import("./ponder.schema.ts");
66
}
77

8-
declare module 'ponder:schema' {
9-
export * from './ponder.schema.ts'
8+
declare module "ponder:schema" {
9+
export * from "./ponder.schema.ts";
1010
}
1111

1212
// This file enables type checking and editor autocomplete for this Ponder project.
+58-12
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,67 @@
1-
import { createConfig } from 'ponder'
2-
import { http } from 'viem'
1+
import { createConfig, factory } from 'ponder';
2+
import { http, parseAbiItem } from 'viem';
33

4-
import { ExampleContractAbi } from './abis/ExampleContractAbi'
4+
import { BottomTournamentAbi } from './abis/BottomTournament';
5+
import { MiddleTournamentAbi } from './abis/MiddleTournament';
6+
import { MultiLevelTournamentFactoryAbi } from './abis/MultiLevelTournamentFactory';
7+
import { TopTournamentAbi } from './abis/TopTournament';
8+
import addressBook from './address-book';
59

610
export default createConfig({
711
networks: {
8-
mainnet: {
9-
chainId: 1,
10-
transport: http(process.env.PONDER_RPC_URL_1),
12+
anvil: {
13+
chainId: 31337,
14+
transport: http(process.env.PONDER_RPC_URL_31337),
1115
},
1216
},
1317
contracts: {
14-
ExampleContract: {
15-
network: 'mainnet',
16-
abi: ExampleContractAbi,
17-
address: '0x0000000000000000000000000000000000000000',
18-
startBlock: 1234567,
18+
MultiLevelTournamentFactory: {
19+
network: 'anvil',
20+
abi: MultiLevelTournamentFactoryAbi,
21+
address: addressBook[31337].multiLevelTournamentFactory,
22+
startBlock: 0,
23+
},
24+
TopTournament: {
25+
network: 'anvil',
26+
abi: TopTournamentAbi,
27+
address: factory({
28+
address: addressBook[31337].multiLevelTournamentFactory,
29+
event: parseAbiItem(
30+
'event tournamentCreated(address tournamentAddress)',
31+
),
32+
parameter: 'tournamentAddress',
33+
}),
34+
startBlock: 0,
35+
},
36+
MiddleTournament: {
37+
network: 'anvil',
38+
abi: MiddleTournamentAbi,
39+
filter: [
40+
{
41+
event: 'newInnerTournament',
42+
args: [],
43+
},
44+
{
45+
event: 'matchCreated',
46+
args: {},
47+
},
48+
],
49+
startBlock: 0,
50+
},
51+
BottomTournament: {
52+
network: 'anvil',
53+
abi: BottomTournamentAbi,
54+
filter: [
55+
{
56+
event: 'matchCreated',
57+
args: {},
58+
},
59+
{
60+
event: 'matchDeleted',
61+
args: {},
62+
},
63+
],
64+
startBlock: 0,
1965
},
2066
},
21-
})
67+
});
+90-18
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,90 @@
1-
import { onchainTable } from 'ponder'
2-
3-
const contracts = [
4-
'TopTournamentFactory',
5-
'MiddleTournamentFactory',
6-
'BottomTournamentFactory',
7-
'MultiLevelTournamentFactory',
8-
'InputBox', //not sure about that one!
9-
'DaveConsensus',
10-
'TopTournament',
11-
'MiddleTournament',
12-
'BottomTournament',
13-
]
14-
15-
export const example = onchainTable('example', (t) => ({
16-
id: t.text().primaryKey(),
17-
name: t.text(),
18-
}))
1+
import { onchainEnum, onchainTable, relations } from 'ponder';
2+
3+
export const matchStatus = onchainEnum('match_status', ['STARTED', 'FINISHED']);
4+
export const commitmentStatus = onchainEnum('commitment_status', [
5+
'WAITING',
6+
'PLAYING',
7+
'WON',
8+
'LOST',
9+
]);
10+
11+
export const tournament = onchainTable('tournament', (t) => ({
12+
id: t.hex().primaryKey(),
13+
timestamp: t.bigint().notNull(),
14+
level: t.bigint().default(0n),
15+
parentId: t.hex(),
16+
matchId: t.hex(),
17+
}));
18+
19+
//matchCreated(p1_keccak256(lNode + rNode), p2_keccak256(lnode + rnode), p2LeftNodeHash)
20+
export const match = onchainTable('match', (t) => ({
21+
id: t.hex().primaryKey(),
22+
commitmentOne: t.hex().notNull(),
23+
commitmentTwo: t.hex().notNull(),
24+
leftOfTwo: t.hex().notNull(),
25+
status: matchStatus('match_status').default('STARTED'),
26+
tournamentId: t.hex().notNull(),
27+
timestamp: t.bigint().notNull(),
28+
}));
29+
30+
// represent matchAdvanced(matchId_hash, parent_node_hash, left_node_hash)
31+
export const step = onchainTable('step', (t) => ({
32+
id: t.hex().primaryKey(),
33+
advancedBy: t.hex().notNull(),
34+
parentNodeHash: t.hex().notNull(),
35+
leftNodeHash: t.hex().notNull(),
36+
matchId: t.hex().notNull(),
37+
timestamp: t.bigint().notNull(),
38+
input: t.jsonb().notNull(),
39+
}));
40+
41+
// commitmentJoined(rootHash(keccak256(lNode, rNode)))
42+
// holds information about the sender, transaction and event-emitted
43+
// Adding the machine-hash when for matching arbitration result after a game is finished
44+
export const commitment = onchainTable('commitment', (t) => ({
45+
id: t.hex().primaryKey(),
46+
commitmentHash: t.hex().notNull(),
47+
status: commitmentStatus('commitment_status').default('WAITING'),
48+
timestamp: t.bigint().notNull(),
49+
tournamentId: t.hex().notNull(),
50+
matchId: t.hex(),
51+
// tx information
52+
playerAddress: t.hex().notNull(),
53+
machineHash: t.hex().notNull(),
54+
proof: t.jsonb().notNull(),
55+
lNode: t.hex().notNull(),
56+
rNode: t.hex().notNull(),
57+
}));
58+
59+
export const tournamentRelations = relations(tournament, ({ many, one }) => ({
60+
parent: one(tournament, {
61+
fields: [tournament.parentId],
62+
references: [tournament.id],
63+
}),
64+
innerTournaments: many(tournament),
65+
matches: many(match),
66+
commitments: many(commitment),
67+
}));
68+
69+
export const commitmentRelations = relations(commitment, ({ one }) => ({
70+
tournament: one(tournament, {
71+
fields: [commitment.tournamentId],
72+
references: [tournament.id],
73+
}),
74+
match: one(match, {
75+
fields: [commitment.matchId],
76+
references: [match.id],
77+
}),
78+
}));
79+
80+
export const matchesRelations = relations(match, ({ one, many }) => ({
81+
tournament: one(tournament, {
82+
fields: [match.tournamentId],
83+
references: [tournament.id],
84+
}),
85+
steps: many(step),
86+
}));
87+
88+
export const stepsRelations = relations(step, ({ one }) => ({
89+
match: one(match, { fields: [step.matchId], references: [match.id] }),
90+
}));

visualization/indexer/prettier.config.mjs

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
*/
77
const config = {
88
tabWidth: 4,
9-
semi: false,
109
singleQuote: true,
1110
bracketSpacing: true,
1211
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { and, eq } from 'ponder';
2+
import { ponder } from 'ponder:registry';
3+
import { match } from 'ponder:schema';
4+
import { generateMatchID, shouldSkipTournamentEvent } from './utils';
5+
6+
const TOURNAMENT_LEVEL = 2n as const;
7+
8+
ponder.on('BottomTournament:matchCreated', async ({ event, context }) => {
9+
const { address: tournamentAddress } = event.log;
10+
const skip = await shouldSkipTournamentEvent(
11+
tournamentAddress,
12+
context,
13+
TOURNAMENT_LEVEL,
14+
);
15+
16+
if (skip) return;
17+
18+
const { leftOfTwo, one, two } = event.args;
19+
20+
const matchId = generateMatchID(one, two);
21+
const { timestamp } = event.block;
22+
23+
await context.db.insert(match).values({
24+
id: matchId,
25+
commitmentOne: one,
26+
commitmentTwo: two,
27+
leftOfTwo,
28+
timestamp,
29+
tournamentId: tournamentAddress,
30+
});
31+
32+
console.log(
33+
`->->(BottomTournament:matchCreated) \n\t\tone:${event.args.one} \n\t\ttwo: ${event.args.two} \n\t\tlNodeTwo:${event.args.leftOfTwo}`,
34+
);
35+
});
36+
37+
ponder.on('BottomTournament:matchDeleted', async ({ event, context }) => {
38+
const { address: tournamentAddress } = event.log;
39+
const skip = await shouldSkipTournamentEvent(
40+
tournamentAddress,
41+
context,
42+
TOURNAMENT_LEVEL,
43+
);
44+
45+
if (skip) return;
46+
47+
const [matchIdHash] = event.args;
48+
49+
const result = await context.db.sql
50+
.update(match)
51+
.set({ status: 'FINISHED' })
52+
.where(
53+
and(
54+
eq(match.id, matchIdHash),
55+
eq(match.tournamentId, tournamentAddress),
56+
),
57+
);
58+
59+
console.log(`-> Update result: (${result})`);
60+
console.log(
61+
`->->(BottomTournament:matchDeleted) \n\t\tmatchId: ${matchIdHash}`,
62+
);
63+
});

visualization/indexer/src/index.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,22 @@
1-
import { ponder } from 'ponder:registry'
1+
import { ponder } from 'ponder:registry';
2+
import { tournament } from 'ponder:schema';
3+
4+
ponder.on(
5+
'MultiLevelTournamentFactory:tournamentCreated',
6+
async ({ event, context }) => {
7+
const [topTournamentAddress] = event.args;
8+
9+
await context.db.insert(tournament).values({
10+
id: topTournamentAddress,
11+
level: 0n,
12+
timestamp: event.block.timestamp,
13+
});
14+
15+
console.log(`->->(Tournament Created)`);
16+
console.log(`\t\ttournamentAddress: ${topTournamentAddress}`);
17+
console.log(`\t\tblocknumber: ${event.block.number}`);
18+
console.log(`\t\tblock-timestamp: ${event.block.timestamp}`);
19+
console.log(`\t\tfrom: ${event.transaction.from}`);
20+
console.log(`\t\tto: ${event.transaction.to}`);
21+
},
22+
);

0 commit comments

Comments
 (0)