import { getMint, TOKEN_2022_PROGRAM_ID, getAccount as getTokenAccount, createTransferCheckedInstruction, createTransferCheckedWithTransferHookInstruction, createExecuteInstruction, } from '@solana/spl-token'; import { Connection, Keypair, PublicKey, SYSVAR_INSTRUCTIONS_PUBKEY, } from '@solana/web3.js'; import { BN } from '@staratlas/anchor'; import { CargoType } from '@staratlas/cargo'; import { ixToIxReturn, keypairToAsyncSigner, readFromRPCOrError, } from '@staratlas/data-source'; import { findCertificateMintAddress, Starbase, StarbasePlayer } from '../src'; import { SageGameTest } from './helpers'; describe('Local Markets', () => { const connection = new Connection('http://localhost:8899', 'confirmed'); const walletKeypair = Keypair.generate(); const walletSigner = keypairToAsyncSigner(walletKeypair); let sageGameTest: SageGameTest; beforeAll(async () => { const airdropTx = await connection.requestAirdrop( walletSigner.publicKey(), 500_000_000_000, ); await connection.confirmTransaction(airdropTx); sageGameTest = await SageGameTest.createGame({ funder: walletKeypair, connection, }); }); it('can create certificate mint', async () => { if ( !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition || !sageGameTest.gameState ) { throw 'test environment not ready'; } // create new Sector await sageGameTest.createSector({ coordinates: [new BN(-3), new BN(-3)], numCrewForPlayer: 4, }); for (let index = 0; index < sageGameTest.sectors.length; index++) { const element = sageGameTest.sectors[index]; const starbaseAccount = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, element.starbases[0], Starbase, 'confirmed', ); const cargoMint = sageGameTest.mints[SageGameTest.FUEL]; const certificateMint = findCertificateMintAddress( sageGameTest.program, element.starbases[0], cargoMint, starbaseAccount.data.seqId, )[0]; await sageGameTest.sendManyToChain( [ Starbase.createCertificateMint( sageGameTest.program, new PublicKey(SageGameTest.MARKET_HOOK_PROGRAM_ID), element.starbases[0], cargoMint, certificateMint, CargoType.findAddress( sageGameTest.cargoProgram, sageGameTest.cargoStatsDefinition, cargoMint, 0, )[0], sageGameTest.gameId, ), ], true, ); const certificateMintAccount = await getMint( sageGameTest.connection, certificateMint, 'processed', TOKEN_2022_PROGRAM_ID, ); expect(certificateMintAccount.isInitialized).toBe(true); expect(Number(certificateMintAccount.supply)).toBe(0); expect( certificateMintAccount.mintAuthority?.equals(element.starbases[0]), ).toBe(true); } }); it('can mint certificates', async () => { if ( !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition || !sageGameTest.gameState ) { throw 'test environment not ready'; } const cargoMint = sageGameTest.mints[SageGameTest.FUEL]; const cargoAmount = 11; const sector1 = sageGameTest.sectors[0]; const starbaseAccount = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sector1.starbases[0], Starbase, 'confirmed', ); const certificateMint = findCertificateMintAddress( sageGameTest.program, sector1.starbases[0], cargoMint, starbaseAccount.data.seqId, )[0]; const certificateTokenTo = await sageGameTest.getOrCreateAssociatedTokenAccount( certificateMint, sageGameTest.profiles.superuser.key.publicKey(), true, TOKEN_2022_PROGRAM_ID, ); const cargoTokenFrom = await sageGameTest.getOrCreateAssociatedTokenAccount( cargoMint, sector1.starbasePlayers[0].cargoPod, true, ); const cargoTokenTo = await sageGameTest.getOrCreateAssociatedTokenAccount( cargoMint, sector1.starbases[0], true, ); const certificateTokenToBefore = await connection.getTokenAccountBalance( certificateTokenTo, 'confirmed', ); const cargoTokenFromBefore = await connection.getTokenAccountBalance( cargoTokenFrom, 'confirmed', ); const cargoTokenToBefore = await connection.getTokenAccountBalance( cargoTokenTo, 'confirmed', ); await sageGameTest.sendManyToChain( [ StarbasePlayer.mintCertificate( sageGameTest.program, sageGameTest.cargoProgram, sector1.starbasePlayers[0].starbasePlayer, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), sector1.starbases[0], sector1.starbasePlayers[0].cargoPod, CargoType.findAddress( sageGameTest.cargoProgram, sageGameTest.cargoStatsDefinition, cargoMint, 0, )[0], sageGameTest.cargoStatsDefinition, certificateTokenTo, certificateMint, cargoTokenFrom, cargoTokenTo, cargoMint, sageGameTest.gameId, sageGameTest.gameState, { amount: new BN(cargoAmount), keyIndex: sageGameTest.profiles.player1.index, }, ), ], true, ); const certificateTokenToAfter = await connection.getTokenAccountBalance( certificateTokenTo, 'confirmed', ); expect( Number(certificateTokenToAfter.value.amount) - Number(certificateTokenToBefore.value.amount), ).toEqual(cargoAmount); const cargoTokenFromAfter = await connection.getTokenAccountBalance( cargoTokenFrom, 'confirmed', ); expect( Number(cargoTokenFromBefore.value.amount) - Number(cargoTokenFromAfter.value.amount), ).toEqual(cargoAmount); const cargoTokenToAfter = await connection.getTokenAccountBalance( cargoTokenTo, 'confirmed', ); expect( Number(cargoTokenToAfter.value.amount) - Number(cargoTokenToBefore.value.amount), ).toEqual(cargoAmount); const certificateMintAccount = await getMint( sageGameTest.connection, certificateMint, 'processed', TOKEN_2022_PROGRAM_ID, ); expect(Number(certificateMintAccount.supply)).toBe(cargoAmount); // can only be minted at the Starbase it belongs to let success = true; try { const sector2 = sageGameTest.sectors[1]; const cargoTokenFrom = await sageGameTest.getOrCreateAssociatedTokenAccount( cargoMint, sector2.starbases[0], true, ); const cargoTokenTo = await sageGameTest.getOrCreateAssociatedTokenAccount( cargoMint, sector2.starbasePlayers[0].cargoPod, true, ); await sageGameTest.sendToChain( StarbasePlayer.mintCertificate( sageGameTest.program, sageGameTest.cargoProgram, sector2.starbasePlayers[0].starbasePlayer, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), sector2.starbases[0], sector2.starbasePlayers[0].cargoPod, CargoType.findAddress( sageGameTest.cargoProgram, sageGameTest.cargoStatsDefinition, cargoMint, 0, )[0], sageGameTest.cargoStatsDefinition, certificateTokenTo, certificateMint, cargoTokenFrom, cargoTokenTo, cargoMint, sageGameTest.gameId, sageGameTest.gameState, { amount: new BN(cargoAmount), keyIndex: sageGameTest.profiles.player1.index, }, ), ); } catch (err) { expect(JSON.stringify(err as Error)).toContain( 'A seeds constraint was violated', ); success = false; } expect(success).toBe(false); }); it('cannot transfer certificate OTC', async () => { if ( !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition || !sageGameTest.gameState ) { throw 'test environment not ready'; } const cargoMint = sageGameTest.mints[SageGameTest.FUEL]; const sector1 = sageGameTest.sectors[0]; const starbaseAccount = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sector1.starbases[0], Starbase, 'confirmed', ); const certificateMint = findCertificateMintAddress( sageGameTest.program, sector1.starbases[0], cargoMint, starbaseAccount.data.seqId, )[0]; const certificateMintAccount = await getMint( sageGameTest.connection, certificateMint, 'processed', TOKEN_2022_PROGRAM_ID, ); const certificateTokenFrom = await sageGameTest.getOrCreateAssociatedTokenAccount( certificateMint, sageGameTest.profiles.superuser.key.publicKey(), true, TOKEN_2022_PROGRAM_ID, ); const certificateTokenTo = await sageGameTest.getOrCreateAssociatedTokenAccount( certificateMint, sageGameTest.profiles.player1.key.publicKey(), true, TOKEN_2022_PROGRAM_ID, ); const transferInstruction0 = await createTransferCheckedWithTransferHookInstruction( sageGameTest.connection, certificateTokenFrom, certificateMint, certificateTokenTo, sageGameTest.profiles.superuser.key.publicKey(), BigInt(1), certificateMintAccount.decimals, [], 'processed', TOKEN_2022_PROGRAM_ID, ); let success = true; try { await sageGameTest.sendManyToChain( [ ixToIxReturn(transferInstruction0, [ sageGameTest.profiles.superuser.key, ]), ], false, ); } catch (err) { expect(JSON.stringify(err as Error)).toContain('ProgramMismatch'); success = false; } expect(success).toBe(false); }); it('can redeem certificates', async () => { if ( !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition || !sageGameTest.gameState ) { throw 'test environment not ready'; } const cargoMint = sageGameTest.mints[SageGameTest.FUEL]; const cargoAmount = 3; const sector1 = sageGameTest.sectors[0]; const starbaseAccount = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sector1.starbases[0], Starbase, 'confirmed', ); const certificateMint = findCertificateMintAddress( sageGameTest.program, sector1.starbases[0], cargoMint, starbaseAccount.data.seqId, )[0]; const certificateTokenFrom = await sageGameTest.getOrCreateAssociatedTokenAccount( certificateMint, sageGameTest.profiles.superuser.key.publicKey(), true, TOKEN_2022_PROGRAM_ID, ); const cargoTokenFrom = await sageGameTest.getOrCreateAssociatedTokenAccount( cargoMint, sector1.starbases[0], true, ); const cargoTokenTo = await sageGameTest.getOrCreateAssociatedTokenAccount( cargoMint, sector1.starbasePlayers[0].cargoPod, true, ); const certificateTokenFromBefore = await connection.getTokenAccountBalance( certificateTokenFrom, 'confirmed', ); const cargoTokenFromBefore = await connection.getTokenAccountBalance( cargoTokenFrom, 'confirmed', ); const cargoTokenToBefore = await connection.getTokenAccountBalance( cargoTokenTo, 'confirmed', ); const certificateMintAccountBefore = await getMint( sageGameTest.connection, certificateMint, 'processed', TOKEN_2022_PROGRAM_ID, ); await sageGameTest.sendManyToChain( [ StarbasePlayer.redeemCertificate( sageGameTest.program, sageGameTest.cargoProgram, sector1.starbasePlayers[0].starbasePlayer, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), 'funder', sector1.starbases[0], sector1.starbasePlayers[0].cargoPod, CargoType.findAddress( sageGameTest.cargoProgram, sageGameTest.cargoStatsDefinition, cargoMint, 0, )[0], sageGameTest.cargoStatsDefinition, certificateTokenFrom, sageGameTest.profiles.superuser.key, certificateMint, cargoTokenFrom, cargoTokenTo, cargoMint, sageGameTest.gameId, sageGameTest.gameState, { amount: new BN(cargoAmount), keyIndex: sageGameTest.profiles.player1.index, }, ), ], true, ); const certificateTokenFromAfter = await connection.getTokenAccountBalance( certificateTokenFrom, 'confirmed', ); expect( Number(certificateTokenFromBefore.value.amount) - Number(certificateTokenFromAfter.value.amount), ).toEqual(cargoAmount); const cargoTokenFromAfter = await connection.getTokenAccountBalance( cargoTokenFrom, 'confirmed', ); expect( Number(cargoTokenFromBefore.value.amount) - Number(cargoTokenFromAfter.value.amount), ).toEqual(cargoAmount); const cargoTokenToAfter = await connection.getTokenAccountBalance( cargoTokenTo, 'confirmed', ); expect( Number(cargoTokenToAfter.value.amount) - Number(cargoTokenToBefore.value.amount), ).toEqual(cargoAmount); const certificateMintAccountAfter = await getMint( sageGameTest.connection, certificateMint, 'processed', TOKEN_2022_PROGRAM_ID, ); expect(Number(certificateMintAccountAfter.supply)).toBe( Number(certificateMintAccountBefore.supply) - cargoAmount, ); // can only be redeemed at the Starbase it belongs to let success = true; try { const sector2 = sageGameTest.sectors[1]; const cargoTokenFrom = await sageGameTest.getOrCreateAssociatedTokenAccount( cargoMint, sector2.starbases[0], true, ); const cargoTokenTo = await sageGameTest.getOrCreateAssociatedTokenAccount( cargoMint, sector2.starbasePlayers[0].cargoPod, true, ); await sageGameTest.sendToChain( StarbasePlayer.redeemCertificate( sageGameTest.program, sageGameTest.cargoProgram, sector2.starbasePlayers[0].starbasePlayer, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), 'funder', sector2.starbases[0], sector2.starbasePlayers[0].cargoPod, CargoType.findAddress( sageGameTest.cargoProgram, sageGameTest.cargoStatsDefinition, cargoMint, 0, )[0], sageGameTest.cargoStatsDefinition, certificateTokenFrom, sageGameTest.profiles.superuser.key, certificateMint, cargoTokenFrom, cargoTokenTo, cargoMint, sageGameTest.gameId, sageGameTest.gameState, { amount: new BN(cargoAmount), keyIndex: sageGameTest.profiles.player1.index, }, ), ); } catch (err) { expect(JSON.stringify(err as Error)).toContain( 'A seeds constraint was violated', ); success = false; } expect(success).toBe(false); // can only be redeemed the amount available let success2 = true; try { await sageGameTest.sendToChain( StarbasePlayer.redeemCertificate( sageGameTest.program, sageGameTest.cargoProgram, sector1.starbasePlayers[0].starbasePlayer, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), 'funder', sector1.starbases[0], sector1.starbasePlayers[0].cargoPod, CargoType.findAddress( sageGameTest.cargoProgram, sageGameTest.cargoStatsDefinition, cargoMint, 0, )[0], sageGameTest.cargoStatsDefinition, certificateTokenFrom, sageGameTest.profiles.superuser.key, certificateMint, cargoTokenFrom, cargoTokenTo, cargoMint, sageGameTest.gameId, sageGameTest.gameState, { amount: new BN(1337), keyIndex: sageGameTest.profiles.player1.index, }, ), ); } catch (err) { expect(JSON.stringify(err as Error)).toContain('insufficient funds'); success2 = false; } expect(success2).toBe(false); // auto-closes after full redemption await sageGameTest.sendManyToChain( [ StarbasePlayer.redeemCertificate( sageGameTest.program, sageGameTest.cargoProgram, sector1.starbasePlayers[0].starbasePlayer, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), 'funder', sector1.starbases[0], sector1.starbasePlayers[0].cargoPod, CargoType.findAddress( sageGameTest.cargoProgram, sageGameTest.cargoStatsDefinition, cargoMint, 0, )[0], sageGameTest.cargoStatsDefinition, certificateTokenFrom, sageGameTest.profiles.superuser.key, certificateMint, cargoTokenFrom, cargoTokenTo, cargoMint, sageGameTest.gameId, sageGameTest.gameState, { amount: new BN(certificateTokenFromAfter.value.amount), keyIndex: sageGameTest.profiles.player1.index, }, ), ], true, ); const certificateTokenFromAfter2 = await connection.getAccountInfo( certificateTokenFrom, 'confirmed', ); expect(certificateTokenFromAfter2).toBeNull(); }); });