From e04400cfd3cbd43457a24af8096d5de06d27c3d6 Mon Sep 17 00:00:00 2001 From: Bogdan Crisan Date: Tue, 18 Feb 2025 16:34:19 +0100 Subject: [PATCH] test(ECO-2862): Add missing test cases for arena processing --- .../sdk/tests/e2e/arena/general/arena.test.ts | 304 +++++++++++++++--- 1 file changed, 268 insertions(+), 36 deletions(-) diff --git a/src/typescript/sdk/tests/e2e/arena/general/arena.test.ts b/src/typescript/sdk/tests/e2e/arena/general/arena.test.ts index 2bf0dc769..79c9a1bcc 100644 --- a/src/typescript/sdk/tests/e2e/arena/general/arena.test.ts +++ b/src/typescript/sdk/tests/e2e/arena/general/arena.test.ts @@ -1,10 +1,11 @@ -import { type Account, type UserTransactionResponse } from "@aptos-labs/ts-sdk"; +import type { Ed25519Account, Account, UserTransactionResponse } from "@aptos-labs/ts-sdk"; import { type AnyNumberString, ARENA_MODULE_ADDRESS, EmojicoinArena, getAptosClient, ONE_APT_BIGINT, + sleep, type SymbolEmoji, } from "../../../../src"; import { EmojicoinClient } from "../../../../src/client/emojicoin-client"; @@ -15,6 +16,7 @@ import { type ArenaPositionModel, type ArenaSwapModel, fetchArenaInfo, + fetchSwapEvents, postgrest, stringifyJSONWithBigInts, TableName, @@ -33,12 +35,34 @@ import { import { getFundedAccount } from "../../../utils/test-accounts"; import { ONE_SECOND_MICROSECONDS, - registerAndUnlockInitialMarketsForArenaTest, setNextMeleeDurationAndEnsureCrank, waitUntilCurrentMeleeEnds, } from "../utils"; import { getPublisher } from "../../../utils/helpers"; +const getEmojicoinLockedDiffFromSwapRes = ( + swapRes: Awaited>, + melee: MeleeEmojiData +) => { + let emojicoin0Locked = 0n; + let emojicoin1Locked = 0n; + const swaps = swapRes.events.swapEvents; + const swap0 = swaps.find((s) => s.marketID === melee.market1.marketID)!; + const swap1 = swaps.find((s) => s.marketID === melee.market2.marketID)!; + const swap = swapRes.events.arenaSwapEvents[0]; + if (swap1.isSell) { + emojicoin0Locked += swap0.netProceeds; + emojicoin1Locked -= swap1.inputAmount; + expect(swap0.netProceeds).toEqual(swap.emojicoin0Proceeds); + } else { + emojicoin0Locked -= swap0.inputAmount; + emojicoin1Locked += swap1.netProceeds; + expect(swap1.netProceeds).toEqual(swap.emojicoin1Proceeds); + } + + return { emojicoin0Locked, emojicoin1Locked }; +}; + const PROCESSING_WAIT_TIME = 2 * 1000; const waitForProcessor = < T extends { version: AnyNumberString } | { response: UserTransactionResponse }, @@ -81,34 +105,28 @@ describe("ensures an arena correctly unfolds and the processor data is accurate" const MELEE_DURATION = ONE_SECOND_MICROSECONDS * 10n; + const emojis: SymbolEmoji[][] = [ + ["♑"], + ["♒"], + ["♈"], + ["♎"], + ["♍"], + ["♊"], + ["♌"], + ["⛎"], + ["♓"], + ["♐"], + ["♏"], + ["♉"], + ]; + beforeAll( async () => { - await registerAndUnlockInitialMarketsForArenaTest(); - const emojis: SymbolEmoji[][] = [ - ["♑"], - ["♒"], - ["♈"], - ["♎"], - ["♍"], - ["♊"], - ["♌"], - ["⛎"], - ["♓"], - ["♐"], - ["♏"], - ["♉"], - ]; for (const emoji of emojis) { await emojicoin.register(getFundedAccount("667"), emoji); await emojicoin.buy(getFundedAccount("667"), emoji, 100000000n); } - await waitUntilCurrentMeleeEnds(); - await setNextMeleeDurationAndEnsureCrank(MELEE_DURATION).then((res) => { - melee = res.melee; - return waitForProcessor(res); - }); - - return true; + melee = await fetchArenaMeleeView(1n).then(fetchMeleeEmojiData); }, 5 * 60 * 1000 ); @@ -121,17 +139,6 @@ describe("ensures an arena correctly unfolds and the processor data is accurate" expect(res.bytecode).toBeTruthy(); }); - it("verifies an arena has already started with a duration of 15 seconds", async () => { - expect(melee.view.duration).toEqual(MELEE_DURATION); - }); - - it("verifies an arena has started in the last 5 seconds", async () => { - const fiveSeconds = 5 * 1000; - const now = new Date().getTime(); - const fiveSecondsAgo = now - fiveSeconds; - expect(melee.view.startTime.getTime()).toBeGreaterThan(fiveSecondsAgo); - }); - it("verifies arena data is correctly inserted into the processor", async () => { const arenaInfo = await fetchArenaInfo({}); expect(arenaInfo).toBeTruthy(); @@ -152,6 +159,55 @@ describe("ensures an arena correctly unfolds and the processor data is accurate" expect(arenaInfo?.emojicoin1MarketAddress).toEqual(melee.market2.marketAddress); }); + it("verifies an arena ends with no activity and nothing bad happens", async () => { + const publisher = getPublisher(); + + await emojicoin.arena.setNextMeleeDuration(publisher, MELEE_DURATION); + + await waitUntilCurrentMeleeEnds(); + + const swaps0 = await fetchSwapEvents({ marketID: melee.market1.marketID }); + const swaps1 = await fetchSwapEvents({ marketID: melee.market2.marketID }); + + expect(swaps0).toHaveLength(0); + expect(swaps1).toHaveLength(0); + + const enterResponse = await emojicoin.arena.enter( + getFundedAccount("007"), + 1n * 10n ** 8n, + false, + melee.market1.symbolEmojis, + melee.market2.symbolEmojis, + "symbol1" + ); + await waitForProcessor(enterResponse); + + await emojicoin.buy(publisher, melee.market1.symbolEmojis, 1n); + await emojicoin.buy(publisher, melee.market2.symbolEmojis, 1n); + + const leaderboard: ArenaLeaderboardHistoryModel[] | null = await postgrest + .from(TableName.ArenaLeaderboardHistory) + .select("*") + .eq("melee_id", melee.view.meleeID.toString()) + .then((r) => r.data) + .then((r) => (r === null ? null : r.map(toArenaLeaderboardHistoryModel))); + + expect(leaderboard).not.toBeNull(); + expect(leaderboard).toHaveLength(0); + }, 20000); + + it("verifies an arena has already started with a duration of 15 seconds", async () => { + melee = await fetchArenaMeleeView(2n).then(fetchMeleeEmojiData); + expect(melee.view.duration).toEqual(MELEE_DURATION); + }); + + it("verifies an arena has started in the last 5 seconds", async () => { + const fiveSeconds = 5 * 1000; + const now = new Date().getTime(); + const fiveSecondsAgo = now - fiveSeconds; + expect(melee.view.startTime.getTime()).toBeGreaterThan(fiveSecondsAgo); + }); + it("verifies enter+swap+exit procedure", async () => { const account = getFundedAccount("007"); const enterResponse = await emojicoin.arena.enter( @@ -310,7 +366,7 @@ describe("ensures an arena correctly unfolds and the processor data is accurate" expect(position.withdrawals).toBeLessThanOrEqual((withdrawalsApt * 10001n) / 10000n); expect(position.emojicoin0Balance).toEqual(0n); expect(position.emojicoin1Balance).toEqual(0n); - }); + }, 30000); }); describe("ensures leaderboard history is working", () => { @@ -497,3 +553,179 @@ describe("ensures leaderboard history is working", () => { expect(leaderboard3.lastExit0).toBeNull(); }, 15000); }); + +describe("ensures arena info is working", () => { + const emojicoin = new EmojicoinClient(); + + let melee: MeleeEmojiData; + + const MELEE_DURATION = ONE_SECOND_MICROSECONDS * 15n; + + const publisher = getPublisher(); + + // Utility function to avoid repetitive code. Only the `account` and `escrowCoin` differs. + const enterHelper = (account: Account, escrowCoin: "symbol1" | "symbol2") => + emojicoin.arena.enter( + account, + ONE_APT_BIGINT, + false, + melee.market1.symbolEmojis, + melee.market2.symbolEmojis, + escrowCoin + ); + + beforeAll(async () => { + await waitUntilCurrentMeleeEnds(); + await setNextMeleeDurationAndEnsureCrank(MELEE_DURATION).then((res) => { + melee = res.melee; + return waitForEmojicoinIndexer(res.version, PROCESSING_WAIT_TIME); + }); + }, 30000); + + beforeEach(async () => { + await waitUntilCurrentMeleeEnds(); + // Crank the melee to end it and start a new one. + const res = await emojicoin.arena.enter( + publisher, + 1n, + false, + melee.market1.symbolEmojis, + melee.market2.symbolEmojis, + "symbol1" + ); + melee = await fetchArenaMeleeView(res.arena.event.meleeID).then(fetchMeleeEmojiData); + await waitForProcessor(res); + + return true; + }, 30000); + + it("verifies that arena info data is correct 1", async () => { + const account1 = getFundedAccount("420"); + const account2 = getFundedAccount("421"); + const account3 = getFundedAccount("422"); + + let volume = 0n; + + const account1EnterRes = await enterHelper(account1, "symbol1"); + const account2EnterRes = await enterHelper(account2, "symbol1"); + const account3EnterRes = await enterHelper(account3, "symbol1"); + + volume += account1EnterRes.events.arenaEnterEvents[0].quoteVolume; + volume += account2EnterRes.events.arenaEnterEvents[0].quoteVolume; + volume += account3EnterRes.events.arenaEnterEvents[0].quoteVolume; + + await emojicoin.arena.exit(account1, melee.market1.symbolEmojis, melee.market2.symbolEmojis); + const swapRes = await emojicoin.arena.swap( + account2, + melee.market1.symbolEmojis, + melee.market2.symbolEmojis + ); + volume += swapRes.events.arenaSwapEvents[0].quoteVolume; + const res = await emojicoin.arena.exit( + account2, + melee.market1.symbolEmojis, + melee.market2.symbolEmojis + ); + await waitForProcessor(res); + + const arenaInfo = await fetchArenaInfo({}); + + expect(arenaInfo).toBeTruthy(); + expect(arenaInfo!.meleeID).toEqual(melee.view.meleeID); + expect(arenaInfo!.duration).toEqual(melee.view.duration); + expect(arenaInfo!.startTime).toEqual(melee.view.startTime); + expect(arenaInfo!.maxMatchAmount).toEqual(melee.view.maxMatchAmount); + expect(arenaInfo!.maxMatchPercentage).toEqual(melee.view.maxMatchPercentage); + expect(arenaInfo!.volume).toEqual(volume); + expect(arenaInfo!.emojicoin0Locked).toEqual( + account3EnterRes.events.arenaEnterEvents[0].emojicoin0Proceeds + ); + expect(arenaInfo!.emojicoin1Locked).toEqual(0n); + }, 30000); + + it("verifies that arena info data is correct 2", async () => { + let volume = 0n; + let emojicoin0Locked = 0n; + let emojicoin1Locked = 0n; + + const accounts = [ + getFundedAccount("420"), + getFundedAccount("421"), + getFundedAccount("422"), + getFundedAccount("423"), + getFundedAccount("424"), + getFundedAccount("425"), + getFundedAccount("426"), + getFundedAccount("427"), + ]; + + for (const account of accounts.map((a, i) => [a, i] as [Ed25519Account, number])) { + const enterRes = await enterHelper( + account[0], + `symbol${(account[1] % 2) + 1}` as "symbol1" | "symbol2" + ); + volume += enterRes.events.arenaEnterEvents[0].quoteVolume; + emojicoin0Locked += enterRes.events.arenaEnterEvents[0].emojicoin0Proceeds; + emojicoin1Locked += enterRes.events.arenaEnterEvents[0].emojicoin1Proceeds; + } + + for (const account of accounts) { + const swapRes = await emojicoin.arena.swap( + account, + melee.market1.symbolEmojis, + melee.market2.symbolEmojis + ); + volume += swapRes.events.arenaSwapEvents[0].quoteVolume; + const diff = getEmojicoinLockedDiffFromSwapRes(swapRes, melee); + emojicoin0Locked += diff.emojicoin0Locked; + emojicoin1Locked += diff.emojicoin1Locked; + } + + for (const account of accounts.slice(0, Math.floor(accounts.length / 2))) { + const swapRes = await emojicoin.arena.swap( + account, + melee.market1.symbolEmojis, + melee.market2.symbolEmojis + ); + volume += swapRes.events.arenaSwapEvents[0].quoteVolume; + const diff = getEmojicoinLockedDiffFromSwapRes(swapRes, melee); + emojicoin0Locked += diff.emojicoin0Locked; + emojicoin1Locked += diff.emojicoin1Locked; + } + + for (const account of accounts.slice(Math.floor(accounts.length / 2))) { + const exitRes = await emojicoin.arena.exit( + account, + melee.market1.symbolEmojis, + melee.market2.symbolEmojis + ); + emojicoin0Locked -= exitRes.events.arenaExitEvents[0].emojicoin0Proceeds; + emojicoin1Locked -= exitRes.events.arenaExitEvents[0].emojicoin1Proceeds; + } + + await sleep(1000); + + const arenaPositions: ArenaPositionModel[] | null = await postgrest + .from(TableName.ArenaPosition) + .select("*") + .eq("melee_id", melee.view.meleeID) + .then((r) => r.data) + .then((r) => (r === null ? null : r.map(toArenaPositionModel))); + + expect(arenaPositions).not.toBeNull(); + expect(arenaPositions!.reduce((p, c) => p + c.emojicoin0Balance, 0n)).toEqual(emojicoin0Locked); + expect(arenaPositions!.reduce((p, c) => p + c.emojicoin1Balance, 0n)).toEqual(emojicoin1Locked); + + const arenaInfo = await fetchArenaInfo({}); + + expect(arenaInfo).toBeTruthy(); + expect(arenaInfo!.meleeID).toEqual(melee.view.meleeID); + expect(arenaInfo!.duration).toEqual(melee.view.duration); + expect(arenaInfo!.startTime).toEqual(melee.view.startTime); + expect(arenaInfo!.maxMatchAmount).toEqual(melee.view.maxMatchAmount); + expect(arenaInfo!.maxMatchPercentage).toEqual(melee.view.maxMatchPercentage); + expect(arenaInfo!.volume).toEqual(volume); + expect(arenaInfo!.emojicoin0Locked).toEqual(emojicoin0Locked); + expect(arenaInfo!.emojicoin1Locked).toEqual(emojicoin1Locked); + }, 30000); +});