import { getAccount, getAssociatedTokenAddress } from '@solana/spl-token'; import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import { AnchorProvider, BN, Program, Wallet } from '@staratlas/anchor'; import { CARGO_IDL, CargoPermissions, CargoPod, CargoStatsDefinition, CargoType, } from '@staratlas/cargo'; import { CREW_IDL } from '@staratlas/crew'; import { FixedSizeArray, InstructionReturn, buildSendAndCheck, createMint, createTokenAccount, keypairToAsyncSigner, mintToTokenAccount, readFromRPCOrError, stringToByteArray, } from '@staratlas/data-source'; import { PLAYER_PROFILE_IDL } from '@staratlas/player-profile'; import { POINTS_IDL, UserPoints } from '@staratlas/points'; import { Faction, PROFILE_FACTION_IDL, ProfileFactionAccount, } from '@staratlas/profile-faction'; import { AddShipEscrowInput, DisbandedFleet, Fleet, FleetShips, Game, GameState, ProgressionConfig, RegisterShipInput, SAGE_IDL, SagePermissions, SagePlayerProfile, Sector, Ship, ShipStats, SizeClass, Starbase, StarbasePlayer, getSizeClassAnchorEnum, } from '../src'; import { BALANCED_STAR_BASE_LEVELS, CREW_PROGRAM_ID, SAGE_PROGRAM_ID, SageGameTest, createAndFundTokenAccounts, createCargoTypes, createManyMints, createProfile, getRandomInt, mintAndImportCrewToGame, sendManyToChain, setupCrewConfigInstructions, setupPointModifierAccounts, setupPointsAccounts, setupProgression, setupUmi, } from './helpers'; import { chunk } from 'lodash'; describe('Invalid Fleets test', () => { const connection = new Connection('http://localhost:8899', 'confirmed'); const walletKeypair = Keypair.generate(); const walletSigner = keypairToAsyncSigner(walletKeypair); const provider = new AnchorProvider( connection, new Wallet(walletKeypair), AnchorProvider.defaultOptions(), ); const program = new Program(SAGE_IDL, SAGE_PROGRAM_ID, provider); const playerProfileProgram = new Program( PLAYER_PROFILE_IDL, new PublicKey('PprofUW1pURCnMW2si88GWPXEEK3Bvh9Tksy8WtnoYJ'), provider, ); const profileFactionProgram = new Program( PROFILE_FACTION_IDL, new PublicKey('pFACzkX2eSpAjDyEohD6i3VRJvREtH9ynbtM1DwVFsj'), provider, ); const cargoProgram = new Program( CARGO_IDL, new PublicKey('CArGoi989iv3VL3xArrJXmYYDNhjwCX5ey5sY5KKwMG'), provider, ); const crewProgram = new Program(CREW_IDL, CREW_PROGRAM_ID, provider); const pointsProgram = new Program( POINTS_IDL, new PublicKey('PointJfvuHi8DgGsPCy97EaZkQ6NvpghAAVkuquLf3w'), provider, ); const funder = keypairToAsyncSigner(Keypair.generate()); const authority = keypairToAsyncSigner(Keypair.generate()); const playerProfile = keypairToAsyncSigner(Keypair.generate()); // fleets stuff const _sageSigner = keypairToAsyncSigner(Keypair.generate()); let _sagePlayerProfileKey: PublicKey; let _starbaseKey: PublicKey; let _nonCssStarbaseKey: PublicKey; let _nonCssStarbasePlayerKey: PublicKey; let _starbasePlayerKey: PublicKey; const _starbasePodSeeds = Keypair.generate().publicKey.toBuffer(); const _starbaseCargoPod = CargoPod.findAddress( cargoProgram, _starbasePodSeeds, )[0]; const _toIdleFleetKey = keypairToAsyncSigner(Keypair.generate()); const _toLoadingBayKey = keypairToAsyncSigner(Keypair.generate()); const _fleetManagerKey = keypairToAsyncSigner(Keypair.generate()); const _removeShipEscrowKey = keypairToAsyncSigner(Keypair.generate()); const _shipManagerKey = keypairToAsyncSigner(Keypair.generate()); const _manageFleetCargoKey = keypairToAsyncSigner(Keypair.generate()); const _moveFleetKey = keypairToAsyncSigner(Keypair.generate()); let fleet1: PublicKey; let fleet1FuelTank: PublicKey; let fleet1AmmoBank: PublicKey; let fleet1CargoHold: PublicKey; // `Ship` const ship = keypairToAsyncSigner(Keypair.generate()); const ship2 = keypairToAsyncSigner(Keypair.generate()); // Ship 3 will be our invalid ship const ship3 = keypairToAsyncSigner(Keypair.generate()); // ship mint const shipMint = keypairToAsyncSigner(Keypair.generate()); let _shipOriginTokenAccount: PublicKey; let _shipEscrowTokenAccount: PublicKey; // Sector stuff const _game = keypairToAsyncSigner(Keypair.generate()); let _gameState: PublicKey; let newGameStateKey: PublicKey; let _sectorKey: PublicKey; let _sector2Key: PublicKey; const _ammo = Keypair.generate(); const _food = Keypair.generate(); const _fuel = Keypair.generate(); const _repairKit = Keypair.generate(); const _fleetCargoMints = [_fuel, _ammo, _repairKit, _food]; const _updatedRiskZones = { mudSecurityZone: { center: [new BN(-5), new BN(-5)], radius: new BN(1), } as never, oniSecurityZone: { center: [new BN(5), new BN(5)], radius: new BN(1), } as never, usturSecurityZone: { center: [new BN(1), new BN(1)], radius: new BN(1), } as never, highRiskZone: { center: [new BN(-1), new BN(-1)], radius: new BN(1), } as never, mediumRiskZone: { center: [new BN(0), new BN(0)], radius: new BN(20), } as never, }; // cargo stuff const _cargoStatsDefinitionSigner = keypairToAsyncSigner(Keypair.generate()); const _cargoSigner = keypairToAsyncSigner(Keypair.generate()); // Points let _miningXpCategory: PublicKey; let _pilotXpCategory: PublicKey; let _councilRankXpCategory: PublicKey; let _userPilotXp: PublicKey; let _pilotXpModifier: PublicKey; let _userCouncilRankXp: PublicKey; let _councilRankXpModifier: PublicKey; // key indices const CARGO_MANAGER = 1; const SAGE_MANAGER = 2; const FLEET_MANAGER = 3; const REMOVE_ESCROW = 4; const MANAGE_SHIP = 5; const MANAGE_CARGO = 6; const LOADING_BAY_TO_IDLE = 7; const IDLE_TO_LOADING_BAY = 8; const MOVE_FLEET = 9; const _createCargoStatsDefinition = async ( statsDefinitionSigner = _cargoStatsDefinitionSigner, ) => { await buildSendAndCheck( CargoStatsDefinition.initDefinition({ program: cargoProgram, profile: playerProfile.publicKey(), statsDefinition: statsDefinitionSigner, input: { cargoStats: 1, }, }), walletSigner, provider.connection, ); }; const _createFleet = async ( fleetName = 'mosh', shipToUse = ship, shipAmount = 1, shipEscrowIndex = 0, starbasePlayer: PublicKey = _starbasePlayerKey, starbase: PublicKey = _starbaseKey, ) => { const fleetLabel = stringToByteArray(fleetName, 32); const fleetAddress = Fleet.findAddress( program, _game.publicKey(), playerProfile.publicKey(), fleetLabel, )[0]; const createFleetIxResult = Fleet.createFleet( program, cargoProgram, _fleetManagerKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], shipToUse.publicKey(), starbasePlayer, starbase, _game.publicKey(), _gameState, _cargoStatsDefinitionSigner.publicKey(), { shipAmount, shipEscrowIndex, fleetLabel, keyIndex: FLEET_MANAGER, }, ); await buildSendAndCheck( createFleetIxResult.instructions, walletSigner, provider.connection, ); const fleet = await readFromRPCOrError( provider.connection, program, fleetAddress, Fleet, 'confirmed', ); expect(fleet.key.equals(fleetAddress)).toBe(true); expect( fleet.data.cargoHold.equals(createFleetIxResult.cargoHoldKey[0]), ).toBe(true); expect(fleet.data.fuelTank.equals(createFleetIxResult.fuelTankKey[0])).toBe( true, ); expect(fleet.data.ammoBank.equals(createFleetIxResult.ammoBankKey[0])).toBe( true, ); expect(fleet.state.StarbaseLoadingBay?.starbase.equals(_starbaseKey)).toBe( true, ); return { ammoBankKey: createFleetIxResult.ammoBankKey, cargoHoldKey: createFleetIxResult.cargoHoldKey, fuelTankKey: createFleetIxResult.fuelTankKey, fleetKey: createFleetIxResult.fleetKey, fleetData: fleet, }; }; const _createGame = async () => { await buildSendAndCheck( Game.initGame(program, authority, playerProfile.publicKey(), _game), walletSigner, provider.connection, ); const game = await readFromRPCOrError( provider.connection, program, _game.publicKey(), Game, 'confirmed', ); const gameStateId = GameState.findAddress( program, _game.publicKey(), game.data.updateId.add(new BN(1)), ); _gameState = gameStateId[0]; const createGameStateIx = GameState.initGameState( program, _sageSigner, playerProfile.publicKey(), _game.publicKey(), _gameState, { keyIndex: SAGE_MANAGER, }, ); const updateLevers = GameState.updateGameState( program, _sageSigner, playerProfile.publicKey(), _game.publicKey(), _gameState, SAGE_MANAGER, { maxFleetSize: 64, } /** fleet */, ); // update levers await buildSendAndCheck( [createGameStateIx, updateLevers].flat(), walletSigner, provider.connection, ); const updateGameIx = Game.updateGame( _game.publicKey(), program, _sageSigner, playerProfile.publicKey(), SAGE_MANAGER, undefined /** points */, { ammo: _ammo.publicKey, food: _food.publicKey, fuel: _fuel.publicKey, repairKit: _repairKit.publicKey, } /** mints */, undefined /** vaults */, undefined /** crafting */, { statsDefinition: _cargoStatsDefinitionSigner.publicKey() } /** cargo */, _updatedRiskZones /** risk zones */, ); const updateGameIx2 = Game.updateGame( _game.publicKey(), program, _sageSigner, playerProfile.publicKey(), SAGE_MANAGER, { pilotXpCategory: _pilotXpCategory, miningXpCategory: _miningXpCategory, councilRankXpCategory: _councilRankXpCategory, } /** points */, ); const starbaseLevelsChunks = chunk(BALANCED_STAR_BASE_LEVELS, 7); const updateStarbaseLevelInfoIxs: InstructionReturn[] = starbaseLevelsChunks.map((thisChunk) => GameState.updateGameState( program, _sageSigner, playerProfile.publicKey(), _game.publicKey(), _gameState, SAGE_MANAGER, { starbaseLevels: thisChunk }, ), ); const activateGameIx = GameState.activateGameState( program, _sageSigner, playerProfile.publicKey(), _game.publicKey(), _gameState, { keyIndex: SAGE_MANAGER, }, ); const sectorIx = Sector.registerSector( program, _sageSigner, playerProfile.publicKey(), playerProfile.publicKey() /** discoverer */, _game.publicKey(), [new BN(1), new BN(1)], 'Super Sector', SAGE_MANAGER, ); _sectorKey = sectorIx.sectorKey[0]; const sector2Ix = Sector.registerSector( program, _sageSigner, playerProfile.publicKey(), playerProfile.publicKey(), _game.publicKey(), [new BN(0), new BN(1)], 'Sector 2', SAGE_MANAGER, ); _sector2Key = sector2Ix.sectorKey[0]; await sendManyToChain( [ updateGameIx, updateGameIx2, ...updateStarbaseLevelInfoIxs, activateGameIx, sectorIx.instructions, sector2Ix.instructions, ], walletSigner, provider.connection, ); }; const _createStarbase = async ( sector: PublicKey, starbaseName: string, subCoordinates: [BN, BN], starbaseLevel: number, ): Promise => { const sectorAcc = await readFromRPCOrError( provider.connection, program, sector, Sector, 'confirmed', ); await buildSendAndCheck( Starbase.registerStarbase( program, _sageSigner, playerProfile.publicKey(), sector, _game.publicKey(), _gameState, { name: stringToByteArray(starbaseName, 64), subCoordinates: subCoordinates, sectorCoordinates: sectorAcc.data.coordinates as [BN, BN], starbaseLevelIndex: starbaseLevel, faction: Faction.Ustur, keyIndex: SAGE_MANAGER, }, ), walletSigner, provider.connection, ); const starbaseKey = Starbase.findAddress( program, _game.publicKey(), sectorAcc.data.coordinates as [BN, BN], )[0]; return starbaseKey; }; const _createSagePlayerProfile = async () => { await buildSendAndCheck( SagePlayerProfile.registerSagePlayerProfile( program, playerProfile.publicKey(), _game.publicKey(), _gameState, ), walletSigner, provider.connection, ); _sagePlayerProfileKey = SagePlayerProfile.findAddress( program, playerProfile.publicKey(), _game.publicKey(), )[0]; }; const _addCargoToStarbasePlayer = async ( starbasePlayerKey: PublicKey, amount: BN = new BN(100_000_000_000), ) => { // create token accounts for _starbaseCargoPod await Promise.all([ createAndFundTokenAccounts( authority, _fleetCargoMints.map((it) => [it.publicKey, 0]), _starbaseCargoPod, walletSigner, walletSigner, provider.connection, ), ]); // create starbasePlayer cargo pod await buildSendAndCheck( StarbasePlayer.createCargoPod( program, cargoProgram, starbasePlayerKey, _sageSigner, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], _starbaseKey, _cargoStatsDefinitionSigner.publicKey(), _game.publicKey(), _gameState, { keyIndex: SAGE_MANAGER, podSeeds: Array.from(_starbasePodSeeds), }, ), walletSigner, provider.connection, ); // add cargo to starbasePlayer for (let index = 0; index < _fleetCargoMints.length; index++) { const mint = _fleetCargoMints[index]; if (mint) { const tokenFrom = await getAssociatedTokenAddress( mint.publicKey, _sageSigner.publicKey(), true, ); const tokenTo = await getAssociatedTokenAddress( mint.publicKey, _starbaseCargoPod, true, ); const ix = StarbasePlayer.depositCargoToGame( program, cargoProgram, starbasePlayerKey, _sageSigner, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], _starbaseKey, _starbaseCargoPod, CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), mint.publicKey, 0, )[0], _cargoStatsDefinitionSigner.publicKey(), tokenFrom, tokenTo, _game.publicKey(), _gameState, { amount, keyIndex: SAGE_MANAGER, }, ); await buildSendAndCheck(ix, walletSigner, provider.connection); } } }; const _createStarbasePlayer = async ( starbase: PublicKey = _starbaseKey, skipCargo = false, ): Promise => { const starbaseSeqId = 0; await buildSendAndCheck( StarbasePlayer.registerStarbasePlayer( program, ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], _sagePlayerProfileKey, starbase, _game.publicKey(), _gameState, starbaseSeqId, ), walletSigner, provider.connection, ); const starbasePlayerKey = StarbasePlayer.findAddress( program, starbase, _sagePlayerProfileKey, starbaseSeqId, )[0]; if (!skipCargo) { await _addCargoToStarbasePlayer(starbasePlayerKey); } return starbasePlayerKey; }; const _createShipMint = async (mint = shipMint) => { // mint `Ship` to walletSigner token account const initMintIx = createMint(mint, 0, authority.publicKey(), null); // simulate transaction await buildSendAndCheck(initMintIx, walletSigner, provider.connection); }; const _createShipTokens = async ( mint = shipMint, originAuthority = walletSigner.publicKey(), escrowAuthority = _sagePlayerProfileKey, amount = 1, ): Promise<{ shipOriginTokenAccount: PublicKey; shipEscrowTokenAccount: PublicKey; }> => { // walletSigner's token account that will own Ship const shipOriginTokenAccount = keypairToAsyncSigner(Keypair.generate()); const initTokenAccountIx = createTokenAccount( shipOriginTokenAccount, originAuthority, mint.publicKey(), ); const mintToIx = mintToTokenAccount( authority, mint.publicKey(), shipOriginTokenAccount.publicKey(), amount, ); const shipEscrowTokenAccount = keypairToAsyncSigner(Keypair.generate()); const initShipEscrowIx = createTokenAccount( shipEscrowTokenAccount, escrowAuthority, mint.publicKey(), ); await buildSendAndCheck( [initTokenAccountIx, mintToIx, initShipEscrowIx], walletSigner, provider.connection, ); return { shipOriginTokenAccount: shipOriginTokenAccount.publicKey(), shipEscrowTokenAccount: shipEscrowTokenAccount.publicKey(), }; }; // _createShipTokens const _createShip = async ( shipKey = ship, mint = shipMint.publicKey(), sizeClass = getRandomInt(SizeClass.Medium), crew = 0, activate = true, ) => { const stats: ShipStats = SageGameTest.getDefaultShipStats(crew); const input: RegisterShipInput = { name: stringToByteArray('💀❤️🤖', 64) as FixedSizeArray, sizeClass: getSizeClassAnchorEnum(sizeClass), stats: stats, isActive: activate, keyIndex: SAGE_MANAGER, }; await buildSendAndCheck( Ship.registerShip( program, _sageSigner, playerProfile.publicKey(), shipKey, mint, _game.publicKey(), input, ), walletSigner, provider.connection, ); }; // init Ship const syncGameAccount = async () => { // Sync game account const gameAcc = await readFromRPCOrError( provider.connection, program, _game.publicKey(), Game, ); newGameStateKey = GameState.findAddress( program, _game.publicKey(), gameAcc.data.updateId.add(new BN(1)), )[0]; const ixs = [ GameState.copyGameState( program, _sageSigner, playerProfile.publicKey(), _game.publicKey(), _gameState, newGameStateKey, { keyIndex: SAGE_MANAGER }, ), GameState.activateGameState( program, _sageSigner, playerProfile.publicKey(), _game.publicKey(), newGameStateKey, { keyIndex: SAGE_MANAGER, }, ), ]; await buildSendAndCheck( (await Promise.all(ixs)).flat(), walletSigner, provider.connection, ); return newGameStateKey; }; beforeAll(async () => { const airdropTx = await connection.requestAirdrop( walletSigner.publicKey(), 500_000_000_000, ); await connection.confirmTransaction(airdropTx); const airdropTx2 = await connection.requestAirdrop( funder.publicKey(), 500_000_000_000, ); await connection.confirmTransaction(airdropTx2); // create mints await createManyMints( _fleetCargoMints, authority.publicKey(), walletSigner, provider.connection, ); await createAndFundTokenAccounts( authority, _fleetCargoMints.map((it) => [it.publicKey, 999_999_999_999]), _sageSigner.publicKey(), funder, walletSigner, provider.connection, ); // create player profile await createProfile( playerProfileProgram, profileFactionProgram, playerProfile, funder, [ // Cargo Superuser // 1 { key: _cargoSigner.publicKey(), expireTime: null, scope: cargoProgram.programId, permissions: CargoPermissions.all(), }, // Fleet Superuser // 2 { key: _sageSigner.publicKey(), expireTime: null, scope: program.programId, permissions: SagePermissions.all(), }, // SagePermissions::MANAGE_FLEET // 3 { key: _fleetManagerKey.publicKey(), expireTime: null, scope: program.programId, permissions: SagePermissions.manageFleetPermissions(), }, // SagePermissions::REMOVE_SHIP_ESCROW // 4 { key: _removeShipEscrowKey.publicKey(), expireTime: null, scope: program.programId, permissions: SagePermissions.removeShipEscrowPermissions(), }, // SagePermissions::MANAGE_SHIP // 5 { key: _shipManagerKey.publicKey(), expireTime: null, scope: program.programId, permissions: SagePermissions.manageShipPermissions(), }, // SagePermissions::MANAGE_FLEET_CARGO // 6 { key: _manageFleetCargoKey.publicKey(), expireTime: null, scope: program.programId, permissions: SagePermissions.manageFleetCargoPermissions(), }, // SagePermissions::LOADING_BAY_TO_IDLE // 7 { key: _toIdleFleetKey.publicKey(), expireTime: null, scope: program.programId, permissions: SagePermissions.transitionFromLoadingBayPermissions(), }, // SagePermissions::IDLE_TO_LOADING_BAY // 8 { key: _toLoadingBayKey.publicKey(), expireTime: null, scope: program.programId, permissions: SagePermissions.transitionFromIdlePermissions(), }, // SagePermissions::MOVE_FLEET // 9 { key: _moveFleetKey.publicKey(), expireTime: null, scope: program.programId, permissions: SagePermissions.moveFleetPermissions(), }, ], provider.connection, authority, walletSigner, Faction.Ustur, ); await _createCargoStatsDefinition(); await createCargoTypes( _fleetCargoMints.map((it) => it.publicKey), _cargoStatsDefinitionSigner.publicKey(), _cargoSigner, playerProfile.publicKey(), walletSigner, 0, { keyIndex: CARGO_MANAGER, values: [new BN(1)], }, provider.connection, cargoProgram, ); const pointsResult = await setupPointsAccounts( authority, walletSigner, playerProfile.publicKey(), playerProfile.publicKey(), pointsProgram, connection, ); _miningXpCategory = pointsResult.miningXpCategory; _pilotXpCategory = pointsResult.pilotXpCategory; _councilRankXpCategory = pointsResult.councilRankXpCategory; await _createGame(); _starbaseKey = await _createStarbase( _sectorKey, 'Star Base Alpha', [new BN(1), new BN(1)], 6, ); _nonCssStarbaseKey = _nonCssStarbasePlayerKey = await _createStarbase( _sector2Key, 'Star Base Beta', [new BN(1), new BN(1)], 1, ); // setup crew const crewConfigResult = await setupCrewConfigInstructions( authority, playerProfile.publicKey(), 0, _game.publicKey(), program, crewProgram, await setupUmi(walletSigner, provider.connection), provider.connection, ); if (crewConfigResult.instructions.length > 0) { await sendManyToChain( crewConfigResult.instructions, walletSigner, provider.connection, 'confirmed', true, ); } await _createSagePlayerProfile(); _starbasePlayerKey = await _createStarbasePlayer(); _nonCssStarbasePlayerKey = await _createStarbasePlayer( _nonCssStarbaseKey, true, ); await _createShipMint(); await _createShip(ship, shipMint.publicKey(), SizeClass.XxSmall, 1); await _createShip(ship2, shipMint.publicKey(), SizeClass.Medium, 2); await _createShip(ship3, shipMint.publicKey(), SizeClass.Large, 3, false); await setupPointModifierAccounts( { miningXpCategory: _miningXpCategory, pilotXpCategory: _pilotXpCategory, councilRankXpCategory: _councilRankXpCategory, }, authority, walletSigner, playerProfile.publicKey(), _game.publicKey(), program, pointsProgram, connection, ); const pointLimits = { dailyLpLimit: new BN(750_000), dailyCouncilRankXpLimit: new BN(650_000), dailyPilotXpLimit: new BN(550_000), dailyDataRunningXpLimit: new BN(700_000), dailyMiningXpLimit: new BN(600_000), dailyCraftingXpLimit: new BN(500_000), }; await setupProgression( authority, walletSigner, playerProfile.publicKey(), _game.publicKey(), program, connection, undefined /** progressionItems */, pointLimits, ); _userPilotXp = UserPoints.findAddress( pointsProgram, _pilotXpCategory, playerProfile.publicKey(), )[0]; _pilotXpModifier = Game.findPointsModifierAddress( program, _game.publicKey(), _pilotXpCategory, )[0]; _userCouncilRankXp = UserPoints.findAddress( pointsProgram, _councilRankXpCategory, playerProfile.publicKey(), )[0]; _councilRankXpModifier = Game.findPointsModifierAddress( program, _game.publicKey(), _councilRankXpCategory, )[0]; const progressionConfigData = await readFromRPCOrError( provider.connection, program, ProgressionConfig.findAddress(program, _game.publicKey())[0], ProgressionConfig, ); expect( progressionConfigData.data.dailyCouncilRankXpLimit.eq( pointLimits.dailyCouncilRankXpLimit, ), ).toBe(true); expect( progressionConfigData.data.dailyPilotXpLimit.eq( pointLimits.dailyPilotXpLimit, ), ).toBe(true); expect( progressionConfigData.data.dailyDataRunningXpLimit.eq( pointLimits.dailyDataRunningXpLimit, ), ).toBe(true); expect( progressionConfigData.data.dailyMiningXpLimit.eq( pointLimits.dailyMiningXpLimit, ), ).toBe(true); expect( progressionConfigData.data.dailyCraftingXpLimit.eq( pointLimits.dailyCraftingXpLimit, ), ).toBe(true); // import crew to game await mintAndImportCrewToGame( crewConfigResult.merkleTree, authority, crewConfigResult.crewProgramConfig, await setupUmi(walletSigner, provider.connection), program, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], _starbasePlayerKey, _starbaseKey, _game.publicKey(), funder, connection, [], // knownLeaves 10, // numCrew ); }); it('Cannot add invalid ship to game', async () => { const shipTokenResult = await _createShipTokens( shipMint, undefined, undefined, 100, ); _shipEscrowTokenAccount = shipTokenResult.shipEscrowTokenAccount; _shipOriginTokenAccount = shipTokenResult.shipOriginTokenAccount; // Try transferring invalid ship to SAGE (should fail) let success = true; const input: AddShipEscrowInput = { shipAmount: new BN(1), index: null, }; const addShipIx = SagePlayerProfile.addShipEscrow( program, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], _sagePlayerProfileKey, walletSigner, _shipOriginTokenAccount, ship3.publicKey(), _shipEscrowTokenAccount, _starbasePlayerKey, _starbaseKey, _game.publicKey(), _gameState, input, ); try { await buildSendAndCheck(addShipIx, walletSigner, provider.connection); } catch (err) { expect(JSON.stringify(err as Error)).toContain('InactiveAccount'); success = false; } expect(success).toBeFalsy(); }); it('Cannot add invalid ship to fleet', async () => { await buildSendAndCheck( ( await Promise.all( [ship, ship2].map((it) => SagePlayerProfile.addShipEscrow( program, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], _sagePlayerProfileKey, walletSigner, _shipOriginTokenAccount, it.publicKey(), _shipEscrowTokenAccount, _starbasePlayerKey, _starbaseKey, _game.publicKey(), _gameState, { shipAmount: new BN(1), index: null, }, ), ), ) ).flat(), walletSigner, provider.connection, ); // Creating A fleet const sbp = await readFromRPCOrError( provider.connection, program, _starbasePlayerKey, StarbasePlayer, 'confirmed', ); const shipEscrowIndex = sbp.wrappedShipEscrows.length; const fleetResult = await _createFleet('New Caledonia', ship, 1); fleet1 = fleetResult.fleetKey[0]; fleet1FuelTank = fleetResult.fuelTankKey[0]; fleet1AmmoBank = fleetResult.ammoBankKey[0]; fleet1CargoHold = fleetResult.cargoHoldKey[0]; // Invalidate Ship 2 await buildSendAndCheck( Ship.invalidateShip( program, _shipManagerKey, playerProfile.publicKey(), ship2.publicKey(), _game.publicKey(), MANAGE_SHIP, ), walletSigner, provider.connection, ); // Sync game account const newGameStateKey = await syncGameAccount(); // Update Ship in fleet await buildSendAndCheck( Fleet.updateShipInFleet( program, fleet1, ship.publicKey(), PublicKey.default, _game.publicKey(), newGameStateKey, { shipAmount: 1, fleetShipInfoIndex: 0 }, ), walletSigner, provider.connection, ); // Adding invalid ship to Fleet will Fail. let success = true; try { await buildSendAndCheck( Fleet.addShipToFleet( program, _fleetManagerKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleetResult.fleetKey[0], ship2.publicKey(), _starbasePlayerKey, _starbaseKey, _game.publicKey(), newGameStateKey, { shipAmount: 1, shipEscrowIndex, fleetShipInfoIndex: null, keyIndex: FLEET_MANAGER, }, ), walletSigner, provider.connection, ); } catch (err) { expect(JSON.stringify(err as Error)).toContain('InactiveAccount'); success = false; } expect(success).toBeFalsy(); _gameState = newGameStateKey; }); it('Invalid ships can be removed from the game at non CSS starbases', async () => { // update ship in escrow await buildSendAndCheck( SagePlayerProfile.updateShipEscrow( program, ship2.publicKey(), PublicKey.default, _starbasePlayerKey, _starbaseKey, _game.publicKey(), _gameState, { shipEscrowIndex: 0, }, ), walletSigner, provider.connection, ); // Create and fund pod token accounts await Promise.all([ createAndFundTokenAccounts( authority, [[_fuel.publicKey, 0]], fleet1FuelTank, walletSigner, walletSigner, provider.connection, ), ]); const fuelTokenAccount = await getAssociatedTokenAddress( _fuel.publicKey, fleet1FuelTank, true, ); const tokenFrom = await getAssociatedTokenAddress( _fuel.publicKey, _starbaseCargoPod, true, ); const tokenTo = await getAssociatedTokenAddress( _fuel.publicKey, fleet1FuelTank, true, ); await buildSendAndCheck( Fleet.depositCargoToFleet( program, cargoProgram, _manageFleetCargoKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], walletSigner.publicKey(), _starbaseKey, _starbasePlayerKey, fleet1, _starbaseCargoPod, fleet1FuelTank, CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), _fuel.publicKey, 0, )[0], _cargoStatsDefinitionSigner.publicKey(), tokenFrom, tokenTo, _fuel.publicKey, _game.publicKey(), _gameState, { amount: new BN(10000), keyIndex: MANAGE_CARGO, }, ), walletSigner, provider.connection, ); const loadinBayToIdle = Fleet.loadingBayToIdle( program, _toIdleFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet1, _starbaseKey, _starbasePlayerKey, _game.publicKey(), _gameState, LOADING_BAY_TO_IDLE, ); await buildSendAndCheck(loadinBayToIdle, walletSigner, provider.connection); const moveIx = Fleet.warpToCoordinate( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet1, fleet1FuelTank, CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), _fuel.publicKey, 0, )[0], _cargoStatsDefinitionSigner.publicKey(), fuelTokenAccount, _fuel.publicKey, newGameStateKey, _game.publicKey(), cargoProgram, { keyIndex: MOVE_FLEET, toSector: [new BN(0), new BN(1)] }, ); await buildSendAndCheck(moveIx, walletSigner, provider.connection); const fleetData = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); if (!fleetData.state.MoveWarp) { throw 'Fleet should be in MoveWarp state'; } const expectedTravelTime = Math.floor( Fleet.calculateWarpTimeWithCoords( fleetData.data.stats, fleetData.state.MoveWarp.fromSector as [BN, BN], fleetData.state.MoveWarp.toSector as [BN, BN], ), ); await new Promise((f) => setTimeout( f, (expectedTravelTime + (fleetData.data.stats).movementStats.warpCoolDown) * 1000, ), ); // Sync StarbasePlayer & Transition from IDLE to Loading Bay await buildSendAndCheck( [ StarbasePlayer.syncStarbasePlayer( program, _nonCssStarbasePlayerKey, _nonCssStarbaseKey, _game.publicKey(), _gameState, ), Fleet.moveWarpHandler( program, pointsProgram, playerProfile.publicKey(), fleet1, _userPilotXp, _pilotXpCategory, _pilotXpModifier, _userCouncilRankXp, _councilRankXpCategory, _councilRankXpModifier, _game.publicKey(), ), Fleet.idleToLoadingBay( program, _toLoadingBayKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet1, _nonCssStarbaseKey, _nonCssStarbasePlayerKey, _game.publicKey(), _gameState, IDLE_TO_LOADING_BAY, ), ], walletSigner, provider.connection, ); // Disband fleet const fleet = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, ); const disbandFleet = Fleet.disbandFleet( program, cargoProgram, _fleetManagerKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet, _nonCssStarbasePlayerKey, _nonCssStarbaseKey, _game.publicKey(), _gameState, { keyIndex: FLEET_MANAGER, }, ); await buildSendAndCheck( disbandFleet.instructions, walletSigner, provider.connection, ); // Disbanded Fleet to escrow await buildSendAndCheck( DisbandedFleet.disbandedFleetToEscrow( program, _fleetManagerKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], disbandFleet.disbandedFleetKey[0], FleetShips.findAddress(program, fleet1)[0], ship.publicKey(), _nonCssStarbasePlayerKey, _nonCssStarbaseKey, _game.publicKey(), _gameState, { fleetShipInfoIndex: 0, shipAmount: 1, shipEscrowIndex: null, keyIndex: FLEET_MANAGER, }, ), walletSigner, provider.connection, ); // Invalidate ship // Check token account balance before const tokenBalanceBefore = await getAccount( provider.connection, _shipOriginTokenAccount, 'confirmed', ); await buildSendAndCheck( Ship.invalidateShip( program, _shipManagerKey, playerProfile.publicKey(), ship.publicKey(), _game.publicKey(), MANAGE_SHIP, ), walletSigner, provider.connection, ); // Sync game account _gameState = await syncGameAccount(); // update ship in escrow await buildSendAndCheck( SagePlayerProfile.updateShipEscrow( program, ship.publicKey(), PublicKey.default, _nonCssStarbasePlayerKey, _nonCssStarbaseKey, _game.publicKey(), _gameState, { shipEscrowIndex: 0, }, ), walletSigner, provider.connection, ); // confirm that an invalid ship cannot be reprocessed let success = true; try { await buildSendAndCheck( SagePlayerProfile.updateShipEscrow( program, ship.publicKey(), PublicKey.default, _nonCssStarbasePlayerKey, _nonCssStarbaseKey, _game.publicKey(), _gameState, { shipEscrowIndex: 0, }, ), walletSigner, provider.connection, ); } catch (err) { expect(JSON.stringify(err as Error)).toContain('AlreadyProcessed'); success = false; } expect(success).toBe(false); // Can remove Invalid ship from Escrow at non CSS starbase const removeShipIx = SagePlayerProfile.removeInvalidShipEscrow( program, _removeShipEscrowKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], _sagePlayerProfileKey, _shipOriginTokenAccount, ship.publicKey(), _shipEscrowTokenAccount, _nonCssStarbasePlayerKey, _nonCssStarbaseKey, _game.publicKey(), _gameState, { shipAmount: new BN(1), permissionKeyIndex: REMOVE_ESCROW, shipEscrowIndex: 0, }, ); await buildSendAndCheck(removeShipIx, walletSigner, provider.connection); // Check 'Ship' removed from `WrappedShipEscrow` amount const tokenBalanceAfter = await getAccount( provider.connection, _shipOriginTokenAccount, 'confirmed', ); const sbp = await readFromRPCOrError( provider.connection, program, _nonCssStarbasePlayerKey, StarbasePlayer, ); const list = sbp.wrappedShipEscrows; expect(list.length).toBe(0); expect( new BN(tokenBalanceAfter.amount.toString()).sub( new BN(tokenBalanceBefore.amount.toString()), ), ).toStrictEqual(new BN(1)); }); });