import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import { BN } from '@staratlas/anchor'; import { CargoPod } from '@staratlas/cargo'; import { InstructionReturn, createAssociatedTokenAccountIdempotent, keypairToAsyncSigner, mintToTokenAccount, readFromRPCOrError, stringToByteArray, } from '@staratlas/data-source'; import { Fleet, LocationType, MineItem, Planet, PlanetType, Resource, SagePlayerProfile, Sector, SectorRing, ShipStats, StarbasePlayer, SurveyDataUnitTracker, } from '../src'; import { SageGameTest, SageGameTestProfile } from './helpers'; describe('Fleet Rental', () => { const connection = new Connection('http://localhost:8899', 'confirmed'); const walletKeypair = Keypair.generate(); const walletSigner = keypairToAsyncSigner(walletKeypair); const invalidator1 = keypairToAsyncSigner(walletKeypair); const originalSector: [BN, BN] = [new BN(0), new BN(0)]; const toSector: [BN, BN] = [new BN(0), new BN(1)]; interface FleetResult { fleetKey: PublicKey; cargoHoldKey: PublicKey; fuelTankKey: PublicKey; ammoBankKey: PublicKey; } const fleetResults: FleetResult[] = []; const numFleets = 10; let cargoFleet: FleetResult; let sduFleet: FleetResult; let warpFleet: FleetResult; let warpFleet2: FleetResult; let warpFleet3: FleetResult; let warpFleet4: FleetResult; let subwarpFleet: FleetResult; let miningFleet: FleetResult; let respawnFleet: FleetResult; let crewFleet: FleetResult; let renterStarbasePlayer: PublicKey; let renterStarbaseCargoPod: PublicKey; let sduTokenFrom: PublicKey; let sduSector: PublicKey; const sduTracker = keypairToAsyncSigner(Keypair.generate()); const miningAsteroid = keypairToAsyncSigner(Keypair.generate()); let mineItemKey: PublicKey; let resourceKey: PublicKey; let sageGameTest: SageGameTest; const getStarbasePlayerAddress = (theProfile: SageGameTestProfile) => { if (!sageGameTest.sectors) { throw 'sageGameTest not set up correctly'; } if (theProfile.profile.equals(sageGameTest.profiles.others[0].profile)) { return renterStarbasePlayer; } return sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer; }; const setupSDUTracker = async (bankAmount = 10000) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const sduSigner = SurveyDataUnitTracker.findSignerAddress( sageGameTest.program, sduTracker.publicKey(), )[0]; const sduBankToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.SDU], sduSigner, true, ); sduTokenFrom = sduBankToken.address; const instructions: InstructionReturn[] = [ SurveyDataUnitTracker.registerSurveyDataUnitTracker( sageGameTest.program, sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, sduTracker, sageGameTest.mints[SageGameTest.SDU], sageGameTest.mints[SageGameTest.FOOD], sageGameTest.gameId, { ...SageGameTest.getDefaultSDUTrackerStats(), keyIndex: sageGameTest.profiles.superuser.index, }, ), ]; instructions.push(sduBankToken.instructions); instructions.push( mintToTokenAccount( sageGameTest.profiles.superuser.key, sageGameTest.mints[SageGameTest.SDU], sduTokenFrom, bankAmount, ), ); await sageGameTest.sendToChain(instructions); }; const setupStarpath = async () => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } await sageGameTest.createSector({ coordinates: toSector, faction: SageGameTest.default_faction, addResourcesToStarbaseCargoPod: false, setUpPlayer: false, }); await sageGameTest.sendToChain([ Sector.addConnection( sageGameTest.program, sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, sageGameTest.sectors[0].sector, sageGameTest.sectors[1].sector, sageGameTest.gameId, originalSector, { isJumpPoint: true, }, toSector, { isJumpPoint: true, }, sageGameTest.profiles.superuser.index, ), ]); }; const setupPlayerAtStarbase = async ( theProfile: SageGameTestProfile, starbaseKey: PublicKey, cargoAmt = 10000, starbaseSeqId = 0, crew = 1, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const sagePlayerProfile = SagePlayerProfile.findAddress( sageGameTest.program, theProfile.profile, sageGameTest.gameId, )[0]; renterStarbasePlayer = StarbasePlayer.findAddress( sageGameTest.program, starbaseKey, sagePlayerProfile, starbaseSeqId, )[0]; const podSeeds = Keypair.generate().publicKey.toBuffer(); renterStarbaseCargoPod = CargoPod.findAddress( sageGameTest.cargoProgram, podSeeds, )[0]; const cargoTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], theProfile.key.publicKey(), true, ); const cargoTokenTo = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], renterStarbaseCargoPod, true, ); await sageGameTest.sendToChain([ SagePlayerProfile.registerSagePlayerProfile( sageGameTest.program, theProfile.profile, sageGameTest.gameId, sageGameTest.gameState, ), StarbasePlayer.registerStarbasePlayer( sageGameTest.program, sageGameTest.getProfileFactionAddress(theProfile.profile), sagePlayerProfile, starbaseKey, sageGameTest.gameId, sageGameTest.gameState, starbaseSeqId, ), StarbasePlayer.createCargoPod( sageGameTest.program, sageGameTest.cargoProgram, renterStarbasePlayer, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), starbaseKey, sageGameTest.cargoStatsDefinition, sageGameTest.gameId, sageGameTest.gameState, { keyIndex: theProfile.index, podSeeds: Array.from(podSeeds), }, ), ]); await sageGameTest.sendToChain([ cargoTokenFrom.instructions, cargoTokenTo.instructions, mintToTokenAccount( sageGameTest.profiles.superuser.key, sageGameTest.mints[SageGameTest.FUEL], cargoTokenFrom.address, cargoAmt, ), StarbasePlayer.depositCargoToGame( sageGameTest.program, sageGameTest.cargoProgram, renterStarbasePlayer, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), starbaseKey, renterStarbaseCargoPod, sageGameTest.getCargoTypeAddress(sageGameTest.mints[SageGameTest.FUEL]), sageGameTest.cargoStatsDefinition, cargoTokenFrom.address, cargoTokenTo.address, sageGameTest.gameId, sageGameTest.gameState, { amount: new BN(cargoAmt), keyIndex: theProfile.index, }, ), ]); if (crew > 0) { if (!sageGameTest.crew) { throw 'game not setup correctly'; } await sageGameTest.mintAndAddCrewToGame( sageGameTest.crew.crewMerkleTree, theProfile.key.publicKey(), starbaseKey, renterStarbasePlayer, crew, undefined, [], theProfile, ); } }; const setupMining = async () => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const mineItemResult = MineItem.registerMineItem( sageGameTest.program, sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, sageGameTest.mints[SageGameTest.INGREDIENT2], sageGameTest.gameId, { keyIndex: sageGameTest.profiles.superuser.index, name: stringToByteArray('🤖 mineral', 64), resourceHardness: 100, }, ); mineItemKey = mineItemResult.mineItemKey[0]; const resourceResult = Resource.registerResource( sageGameTest.program, sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, miningAsteroid.publicKey(), mineItemKey, sageGameTest.gameId, { systemRichness: 250, keyIndex: sageGameTest.profiles.superuser.index, locationType: LocationType.Planet, }, ); resourceKey = resourceResult.resourceKey[0]; await sageGameTest.sendManyToChain([ Planet.registerPlanet( sageGameTest.program, sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, miningAsteroid, sageGameTest.sectors[0].sector, sageGameTest.gameId, { name: '🤖 planet', size: new BN(133_337), maxHp: new BN(1000_000_000), subCoordinates: [new BN(2), new BN(2)], planetType: PlanetType.AsteroidBelt, position: SectorRing.Inner, keyIndex: sageGameTest.profiles.superuser.index, }, ), mineItemResult.instructions, resourceResult.instructions, ]); }; const depositCargoToFleet = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, amount = 20, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const cargoPodFuelToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], renterStarbaseCargoPod, true, ); const fleetCargoHoldFuelToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.cargoHoldKey, true, ); try { await sageGameTest.sendToChain([ cargoPodFuelToken.instructions, fleetCargoHoldFuelToken.instructions, Fleet.depositCargoToFleet( sageGameTest.program, sageGameTest.cargoProgram, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), 'funder', sageGameTest.sectors[0].starbases[0], getStarbasePlayerAddress(theProfile), fleetResult.fleetKey, renterStarbaseCargoPod, fleetResult.cargoHoldKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.FUEL], ), sageGameTest.cargoStatsDefinition, cargoPodFuelToken.address, fleetCargoHoldFuelToken.address, sageGameTest.mints[SageGameTest.FUEL], sageGameTest.gameId, sageGameTest.gameState, { keyIndex: theProfile.index, amount: new BN(amount), }, ), ]); return true; } catch { return false; } }; const withdrawCargoFromFleet = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, amount = 7, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const cargoPodFuelToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], renterStarbaseCargoPod, true, ); const fleetCargoHoldFuelToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.cargoHoldKey, true, ); try { await sageGameTest.sendToChain([ cargoPodFuelToken.instructions, fleetCargoHoldFuelToken.instructions, Fleet.withdrawCargoFromFleet( sageGameTest.program, sageGameTest.cargoProgram, theProfile.key, 'funder', theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), sageGameTest.sectors[0].starbases[0], getStarbasePlayerAddress(theProfile), fleetResult.fleetKey, fleetResult.cargoHoldKey, renterStarbaseCargoPod, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.FUEL], ), sageGameTest.cargoStatsDefinition, fleetCargoHoldFuelToken.address, cargoPodFuelToken.address, sageGameTest.mints[SageGameTest.FUEL], sageGameTest.gameId, sageGameTest.gameState, { keyIndex: theProfile.index, amount: new BN(amount), }, ), ]); return true; } catch { return false; } }; const transferCargoWithinFleet = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, amount = 5, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const fleetFuelTankFuelToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.fuelTankKey, true, ); const fleetCargoHoldFuelToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.cargoHoldKey, true, ); try { await sageGameTest.sendToChain([ fleetFuelTankFuelToken.instructions, fleetCargoHoldFuelToken.instructions, Fleet.transferCargoWithinFleet( sageGameTest.program, sageGameTest.cargoProgram, theProfile.key, 'funder', theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, fleetResult.cargoHoldKey, fleetResult.fuelTankKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.FUEL], ), sageGameTest.cargoStatsDefinition, fleetCargoHoldFuelToken.address, fleetFuelTankFuelToken.address, sageGameTest.mints[SageGameTest.FUEL], sageGameTest.gameId, sageGameTest.gameState, { keyIndex: theProfile.index, amount: new BN(amount), }, sageGameTest.sectors[0].starbases[0], ), ]); return true; } catch { return false; } }; const closeFleetCargoPodTokenAccount = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const fleetCargoHoldToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.AMMO], fleetResult.cargoHoldKey, true, ); try { await sageGameTest.sendToChain([ fleetCargoHoldToken.instructions, Fleet.closeFleetCargoPodTokenAccount( sageGameTest.program, sageGameTest.cargoProgram, theProfile.key, 'funder', theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, fleetResult.cargoHoldKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.AMMO], ), sageGameTest.cargoStatsDefinition, fleetCargoHoldToken.address, sageGameTest.mints[SageGameTest.AMMO], sageGameTest.gameId, sageGameTest.gameState, { keyIndex: theProfile.index, }, ), ]); return true; } catch { return false; } }; const undockThenDock = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } try { await sageGameTest.sendToChain([ Fleet.loadingBayToIdle( sageGameTest.program, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, sageGameTest.sectors[0].starbases[0], getStarbasePlayerAddress(theProfile), sageGameTest.gameId, sageGameTest.gameState, theProfile.index, ), Fleet.idleToLoadingBay( sageGameTest.program, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, sageGameTest.sectors[0].starbases[0], getStarbasePlayerAddress(theProfile), sageGameTest.gameId, sageGameTest.gameState, theProfile.index, ), ]); return true; } catch { return false; } }; const respawn = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, feeWalletAmount = 2000, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const feeTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.ATLAS], theProfile.key.publicKey(), true, ); const feeTokenTo = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.ATLAS], sageGameTest.profiles.superuser.key.publicKey(), true, ); try { await sageGameTest.sendToChain([ feeTokenFrom.instructions, feeTokenTo.instructions, mintToTokenAccount( sageGameTest.profiles.superuser.key, sageGameTest.mints[SageGameTest.ATLAS], feeTokenFrom.address, feeWalletAmount, ), Fleet.idleToRespawn( sageGameTest.program, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, feeTokenFrom.address, feeTokenTo.address, sageGameTest.gameState, sageGameTest.gameId, { keyIndex: theProfile.index }, ), ]); return true; } catch { return false; } }; const disband = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const fleetAccount = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleetResult.fleetKey, Fleet, 'confirmed', ); const disbandResult = Fleet.disbandFleet( sageGameTest.program, sageGameTest.cargoProgram, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetAccount, getStarbasePlayerAddress(theProfile), sageGameTest.sectors[0].starbases[0], sageGameTest.gameId, sageGameTest.gameState, { keyIndex: theProfile.index }, ); try { await sageGameTest.sendToChain([disbandResult.instructions]); return true; } catch { return false; } }; const scanForSDUs = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.pointCategories || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const fleetSDUToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.SDU], fleetResult.cargoHoldKey, true, ); const fleetResourceToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FOOD], fleetResult.cargoHoldKey, true, ); await sageGameTest.sendManyToChain([ fleetSDUToken.instructions, fleetResourceToken.instructions, ]); try { await sageGameTest.sendManyToChain([ SurveyDataUnitTracker.scanForSurveyDataUnits( sageGameTest.program, sageGameTest.cargoProgram, sageGameTest.pointsProgram, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, sduSector, sduTracker.publicKey(), fleetResult.cargoHoldKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.SDU], ), sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.FOOD], ), sageGameTest.cargoStatsDefinition, sduTokenFrom, fleetSDUToken.address, fleetResourceToken.address, sageGameTest.mints[SageGameTest.FOOD], sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.dataRunningXpCategory, theProfile.profile, ), sageGameTest.pointCategories.dataRunningXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.dataRunningXpCategory, ), sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.councilRankXpCategory, theProfile.profile, ), sageGameTest.pointCategories.councilRankXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.councilRankXpCategory, ), sageGameTest.gameId, sageGameTest.gameState, { keyIndex: theProfile.index, }, ), ]); return true; } catch { return false; } }; const startWarp = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, toSector: BN[], ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const fuelTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.fuelTankKey, true, ); try { await sageGameTest.sendToChain([ fuelTokenFrom.instructions, Fleet.warpToCoordinate( sageGameTest.program, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, fleetResult.fuelTankKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.FUEL], ), sageGameTest.cargoStatsDefinition, fuelTokenFrom.address, sageGameTest.mints[SageGameTest.FUEL], sageGameTest.gameState, sageGameTest.gameId, sageGameTest.cargoProgram, { keyIndex: theProfile.index, toSector, }, ), ]); return true; } catch { return false; } }; const startWarpLane = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const feeWalletAmount = 1000000; const fuelTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.fuelTankKey, true, ); const feeTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.ATLAS], theProfile.key.publicKey(), true, ); const feeTokenTo = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.ATLAS], sageGameTest.profiles.superuser.key.publicKey(), true, ); try { await sageGameTest.sendToChain([ fuelTokenFrom.instructions, feeTokenFrom.instructions, feeTokenTo.instructions, mintToTokenAccount( sageGameTest.profiles.superuser.key, sageGameTest.mints[SageGameTest.ATLAS], feeTokenFrom.address, feeWalletAmount, ), Fleet.warpLane( sageGameTest.program, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[1].starbases[0], sageGameTest.sectors[0].sector, sageGameTest.sectors[1].sector, fleetResult.fuelTankKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.FUEL], ), sageGameTest.cargoStatsDefinition, fuelTokenFrom.address, sageGameTest.mints[SageGameTest.FUEL], feeTokenFrom.address, feeTokenTo.address, sageGameTest.mints[SageGameTest.ATLAS], sageGameTest.gameState, sageGameTest.gameId, sageGameTest.cargoProgram, { keyIndex: theProfile.index, toSectorIndex: 0, fromSectorIndex: 0, }, ), ]); return true; } catch { return false; } }; const stopWarp = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.pointCategories || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const fuelTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.cargoHoldKey, true, ); try { await sageGameTest.sendManyToChain([ fuelTokenFrom.instructions, Fleet.moveWarpHandler( sageGameTest.program, sageGameTest.pointsProgram, theProfile.profile, fleetResult.fleetKey, sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.pilotXpCategory, theProfile.profile, ), sageGameTest.pointCategories.pilotXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.pilotXpCategory, ), sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.councilRankXpCategory, theProfile.profile, ), sageGameTest.pointCategories.councilRankXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.councilRankXpCategory, ), sageGameTest.gameId, ), ]); return true; } catch { return false; } }; const startSubwarp = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, toSector: BN[], ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const fuelTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.fuelTankKey, true, ); try { await sageGameTest.sendToChain([ fuelTokenFrom.instructions, Fleet.startSubwarp( sageGameTest.program, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, sageGameTest.gameId, sageGameTest.gameState, { keyIndex: theProfile.index, toSector, }, ), ]); return true; } catch { return false; } }; const stopSubwarp = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.pointCategories || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const fuelTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.fuelTankKey, true, ); try { await sageGameTest.sendToChain([ fuelTokenFrom.instructions, Fleet.stopSubwarp( sageGameTest.program, sageGameTest.cargoProgram, sageGameTest.pointsProgram, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, fleetResult.fuelTankKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.FUEL], ), sageGameTest.cargoStatsDefinition, fuelTokenFrom.address, sageGameTest.mints[SageGameTest.FUEL], sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.pilotXpCategory, theProfile.profile, ), sageGameTest.pointCategories.pilotXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.pilotXpCategory, ), sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.councilRankXpCategory, theProfile.profile, ), sageGameTest.pointCategories.councilRankXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.councilRankXpCategory, ), sageGameTest.gameId, sageGameTest.gameState, { keyIndex: theProfile.index, }, ), ]); return true; } catch { return false; } }; const startMining = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const fleetFuelToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.fuelTankKey, true, ); try { await sageGameTest.sendManyToChain([ fleetFuelToken.instructions, Fleet.startMiningAsteroid( sageGameTest.program, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, sageGameTest.sectors[0].starbases[0], getStarbasePlayerAddress(theProfile), mineItemKey, resourceKey, miningAsteroid.publicKey(), sageGameTest.gameState, sageGameTest.gameId, fleetFuelToken.address, { keyIndex: theProfile.index, }, ), ]); return true; } catch { return false; } }; const stopMining = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, resourceAmt = 5000, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.pointCategories || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const mineItemAccount = await readFromRPCOrError( connection, sageGameTest.program, mineItemKey, MineItem, ); const fleetFoodToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FOOD], fleetResult.cargoHoldKey, true, ); const fleetAmmoToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.AMMO], fleetResult.ammoBankKey, true, ); const fleetFuelToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.fuelTankKey, true, ); const resourceTokenFrom = createAssociatedTokenAccountIdempotent( mineItemAccount.data.mint, mineItemKey, true, ); const resourceTokenTo = createAssociatedTokenAccountIdempotent( mineItemAccount.data.mint, fleetResult.cargoHoldKey, true, ); try { await sageGameTest.sendToChain([ fleetFoodToken.instructions, fleetAmmoToken.instructions, resourceTokenFrom.instructions, resourceTokenTo.instructions, mintToTokenAccount( sageGameTest.profiles.superuser.key, mineItemAccount.data.mint, resourceTokenFrom.address, resourceAmt, ), Fleet.asteroidMiningHandler( sageGameTest.program, sageGameTest.cargoProgram, fleetResult.fleetKey, sageGameTest.sectors[0].starbases[0], mineItemKey, resourceKey, miningAsteroid.publicKey(), fleetResult.cargoHoldKey, fleetResult.ammoBankKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.FOOD], ), sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.AMMO], ), sageGameTest.getCargoTypeAddress( mineItemAccount.data.mint, ) /** the thing being mined is this */, sageGameTest.cargoStatsDefinition, sageGameTest.gameState, sageGameTest.gameId, fleetFoodToken.address, fleetAmmoToken.address, resourceTokenFrom.address, resourceTokenTo.address, sageGameTest.mints[SageGameTest.FOOD], sageGameTest.mints[SageGameTest.AMMO], ), ]); await sageGameTest.sendToChain([ fleetFuelToken.instructions, Fleet.stopMiningAsteroid( sageGameTest.program, sageGameTest.cargoProgram, sageGameTest.pointsProgram, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, mineItemKey, resourceKey, miningAsteroid.publicKey(), fleetResult.fuelTankKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.FUEL], ), sageGameTest.cargoStatsDefinition, sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.miningXpCategory, theProfile.profile, ), sageGameTest.pointCategories.miningXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.miningXpCategory, ), sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.pilotXpCategory, theProfile.profile, ), sageGameTest.pointCategories.pilotXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.pilotXpCategory, ), sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.councilRankXpCategory, theProfile.profile, ), sageGameTest.pointCategories.councilRankXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.councilRankXpCategory, ), sageGameTest.gameState, sageGameTest.gameId, fleetFuelToken.address, sageGameTest.mints[SageGameTest.FUEL], { keyIndex: theProfile.index }, ), ]); return true; } catch { return false; } }; const addRemoveFleetCrew = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, load = true, unload = true, crewCount = 1, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const instructions = load ? [ Fleet.loadFleetCrew( sageGameTest.program, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, sageGameTest.sectors[0].starbases[0], getStarbasePlayerAddress(theProfile), sageGameTest.gameId, { keyIndex: theProfile.index, count: crewCount, }, ), ] : []; if (unload) { instructions.push( Fleet.unloadFleetCrew( sageGameTest.program, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, sageGameTest.sectors[0].starbases[0], getStarbasePlayerAddress(theProfile), sageGameTest.gameId, { keyIndex: theProfile.index, count: crewCount, }, ), ); } try { await sageGameTest.sendManyToChain(instructions, true); return true; } catch { return false; } }; beforeAll(async () => { const airdropTx = await connection.requestAirdrop( walletSigner.publicKey(), 500_000_000_000, ); await connection.confirmTransaction(airdropTx); sageGameTest = await SageGameTest.createGame({ funder: walletKeypair, numCrewForPlayer: 20, chunkCrewInstructions: 4, connection, }); // do some set-up await setupSDUTracker(); await setupStarpath(); await setupMining(); await sageGameTest.setupPlayerProgressionForPlayer( sageGameTest.profiles.others[0].profile, ); // create fleets for tests if (!sageGameTest.sectors) { throw 'sageGameTest.sectors not set up correctly'; } const sectorData = sageGameTest.sectors[0]; const starBase = sectorData.starbases[0]; const starBasePlayerData = sectorData.starbasePlayers[0]; await Promise.all([ setupPlayerAtStarbase(sageGameTest.profiles.others[0], starBase), ]); const fleetsIndicesToCargo: { [key: number]: { [key: string]: number; }; } = { 1: { [SageGameTest.AMMO]: 500, [SageGameTest.FOOD]: 1000, [SageGameTest.FUEL]: 7000, [SageGameTest.REPAIR_KIT]: 500, }, }; for (let i = 2; i <= numFleets - 1; i++) { fleetsIndicesToCargo[i] = { ...fleetsIndicesToCargo[1] }; } fleetResults.push( ...(await sageGameTest.createManyFleets({ starbase: starBase, starbasePlayer: starBasePlayerData.starbasePlayer, numFleets, fleetsIndicesToStayDocked: [0, 9], fleetsIndicesToCargo, })), ); expect(fleetResults.length).toBe(numFleets); cargoFleet = fleetResults[0]; sduFleet = fleetResults[1]; warpFleet = fleetResults[2]; warpFleet2 = fleetResults[3]; warpFleet3 = fleetResults[4]; warpFleet4 = fleetResults[5]; subwarpFleet = fleetResults[6]; miningFleet = fleetResults[7]; respawnFleet = fleetResults[8]; crewFleet = fleetResults[9]; sduSector = sectorData.sector; // start some fleet actions that the renter will later stop const startFleetActions = await Promise.all([ startWarp(sageGameTest.profiles.player1, warpFleet, toSector), startWarpLane(sageGameTest.profiles.player1, warpFleet2), startSubwarp(sageGameTest.profiles.player1, subwarpFleet, toSector), addRemoveFleetCrew( sageGameTest.profiles.player1, crewFleet, false, true, 1, ) /** ensure no rented crew */, ]); expect(startFleetActions.every((element) => element === true)).toBe(true); // wait for fleet action duration const warpFleetFleetAccount = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, warpFleet.fleetKey, Fleet, 'confirmed', ); const expectedTravelTime = Math.floor( Fleet.calculateWarpTimeWithCoords( warpFleetFleetAccount.data.stats, originalSector, toSector, ), ); const warpCooldownTime = (expectedTravelTime + (warpFleetFleetAccount.data.stats).movementStats .warpCoolDown) * 1000; await new Promise((f) => setTimeout(f, warpCooldownTime)); }); it('add rental', async () => { if ( !sageGameTest.gameState || !sageGameTest.fleets || sageGameTest.profiles.others.length < 2 ) { throw 'game not set up correctly'; } for (let index = 0; index < fleetResults.length; index++) { const fleetResult = fleetResults[index]; const fleetAccountBefore = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleetResult.fleetKey, Fleet, 'confirmed', ); await sageGameTest.sendToChain( Fleet.addRental( sageGameTest.program, sageGameTest.profiles.player1.key, invalidator1, sageGameTest.profiles.player1.profile, fleetResult.fleetKey, sageGameTest.gameId, sageGameTest.profiles.player1.index, ), ); const fleetAccountAfter0 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleetResult.fleetKey, Fleet, 'confirmed', ); expect( fleetAccountAfter0.data.subProfile.key.equals( sageGameTest.program.programId, ), ).toBe(true); expect( fleetAccountAfter0.data.subProfileInvalidator.equals( invalidator1.publicKey(), ), ).toBe(true); await sageGameTest.sendToChain( Fleet.changeRental( sageGameTest.program, sageGameTest.profileFactionProgram, invalidator1, sageGameTest.profiles.others[0].profile, fleetResult.fleetKey, sageGameTest.gameId, ), ); const fleetAccountAfter = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleetResult.fleetKey, Fleet, 'confirmed', ); expect( fleetAccountAfter.data.subProfile.key.equals( sageGameTest.profiles.others[0].profile, ), ).toBe(true); expect( fleetAccountAfter.data.subProfileInvalidator.equals( invalidator1.publicKey(), ), ).toBe(true); expect( (fleetAccountBefore.data.stats).miscStats.crewCount, ).toBe((fleetAccountAfter.data.stats).miscStats.rentedCrew); expect( (fleetAccountBefore.data.stats).miscStats.crewCount, ).toBe((fleetAccountAfter.data.stats).miscStats.crewCount); } }); it('use rented fleet', async () => { if ( !sageGameTest.gameState || !sageGameTest.fleets || sageGameTest.profiles.others.length < 2 ) { throw 'game not set up correctly'; } // profile that rented can use fleet const successfulTxs = await Promise.all([ scanForSDUs(sageGameTest.profiles.others[0], sduFleet), startWarpLane(sageGameTest.profiles.others[0], warpFleet3), depositCargoToFleet(sageGameTest.profiles.others[0], cargoFleet), stopWarp(sageGameTest.profiles.others[0], warpFleet), stopWarp(sageGameTest.profiles.others[0], warpFleet2), stopSubwarp(sageGameTest.profiles.others[0], subwarpFleet), startMining(sageGameTest.profiles.others[0], miningFleet), addRemoveFleetCrew( sageGameTest.profiles.others[0], crewFleet, true, true, 1, ) /** add and remove 1 required crew to back to 0 */, ]).then((firstResults) => Promise.all([ transferCargoWithinFleet(sageGameTest.profiles.others[0], cargoFleet), withdrawCargoFromFleet(sageGameTest.profiles.others[0], cargoFleet), closeFleetCargoPodTokenAccount( sageGameTest.profiles.others[0], cargoFleet, ), undockThenDock(sageGameTest.profiles.others[0], cargoFleet), ]).then((secondResults) => [...firstResults, ...secondResults]), ); expect(successfulTxs.every((element) => element === true)).toBe(true); // original owner cannot use fleet (this is for instructions that must be called before `successfulTxs1`) const failedTxs = await Promise.all([ startWarp(sageGameTest.profiles.player1, warpFleet2, toSector), startSubwarp(sageGameTest.profiles.player1, subwarpFleet, toSector), startWarpLane(sageGameTest.profiles.player1, warpFleet4), transferCargoWithinFleet(sageGameTest.profiles.player1, cargoFleet), withdrawCargoFromFleet(sageGameTest.profiles.player1, cargoFleet), closeFleetCargoPodTokenAccount(sageGameTest.profiles.player1, cargoFleet), undockThenDock(sageGameTest.profiles.player1, cargoFleet), stopMining(sageGameTest.profiles.player1, miningFleet), respawn(sageGameTest.profiles.player1, respawnFleet), disband(sageGameTest.profiles.player1, cargoFleet), addRemoveFleetCrew( sageGameTest.profiles.player1, crewFleet, true, false, ) /** cant add */, addRemoveFleetCrew( sageGameTest.profiles.player1, cargoFleet, false, true, ) /** cant remove */, addRemoveFleetCrew( sageGameTest.profiles.player1, cargoFleet, ) /** passengers */, ]); expect(failedTxs.every((element) => element === false)).toBe(true); // profile that rented can use fleet (transactions that cannot be called at the same time as `successfulTxs`) const successfulTxs1 = await Promise.all([ startWarp(sageGameTest.profiles.others[0], warpFleet2, toSector), startSubwarp(sageGameTest.profiles.others[0], subwarpFleet, toSector), stopMining(sageGameTest.profiles.others[0], miningFleet), respawn(sageGameTest.profiles.others[0], respawnFleet), addRemoveFleetCrew( sageGameTest.profiles.others[0], cargoFleet, ) /** passengers */, ]); expect(successfulTxs1.every((element) => element === true)).toBe(true); // original owner still cannot use fleet const failedTxs1 = await Promise.all([ scanForSDUs(sageGameTest.profiles.player1, sduFleet), stopSubwarp(sageGameTest.profiles.player1, subwarpFleet), startMining(sageGameTest.profiles.player1, miningFleet), ]); expect(failedTxs1.every((element) => element === false)).toBe(true); // profile that rented cannot do these const legitFailedTxs = await Promise.all([ disband(sageGameTest.profiles.others[0], cargoFleet), addRemoveFleetCrew( sageGameTest.profiles.others[0], cargoFleet, false, true, ) /** can't remove rented crew */, ]); expect(legitFailedTxs.every((element) => element === false)).toBe(true); }); it('invalidate rental with excess crew', async () => { if ( !sageGameTest.gameState || !sageGameTest.fleets || !sageGameTest.sectors || sageGameTest.profiles.others.length < 2 ) { throw 'game not set up correctly'; } const excessCrewCount = 1; const fleetAccountBefore = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, crewFleet.fleetKey, Fleet, 'confirmed', ); const renterStarbasePlayerKey = getStarbasePlayerAddress( sageGameTest.profiles.others[0], ); const renterStarbasePlayer0 = await sageGameTest.loadStarbasePlayer( renterStarbasePlayerKey, ); await sageGameTest.sendToChain([ Fleet.loadFleetCrew( sageGameTest.program, sageGameTest.profiles.others[0].key, sageGameTest.profiles.others[0].profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.others[0].profile, ), crewFleet.fleetKey, sageGameTest.sectors[0].starbases[0], renterStarbasePlayerKey, sageGameTest.gameId, { keyIndex: sageGameTest.profiles.others[0].index, count: excessCrewCount, }, ), ]); const renterStarbasePlayer1 = await sageGameTest.loadStarbasePlayer( renterStarbasePlayerKey, ); const fleetAccountAfter0 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, crewFleet.fleetKey, Fleet, 'confirmed', ); // starbase crew was decremented expect(renterStarbasePlayer0.totalCrew()).toBe( renterStarbasePlayer1.totalCrew() + excessCrewCount, ); expect( (fleetAccountBefore.data.stats).miscStats.crewCount + excessCrewCount, ).toBe((fleetAccountAfter0.data.stats).miscStats.crewCount); await sageGameTest.sendToChain([ Fleet.invalidateRental( sageGameTest.program, invalidator1, crewFleet.fleetKey, true, sageGameTest.gameId, sageGameTest.sectors[0].starbases[0], renterStarbasePlayerKey, ), ]); const renterStarbasePlayer2 = await sageGameTest.loadStarbasePlayer( renterStarbasePlayerKey, ); const fleetAccountAfter = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, crewFleet.fleetKey, Fleet, 'confirmed', ); // starbase crew was incremented expect(renterStarbasePlayer2.totalCrew()).toBe( renterStarbasePlayer1.totalCrew() + excessCrewCount, ); // but overall nothing changed expect(renterStarbasePlayer0.totalCrew()).toBe( renterStarbasePlayer2.totalCrew(), ); expect( fleetAccountAfter.data.subProfile.key.equals(PublicKey.default), ).toBe(true); expect( fleetAccountAfter.data.subProfileInvalidator.equals( fleetAccountAfter.data.ownerProfile, ), ).toBe(true); // fleet crew count was unchanged expect((fleetAccountBefore.data.stats).miscStats.crewCount).toBe( (fleetAccountAfter.data.stats).miscStats.crewCount, ); expect((fleetAccountBefore.data.stats).miscStats.crewCount).toBe( (fleetAccountAfter.data.stats).miscStats.rentedCrew, ); }); it('change rental', async () => { if ( !sageGameTest.gameState || !sageGameTest.fleets || sageGameTest.profiles.others.length < 2 ) { throw 'game not set up correctly'; } for (let index = 0; index < fleetResults.length; index++) { const fleetResult = fleetResults[index]; if (!fleetResult.fleetKey.equals(crewFleet.fleetKey)) { await sageGameTest.sendToChain( Fleet.changeRental( sageGameTest.program, sageGameTest.profileFactionProgram, invalidator1, sageGameTest.profiles.others[1].profile, fleetResult.fleetKey, sageGameTest.gameId, ), ); const fleetAccountAfter = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleetResult.fleetKey, Fleet, 'confirmed', ); expect( fleetAccountAfter.data.subProfile.key.equals( sageGameTest.profiles.others[1].profile, ), ).toBe(true); expect( fleetAccountAfter.data.subProfileInvalidator.equals( invalidator1.publicKey(), ), ).toBe(true); } } }); it('invalidate rental with no excess crew', async () => { if ( !sageGameTest.gameState || !sageGameTest.fleets || !sageGameTest.sectors || sageGameTest.profiles.others.length < 2 ) { throw 'game not set up correctly'; } for (let index = 0; index < fleetResults.length; index++) { const fleetResult = fleetResults[index]; const fleetAccountBefore = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleetResult.fleetKey, Fleet, 'confirmed', ); if (!fleetResult.fleetKey.equals(crewFleet.fleetKey)) { await sageGameTest.sendToChain( Fleet.invalidateRental( sageGameTest.program, invalidator1, fleetResult.fleetKey, true, ), ); const fleetAccountAfter = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleetResult.fleetKey, Fleet, 'confirmed', ); expect( fleetAccountAfter.data.subProfile.key.equals(PublicKey.default), ).toBe(true); expect( fleetAccountAfter.data.subProfileInvalidator.equals( fleetAccountAfter.data.ownerProfile, ), ).toBe(true); // fleet crew count unchanged expect( (fleetAccountBefore.data.stats).miscStats.crewCount, ).toBe((fleetAccountAfter.data.stats).miscStats.crewCount); } } }); it('invalidate rental without removing invalidator', async () => { if ( !sageGameTest.gameState || !sageGameTest.fleets || !sageGameTest.sectors || sageGameTest.profiles.others.length < 2 ) { throw 'game not set up correctly'; } const fleetResult = fleetResults[0]; await sageGameTest.sendManyToChain([ Fleet.addRental( sageGameTest.program, sageGameTest.profiles.player1.key, invalidator1, sageGameTest.profiles.player1.profile, fleetResult.fleetKey, sageGameTest.gameId, sageGameTest.profiles.player1.index, ), Fleet.changeRental( sageGameTest.program, sageGameTest.profileFactionProgram, invalidator1, sageGameTest.profiles.others[0].profile, fleetResult.fleetKey, sageGameTest.gameId, ), Fleet.invalidateRental( sageGameTest.program, invalidator1, fleetResult.fleetKey, false, ), ]); const fleetAccountAfter = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleetResult.fleetKey, Fleet, 'confirmed', ); expect( fleetAccountAfter.data.subProfile.key.equals( sageGameTest.program.programId, ), ).toBe(true); expect( fleetAccountAfter.data.subProfileInvalidator.equals( invalidator1.publicKey(), ), ).toBe(true); // now remove invalidator await sageGameTest.sendManyToChain([ Fleet.invalidateRental( sageGameTest.program, invalidator1, fleetResult.fleetKey, true, ), ]); const fleetAccountAfter1 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleetResult.fleetKey, Fleet, 'confirmed', ); expect( fleetAccountAfter1.data.subProfileInvalidator.equals( fleetAccountAfter1.data.ownerProfile, ), ).toBe(true); }); });