import { Account, getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID, } from '@solana/spl-token'; import { Connection, Keypair, PublicKey, SystemProgram } from '@solana/web3.js'; import { BN } from '@staratlas/anchor'; import { CraftingFacility, CraftingFacilityLocationType, CraftingProcess, CraftingProcessStatus, Recipe, } from '@staratlas/crafting'; import { arrayDeepEquals, createAssociatedTokenAccountIdempotent, getNumberFromEnum, getParsedTokenAccountsByOwner, keypairToAsyncSigner, readFromRPCNullable, readFromRPCOrError, } from '@staratlas/data-source'; import { UserPoints } from '@staratlas/points'; import { UserRedemption } from '@staratlas/points-store'; import { Faction } from '@staratlas/profile-faction'; import { CraftingInstance, craftingInstanceDataEquals, CraftingInstanceType, FactionsStarbaseLevelInfo, Fleet, ProgressionItemType, ShipStats, Starbase, StarbaseUpgradeState, StarbaseUpkeepLevels, UpdateFleetDataInput, UpkeepResourceType, } from '../src'; import { SageGameTest, SageGameTestSector } from './helpers'; describe('Starbase Upgrade', () => { const connection = new Connection('http://localhost:8899', 'confirmed'); const walletKeypair = Keypair.generate(); const walletSigner = keypairToAsyncSigner(walletKeypair); const craftingFacility = keypairToAsyncSigner(Keypair.generate()); const upgradeFacility = keypairToAsyncSigner(Keypair.generate()); let recipeForStarbaseUpgradeItself: Recipe; const craftingInstances: CraftingInstance[] = []; const craftingProcessKeys: PublicKey[] = []; let toolkitRecipe: PublicKey; let ingredient1Recipe: PublicKey; let ingredient2Recipe: PublicKey; const toolkitRecipeValue = 0.6; const ingredient1RecipeValue = 0.8; const ingredient2RecipeValue = 0.7; const INGREDIENT1_QTY = Math.floor( SageGameTest.defaultUpgradeMultiplier * 0.6, ); const INGREDIENT2_QTY = Math.floor( SageGameTest.defaultUpgradeMultiplier * 0.5, ); const redemptionConfig = keypairToAsyncSigner(Keypair.generate()); const userRedemption = keypairToAsyncSigner(Keypair.generate()); const CREW_COUNT = 32; let sageGameTest: SageGameTest; const depositResourceAndStartProcess = async ( craftingInstance: CraftingInstance, recipeForResourceSubmission: Recipe, theSector: SageGameTestSector, ) => { const craftingProcessAcct = await readFromRPCOrError( connection, sageGameTest.craftingProgram, craftingInstance.data.craftingProcess, CraftingProcess, 'confirmed', ); const instructions = []; for ( let ingredientIndex = 0; ingredientIndex < recipeForResourceSubmission.ingredientInputsOutputs.length; ingredientIndex++ ) { const recipeIngredient = recipeForResourceSubmission.ingredientInputsOutputs[ingredientIndex]; const tokenFrom = createAssociatedTokenAccountIdempotent( recipeIngredient.mint, theSector.starbasePlayers[0].cargoPod, true, ); const tokenTo = createAssociatedTokenAccountIdempotent( recipeIngredient.mint, craftingInstance.data.craftingProcess, true, ); const depositIx = Starbase.depositStarbaseUpgradeResourceIngredient( sageGameTest.program, sageGameTest.cargoProgram, sageGameTest.craftingProgram, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), theSector.starbasePlayers[0].starbasePlayer, theSector.starbases[0], craftingInstance.key, craftingInstance.data.craftingProcess, upgradeFacility.publicKey(), recipeForResourceSubmission.key, theSector.starbasePlayers[0].cargoPod, sageGameTest.getCargoTypeAddress(recipeIngredient.mint), sageGameTest.cargoStatsDefinition as PublicKey, tokenFrom.address, tokenTo.address, sageGameTest.gameId, sageGameTest.gameState as PublicKey, { keyIndex: sageGameTest.profiles.player1.index, ingredientIndex, amount: craftingProcessAcct.data.quantity.mul( recipeIngredient.amount, ), }, ); instructions.push( ...[tokenFrom.instructions, tokenTo.instructions, depositIx], ); } instructions.push( Starbase.startStarbaseUpgradeCraftingProcess( sageGameTest.program, sageGameTest.craftingProgram, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), theSector.starbasePlayers[0].starbasePlayer, theSector.starbases[0], craftingInstance.key, craftingInstance.data.craftingProcess, upgradeFacility.publicKey(), recipeForResourceSubmission.key, sageGameTest.gameId, sageGameTest.gameState as PublicKey, { keyIndex: sageGameTest.profiles.player1.index, }, ), ); await sageGameTest.sendManyToChain(instructions, true); }; const submitResource = async ( craftingInstance: CraftingInstance, recipeForResourceSubmission: Recipe, theSector: SageGameTestSector, ) => { if ( !sageGameTest.mints || !sageGameTest.pointCategories || !sageGameTest.lookupTables ) { throw 'game incorrectly set up'; } let initializeUserRedemption = false; let initialRedeemedPoints = new BN(0); try { initialRedeemedPoints = (await loadUserRedemption()).data.points; } catch { initializeUserRedemption = true; } const lpBefore = ( await readFromRPCOrError( connection, sageGameTest.pointsProgram, sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.lpCategory, ), UserPoints, ) ).data.earnedPoints; const tokenAddresses: PublicKey[] = []; const instructions = []; for ( let upgradeProcessRecipeInputIndex = 0; upgradeProcessRecipeInputIndex < recipeForResourceSubmission.ingredientInputsOutputs.length; upgradeProcessRecipeInputIndex++ ) { const recipeIngredient = recipeForResourceSubmission.ingredientInputsOutputs[ upgradeProcessRecipeInputIndex ]; const tokenTo = createAssociatedTokenAccountIdempotent( recipeIngredient.mint, theSector.starbasePlayers[0].cargoPod, true, ); const resourceRecipe = recipeIngredient.mint.equals( sageGameTest.mints[SageGameTest.INGREDIENT1], ) ? ingredient1Recipe : recipeIngredient.mint.equals( sageGameTest.mints[SageGameTest.INGREDIENT2], ) ? ingredient2Recipe : (() => { throw 'Invalid recipe ingredient'; })(); tokenAddresses.push(tokenTo.address); const starbaseUpgradeRecipeInputIndex = recipeForStarbaseUpgradeItself.ingredientInputsOutputs .map((i) => i.mint.toBase58()) .indexOf(recipeIngredient.mint.toBase58()); const submitIx = Starbase.submitStarbaseUpgradeResource( sageGameTest.program, sageGameTest.cargoProgram, sageGameTest.craftingProgram, sageGameTest.pointsProgram, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), 'funder', theSector.starbasePlayers[0].starbasePlayer, theSector.starbases[0], sageGameTest.gameId, sageGameTest.gameState as PublicKey, craftingInstance.data.craftingProcess, upgradeFacility.publicKey(), recipeForResourceSubmission.key, recipeForStarbaseUpgradeItself.key, resourceRecipe, theSector.starbasePlayers[0].cargoPod, sageGameTest.getCargoTypeAddress(recipeIngredient.mint), sageGameTest.cargoStatsDefinition as PublicKey, getAssociatedTokenAddressSync( recipeIngredient.mint, craftingInstance.data.craftingProcess, true, ), tokenTo.address, recipeIngredient.mint, sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.lpCategory, ), sageGameTest.pointCategories.lpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.lpCategory, ), initializeUserRedemption ? userRedemption : userRedemption.publicKey(), // userRedemptionAccount redemptionConfig.publicKey(), // redemptionConfig sageGameTest.pointsStoreProgram, // pointsStoreProgram { sagePermissionsKeyIndex: sageGameTest.profiles.player1.index, pointsProgramPermissionsKeyIndex: sageGameTest.profiles.player1.index, upgradeProcessRecipeInputIndex, starbaseUpgradeRecipeInputIndex, resourceRecipeOutputIndex: 0, epochIndex: 0, }, ); instructions.push(...[tokenTo.instructions, submitIx]); } await sageGameTest.sendManyToChain( instructions, true, sageGameTest.lookupTables, ); const lpAfter = ( await readFromRPCOrError( connection, sageGameTest.pointsProgram, sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.lpCategory, ), UserPoints, ) ).data.earnedPoints; // confirm auto-redemption worked for starbase upgrade expect( (await loadUserRedemption()).data.points.eq( initialRedeemedPoints.add(lpAfter.sub(lpBefore)), ), ).toBe(true); }; const getIngredientTokens = async (owner: PublicKey) => { if (!sageGameTest.mints) { throw 'game incorrectly set up'; } const allMints = sageGameTest.mints; const ownerTokens = await getParsedTokenAccountsByOwner( connection, owner, TOKEN_PROGRAM_ID, ); return ownerTokens.filter( (it) => it.mint.equals(allMints[SageGameTest.INGREDIENT1]) || it.mint.equals(allMints[SageGameTest.INGREDIENT2]), ); }; const getIngredientTokenBalance = ( accounts: Account[], tokenName: string, ) => { if (!sageGameTest.mints) { throw 'game incorrectly set up'; } const allMints = sageGameTest.mints; const account = accounts.find((it) => it.mint.equals(allMints[tokenName])); if (!account) { throw 'account not found'; } return Number(account.delegatedAmount); }; const updateUpgradeRecipe = async ( theSector: SageGameTestSector, newRecipe: PublicKey, currentLevelIndex: number, ) => { if ( !sageGameTest.gameState || !sageGameTest.cargoStatsDefinition || !sageGameTest.craftingDomain || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.fleets || !sageGameTest.pointCategories || !sageGameTest.recipes ) { throw 'game incorrectly set up'; } const gameStateAcc = await sageGameTest.loadGameState(); const factionStarbaseLevelInfos = gameStateAcc.data.fleet .starbaseLevels as FactionsStarbaseLevelInfo; const levelInfo = factionStarbaseLevelInfos.mud[currentLevelIndex]; const input: UpdateFleetDataInput = { starbaseLevels: [ { ...levelInfo, level: currentLevelIndex, faction: Faction.MUD as number, oldRecipeForUpgrade: levelInfo.recipeForUpgrade, newRecipeForUpgrade: newRecipe, }, ], }; await sageGameTest.createOrUpdateGameState(input); await sageGameTest.syncStarbasePlayer( theSector.starbasePlayers[0].starbasePlayer, ); }; const createUpgradeRecipe = async () => { const tempRecipe = keypairToAsyncSigner(Keypair.generate()); const currentLevelRecipeIngredients: Array<[PublicKey, number]> = recipeForStarbaseUpgradeItself.ingredientInputsOutputs.map((it) => [ it.mint, it.amount.toNumber(), ]); await sageGameTest.sendManyToChain( sageGameTest.setupRecipeInstructions( tempRecipe, recipeForStarbaseUpgradeItself.data.category, { duration: recipeForStarbaseUpgradeItself.data.duration, minDuration: recipeForStarbaseUpgradeItself.data.minDuration, namespace: recipeForStarbaseUpgradeItself.data.namespace, usageLimit: recipeForStarbaseUpgradeItself.data.usageLimit, value: recipeForStarbaseUpgradeItself.data.value, }, currentLevelRecipeIngredients.concat(currentLevelRecipeIngredients), [], [], ), true, ); return tempRecipe.publicKey(); }; const updateAndSyncUpgradeRecipe = async ( theSector: SageGameTestSector, newUpgradeRecipe: PublicKey, currentStarbaseLevel: number, ) => { if (!sageGameTest.gameState) { throw 'game incorrectly set up'; } await updateUpgradeRecipe( theSector, newUpgradeRecipe, currentStarbaseLevel, ); await sageGameTest.sendManyToChain( Starbase.syncStarbaseUpgradeIngredients( sageGameTest.program, sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, theSector.starbases[0], sageGameTest.gameId, sageGameTest.gameState, newUpgradeRecipe, { keyIndex: sageGameTest.profiles.superuser.index }, ), true, ); }; const loadUserRedemption = async () => { return await readFromRPCOrError( sageGameTest.connection, sageGameTest.pointsStoreProgram, userRedemption.publicKey(), UserRedemption, 'confirmed', ); }; const userRedemptionExists = async () => { return ( (await readFromRPCNullable( sageGameTest.connection, sageGameTest.pointsStoreProgram, userRedemption.publicKey(), UserRedemption, 'confirmed', )) === null ); }; const setupLookupTables = async () => { if (!sageGameTest.mints || !sageGameTest.pointCategories) { throw 'game incorrectly set up'; } await sageGameTest.newLookupTable([ SystemProgram.programId, TOKEN_PROGRAM_ID, sageGameTest.program.programId, sageGameTest.playerProfileProgram.programId, sageGameTest.profileFactionProgram.programId, sageGameTest.cargoProgram.programId, sageGameTest.craftingProgram.programId, sageGameTest.pointsProgram.programId, sageGameTest.pointsStoreProgram.programId, sageGameTest.funder.publicKey(), sageGameTest.profiles.player1.key.publicKey(), sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), sageGameTest.gameId, sageGameTest.gameState as PublicKey, sageGameTest.cargoStatsDefinition as PublicKey, sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.lpCategory, ), sageGameTest.pointCategories.lpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.lpCategory, ), redemptionConfig.publicKey(), userRedemption.publicKey(), ]); }; beforeAll(async () => { const airdropTx = await connection.requestAirdrop( walletSigner.publicKey(), 500_000_000_000, ); await connection.confirmTransaction(airdropTx); // trying to get fleets with 0 crew to allow for the large number of ships we need for this test const defaultShipStats = SageGameTest.getDefaultShipStats(); defaultShipStats.miscStats.requiredCrew = 0; // create game sageGameTest = await SageGameTest.createGame({ funder: walletKeypair, connection, numberOfShips: 1, setupCraftingAccounts: true, fleet: { maxFleetSize: 3_000_000 }, numberOfShipsInFleet: 2000, addCargoToFleet: false, fleetToIdle: false, shipStats: defaultShipStats, numCrewForPlayer: CREW_COUNT, chunkCrewInstructions: Math.ceil(CREW_COUNT / 5), }); const resourceRecipes = await sageGameTest.setupResourceRecipes([ { name: SageGameTest.REPAIR_KIT, recipeValue: toolkitRecipeValue, }, { name: SageGameTest.INGREDIENT1, recipeValue: ingredient1RecipeValue, }, { name: SageGameTest.INGREDIENT2, recipeValue: ingredient2RecipeValue, }, ]); toolkitRecipe = resourceRecipes[0]; ingredient1Recipe = resourceRecipes[1]; ingredient2Recipe = resourceRecipes[2]; await sageGameTest.setupAtlasRedemption(redemptionConfig, userRedemption); await setupLookupTables(); }); it('start starbase upgrade', async () => { if ( !sageGameTest.gameState || !sageGameTest.cargoStatsDefinition || !sageGameTest.craftingDomain || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.fleets || !sageGameTest.recipes ) { throw 'game incorrectly set up'; } // create upgradeable starbase and move to it const newSectorCoords = [new BN(0), new BN(-1)] as [BN, BN]; await sageGameTest.createSector({ coordinates: newSectorCoords, addResourcesToStarbaseCargoPod: false, setUpPlayer: false, starbaseLevel: 3, }); const oldSector = sageGameTest.sectors[0]; const newSector = sageGameTest.sectors[1]; const fleet1 = sageGameTest.fleets[0]; const fleetAccount = await sageGameTest.loadFleet(fleet1); // move to upgradeable starbase const ingredient1CargoAmt = 11_000; const ingredient2CargoAmt = 12_000; const cargoCapacity = (fleetAccount.data.stats).cargoStats .cargoCapacity; const holdLeftOver = cargoCapacity > ingredient1CargoAmt + ingredient2CargoAmt ? cargoCapacity - (ingredient1CargoAmt + ingredient2CargoAmt) : 0; const toolkitAmountNeeded = Math.min( (fleetAccount.data.stats).cargoStats.ammoCapacity, holdLeftOver, ); await sageGameTest.sendManyToChain( [ ...sageGameTest.depositCargoToFleetInstructions( fleet1, fleetAccount.data.fuelTank, oldSector.starbases[0], oldSector.starbasePlayers[0].starbasePlayer, sageGameTest.mints[SageGameTest.FUEL], (fleetAccount.data.stats).cargoStats.fuelCapacity, ), ...sageGameTest.depositCargoToFleetInstructions( fleet1, fleetAccount.data.cargoHold, oldSector.starbases[0], oldSector.starbasePlayers[0].starbasePlayer, sageGameTest.mints[SageGameTest.REPAIR_KIT], toolkitAmountNeeded, ), ...sageGameTest.addCargoToGameInstructions( oldSector.starbases[0], oldSector.starbasePlayers[0].starbasePlayer, sageGameTest.mints[SageGameTest.INGREDIENT1], ingredient1CargoAmt, oldSector.starbasePlayers[0].cargoPod, ), ...sageGameTest.addCargoToGameInstructions( oldSector.starbases[0], oldSector.starbasePlayers[0].starbasePlayer, sageGameTest.mints[SageGameTest.INGREDIENT2], ingredient2CargoAmt, oldSector.starbasePlayers[0].cargoPod, ), ...sageGameTest.depositCargoToFleetInstructions( fleet1, fleetAccount.data.cargoHold, oldSector.starbases[0], oldSector.starbasePlayers[0].starbasePlayer, sageGameTest.mints[SageGameTest.INGREDIENT1], ingredient1CargoAmt, ), ...sageGameTest.depositCargoToFleetInstructions( fleet1, fleetAccount.data.cargoHold, oldSector.starbases[0], oldSector.starbasePlayers[0].starbasePlayer, sageGameTest.mints[SageGameTest.INGREDIENT2], ingredient2CargoAmt, ), Fleet.loadFleetCrew( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), fleet1, sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.gameId, { keyIndex: sageGameTest.profiles.player1.index, count: CREW_COUNT, }, ), Fleet.loadingBayToIdle( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), fleet1, oldSector.starbases[0], oldSector.starbasePlayers[0].starbasePlayer, sageGameTest.gameId, sageGameTest.gameState, sageGameTest.profiles.player1.index, ), ...sageGameTest.warpToCoordinateInstructions( fleetAccount.key, fleetAccount.data.fuelTank, newSectorCoords, ), ], true, ); const fleetAccount2 = await sageGameTest.loadFleet(fleet1); if (!fleetAccount2.state.MoveWarp) { throw 'Fleet should be in MoveWarp state'; } const moveTime = fleetAccount2.state.MoveWarp.warpFinish .sub(fleetAccount2.state.MoveWarp.warpStart) .toNumber(); // wait for movement to finish await new Promise((r) => setTimeout( r, (Math.max( moveTime, (fleetAccount2.data.stats).movementStats.warpCoolDown, ) + 1) * 1000, ), ); // dock and disband await sageGameTest.sendManyToChain([ ...sageGameTest.moveWarpHandlerInstructions(fleet1), Fleet.idleToLoadingBay( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), fleet1, newSector.starbases[0], newSector.starbasePlayers[0].starbasePlayer, sageGameTest.gameId, sageGameTest.gameState, sageGameTest.profiles.player1.index, ), ]); await sageGameTest.disbandFleet( fleet1, newSector.starbases[0], newSector.starbasePlayers[0].starbasePlayer, ); // Register crafting facilities at Starbase const gameStateAccount = await sageGameTest.loadGameState(); const starbaseAcct = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); const levels = gameStateAccount.data.fleet .starbaseLevels as FactionsStarbaseLevelInfo; const thisLevel = levels.mud[starbaseAcct.data.level]; recipeForStarbaseUpgradeItself = await sageGameTest.loadRecipe( thisLevel.recipeForUpgrade, ); await sageGameTest.sendManyToChain([ CraftingFacility.registerCraftingFacility( sageGameTest.craftingProgram, craftingFacility, sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, sageGameTest.craftingDomain, newSector.starbases[0], { locationType: CraftingFacilityLocationType.Starbase, efficiency: 10000, maxConcurrentProcesses: 1000, keyIndex: sageGameTest.profiles.superuser.index, }, ) /** register regular crafting facility */, CraftingFacility.registerCraftingFacility( sageGameTest.craftingProgram, upgradeFacility, sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, sageGameTest.craftingDomain, newSector.starbases[0], { locationType: CraftingFacilityLocationType.Starbase, efficiency: 10000, maxConcurrentProcesses: 1000, keyIndex: sageGameTest.profiles.superuser.index, }, ) /** register upgrade facility */, CraftingFacility.addCraftingFacilityRecipeCategory( sageGameTest.craftingProgram, craftingFacility.publicKey(), sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, sageGameTest.craftingDomain, thisLevel.recipeCategoryForLevel, { keyIndex: sageGameTest.profiles.superuser.index }, ) /** register crafting recipe category */, CraftingFacility.addCraftingFacilityRecipeCategory( sageGameTest.craftingProgram, upgradeFacility.publicKey(), sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, sageGameTest.craftingDomain, sageGameTest.recipes[0].category, { keyIndex: sageGameTest.profiles.superuser.index }, ) /** register upgrade recipe category */, Starbase.updateStarbase( sageGameTest.program, newSector.starbases[0], sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, sageGameTest.gameId, { keyIndex: sageGameTest.profiles.superuser.index, }, sageGameTest.craftingDomain, craftingFacility.publicKey(), upgradeFacility.publicKey(), ), ]); const starbaseAcct2 = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); expect( starbaseAcct2.data.craftingFacility.equals(craftingFacility.publicKey()), ).toBe(true); expect( starbaseAcct2.data.upgradeFacility.equals(upgradeFacility.publicKey()), ).toBe(true); expect( starbaseAcct2.data.upgradeState === StarbaseUpgradeState.Started, ).toBe(false); await sageGameTest.sendManyToChain( Starbase.startStarbaseUpgrade( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), newSector.starbasePlayers[0].starbasePlayer, newSector.starbases[0], sageGameTest.gameId, sageGameTest.gameState, upgradeFacility.publicKey(), recipeForStarbaseUpgradeItself.key, { keyIndex: sageGameTest.profiles.player1.index }, ), ); const starbaseAcct3 = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); expect( starbaseAcct3.data.upgradeState === StarbaseUpgradeState.Started, ).toBe(true); expect( new BN(starbaseAcct3.data.upgradeIngredientsChecksum, 10, 'le').eq( new BN(0), ), ).toBe(true); expect(starbaseAcct3.data.numUpgradeIngredients).toBe( recipeForStarbaseUpgradeItself.data.consumablesCount, ); expect( arrayDeepEquals( starbaseAcct3.upgradeIngredientAmounts, [new BN(0), new BN(0)], (a, b) => a.eq(b), ), ).toBe(true); }); it('start process to submit starbase upgrade resource', async () => { if ( !sageGameTest.gameState || !sageGameTest.cargoStatsDefinition || !sageGameTest.craftingDomain || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.fleets || !sageGameTest.recipes ) { throw 'game incorrectly set up'; } const recipeForResourceSubmission = await sageGameTest.loadRecipe( sageGameTest.recipes[0].recipes[0], ); const newSector = sageGameTest.sectors[1]; const loopCount = 6; const input = { recipeCategoryIndex: 0, numCrew: new BN((CREW_COUNT - 2) / loopCount), }; for (let index = 1; index < loopCount; index++) { const createProcess = Starbase.createStarbaseUpgradeResourceProcess( sageGameTest.program, sageGameTest.craftingProgram, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), newSector.starbasePlayers[0].starbasePlayer, newSector.starbases[0], sageGameTest.gameId, sageGameTest.gameState, upgradeFacility.publicKey(), recipeForResourceSubmission.key, sageGameTest.craftingDomain, { ...input, craftingId: new BN(index), quantity: index === 1 ? new BN(INGREDIENT1_QTY) : new BN(INGREDIENT2_QTY), keyIndex: sageGameTest.profiles.player1.index, }, ); await sageGameTest.sendManyToChain(createProcess.instructions, true); const { craftingProcessKey, craftingInstanceKey } = createProcess; const craftingInstance = await readFromRPCOrError( connection, sageGameTest.program, craftingInstanceKey[0], CraftingInstance, 'confirmed', ); expect( craftingInstanceDataEquals(craftingInstance.data, { version: 0, seqId: 0, numCrew: input.numCrew, authority: newSector.starbasePlayers[0].starbasePlayer, instanceType: CraftingInstanceType.StarbaseUpgradeMaterial, craftingProcess: craftingProcessKey[0], bump: craftingInstanceKey[1], }), ).toBe(true); craftingProcessKeys.push(craftingProcessKey[0]); craftingInstances.push(craftingInstance); } expect(craftingInstances.length).toBe(5); }); it('deposit & submit starbase upgrade resource', async () => { if ( !sageGameTest.gameState || !sageGameTest.cargoStatsDefinition || !sageGameTest.craftingDomain || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.fleets || !sageGameTest.pointCategories || !sageGameTest.recipes ) { throw 'game incorrectly set up'; } const recipeForResourceSubmission = await sageGameTest.loadRecipe( sageGameTest.recipes[0].recipes[0], ); const gameState = await sageGameTest.loadGameState(); const newSector = sageGameTest.sectors[1]; // submit part of required resources for (let index = 0; index < craftingInstances.length; index++) { const craftingInstance = craftingInstances[index]; await depositResourceAndStartProcess( craftingInstance, recipeForResourceSubmission, newSector, ); } // stop one crafting instance const lastCraftingInstance0 = craftingInstances[craftingInstances.length - 1]; await sageGameTest.sendManyToChain( Starbase.stopStarbaseUpgradeCraftingProcess( sageGameTest.program, sageGameTest.craftingProgram, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), newSector.starbasePlayers[0].starbasePlayer, newSector.starbases[0], lastCraftingInstance0.key, lastCraftingInstance0.data.craftingProcess, upgradeFacility.publicKey(), sageGameTest.recipes[0].recipes[0], sageGameTest.gameId, sageGameTest.gameState, { keyIndex: sageGameTest.profiles.player1.index }, ), true, ); const stoppedCraftingProcess = await readFromRPCOrError( connection, sageGameTest.craftingProgram, lastCraftingInstance0.data.craftingProcess, CraftingProcess, 'confirmed', ); expect(stoppedCraftingProcess.data.status).toBe( getNumberFromEnum( CraftingProcessStatus, CraftingProcessStatus.Initialized, ), ); // wait for crafting process duration to end await new Promise((r) => setTimeout(r, 3000)); let lpBefore = 0; const ingredientTokens = await getIngredientTokens( newSector.starbasePlayers[0].cargoPod, ); let submitBeforeUpkeep = true; try { // cannot submit without upkeep since it is configured for the starbase await submitResource( craftingInstances[0], recipeForResourceSubmission, newSector, ); } catch (err) { submitBeforeUpkeep = false; const initializeUserRedemption = await userRedemptionExists(); const initialRedeemedPoints = initializeUserRedemption ? new BN(0) : (await loadUserRedemption()).data.points; const toolkitAmount = (gameState.data.fleet.upkeep) .level4.toolkitReserve; const toolkitTokenKey = getAssociatedTokenAddressSync( sageGameTest.mints[SageGameTest.REPAIR_KIT], newSector.starbasePlayers[0].cargoPod, true, ); const depositToolkitUpkeepIx = Starbase.depositStarbaseUpkeepResource( sageGameTest.program, sageGameTest.cargoProgram, sageGameTest.pointsProgram, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), 'funder', newSector.starbasePlayers[0].starbasePlayer, newSector.starbases[0], sageGameTest.gameId, sageGameTest.gameState, newSector.starbasePlayers[0].cargoPod, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.REPAIR_KIT], ), sageGameTest.cargoStatsDefinition, toolkitRecipe, toolkitTokenKey, sageGameTest.mints[SageGameTest.REPAIR_KIT], sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.lpCategory, ), sageGameTest.pointCategories.lpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.lpCategory, ), initializeUserRedemption ? userRedemption : userRedemption.publicKey(), // userRedemption redemptionConfig.publicKey(), // redemptionConfig sageGameTest.pointsStoreProgram, // pointsStoreProgram { sagePermissionsKeyIndex: sageGameTest.profiles.player1.index, pointsProgramPermissionsKeyIndex: sageGameTest.profiles.player1.index, amount: toolkitAmount, resourceType: UpkeepResourceType.Toolkit, resourceIndex: 0, epochIndex: 0, }, ); await sageGameTest.sendManyToChain(depositToolkitUpkeepIx); // confirm auto-redemption worked for starbase upkeep const userRedemptionAcc1 = await loadUserRedemption(); expect(userRedemptionAcc1.data.points.gt(initialRedeemedPoints)).toBe( true, ); lpBefore = ( await readFromRPCOrError( connection, sageGameTest.pointsProgram, sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.lpCategory, ), UserPoints, ) ).data.earnedPoints.toNumber(); // ensure crafting process ends await new Promise((r) => setTimeout(r, 3000)); await submitResource( craftingInstances[0], recipeForResourceSubmission, newSector, ); } expect(submitBeforeUpkeep).toBe(false); const starbaseAcct = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); expect( new BN(starbaseAcct.data.upgradeIngredientsChecksum, 10, 'le').eq( new BN(0), ), ).toBe(true); const ingredient1Deposited_0 = SageGameTest.defaultConsumableAmount * INGREDIENT1_QTY; const ingredient2Deposited_0 = SageGameTest.defaultNonConsumableAmount * INGREDIENT1_QTY; expect( arrayDeepEquals( starbaseAcct.upgradeIngredientAmounts, [new BN(ingredient1Deposited_0), new BN(ingredient2Deposited_0)], (a, b) => a.eq(b), ), ).toBe(true); const ingredientTokens2 = await getIngredientTokens( newSector.starbasePlayers[0].cargoPod, ); // nothing was refunded expect( getIngredientTokenBalance(ingredientTokens, SageGameTest.INGREDIENT1), ).toBe( getIngredientTokenBalance(ingredientTokens2, SageGameTest.INGREDIENT1), ); expect( getIngredientTokenBalance(ingredientTokens, SageGameTest.INGREDIENT2), ).toBe( getIngredientTokenBalance(ingredientTokens2, SageGameTest.INGREDIENT2), ); const lpAfter = ( await readFromRPCOrError( connection, sageGameTest.pointsProgram, sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.lpCategory, ), UserPoints, ) ).data.earnedPoints.toNumber(); const earnedLP = Math.floor( ingredient1Deposited_0 * ingredient1RecipeValue * (ProgressionItemType.Upgrade + 1), ) + Math.floor( ingredient2Deposited_0 * ingredient2RecipeValue * (ProgressionItemType.Upgrade + 1), ); expect(lpAfter).toBe(lpBefore + earnedLP); // sync starbase ingredients doesn't change anything if recipe is the same await sageGameTest.sendManyToChain( Starbase.syncStarbaseUpgradeIngredients( sageGameTest.program, sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, newSector.starbases[0], sageGameTest.gameId, sageGameTest.gameState, recipeForStarbaseUpgradeItself.key, { keyIndex: sageGameTest.profiles.superuser.index }, ), true, ); const starbaseAcct1 = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); expect( new BN(starbaseAcct.data.upgradeIngredientsChecksum, 10, 'le').eq( new BN(starbaseAcct1.data.upgradeIngredientsChecksum, 10, 'le'), ), ).toBe(true); expect( arrayDeepEquals( starbaseAcct.upgradeIngredientAmounts, starbaseAcct1.upgradeIngredientAmounts, (a, b) => a.eq(b), ), ).toBe(true); expect(starbaseAcct.data.numUpgradeIngredients).toBe( starbaseAcct1.data.numUpgradeIngredients, ); // submit the remaining part + extra await submitResource( craftingInstances[1], recipeForResourceSubmission, newSector, ); const starbaseAcct2 = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); expect( new BN(starbaseAcct2.data.upgradeIngredientsChecksum, 10, 'le').eq( new BN(3), ), ).toBe(true); expect( arrayDeepEquals( starbaseAcct2.upgradeIngredientAmounts, [ new BN( SageGameTest.defaultConsumableAmount * SageGameTest.defaultUpgradeMultiplier, ), new BN( SageGameTest.defaultNonConsumableAmount * SageGameTest.defaultUpgradeMultiplier, ), ], (a, b) => a.eq(b), ), ).toBe(true); // extra amount was refunded const ingredientTokens3 = await getIngredientTokens( newSector.starbasePlayers[0].cargoPod, ); expect( getIngredientTokenBalance(ingredientTokens3, SageGameTest.INGREDIENT1) - getIngredientTokenBalance(ingredientTokens2, SageGameTest.INGREDIENT1), ).toBe(SageGameTest.defaultConsumableAmount); expect( getIngredientTokenBalance(ingredientTokens3, SageGameTest.INGREDIENT2) - getIngredientTokenBalance(ingredientTokens2, SageGameTest.INGREDIENT2), ).toBe(SageGameTest.defaultNonConsumableAmount); // update upgrade recipe while upgrade is in progress and check that upgrade values changed const tempRecipe = await createUpgradeRecipe(); await updateAndSyncUpgradeRecipe( newSector, tempRecipe, starbaseAcct.data.level, ); const starbaseAcct2A = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); expect( new BN(starbaseAcct2.data.upgradeIngredientsChecksum, 10, 'le').eq( new BN(starbaseAcct2A.data.upgradeIngredientsChecksum, 10, 'le'), ), ).toBe(true); expect(starbaseAcct2.data.numUpgradeIngredients + 2).toBe( starbaseAcct2A.data.numUpgradeIngredients, ); expect( arrayDeepEquals( starbaseAcct2A.upgradeIngredientAmounts, [ new BN( SageGameTest.defaultConsumableAmount * SageGameTest.defaultUpgradeMultiplier, ), new BN( SageGameTest.defaultNonConsumableAmount * SageGameTest.defaultUpgradeMultiplier, ), new BN(0), new BN(0), ], (a, b) => a.eq(b), ), ).toBe(true); // change it back to the previous recipe and confirm upgrade data is the same as before await updateAndSyncUpgradeRecipe( newSector, recipeForStarbaseUpgradeItself.key, starbaseAcct.data.level, ); const starbaseAcct2B = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); expect( new BN(starbaseAcct2.data.upgradeIngredientsChecksum, 10, 'le').eq( new BN(starbaseAcct2B.data.upgradeIngredientsChecksum, 10, 'le'), ), ).toBe(true); expect( arrayDeepEquals( starbaseAcct2.upgradeIngredientAmounts, starbaseAcct2B.upgradeIngredientAmounts, (a, b) => a.eq(b), ), ).toBe(true); expect(starbaseAcct2.data.numUpgradeIngredients).toBe( starbaseAcct2B.data.numUpgradeIngredients, ); // submit after all requirements have been met await submitResource( craftingInstances[2], recipeForResourceSubmission, newSector, ); const starbaseAcct3 = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); expect( new BN(starbaseAcct3.data.upgradeIngredientsChecksum, 10, 'le').eq( new BN(3), ), ).toBe(true); expect( arrayDeepEquals( starbaseAcct3.upgradeIngredientAmounts, [ new BN( SageGameTest.defaultConsumableAmount * SageGameTest.defaultUpgradeMultiplier, ), new BN( SageGameTest.defaultNonConsumableAmount * SageGameTest.defaultUpgradeMultiplier, ), ], (a, b) => a.eq(b), ), ).toBe(true); // entire amount was refunded const ingredientTokens4 = await getIngredientTokens( newSector.starbasePlayers[0].cargoPod, ); expect( getIngredientTokenBalance(ingredientTokens4, SageGameTest.INGREDIENT1) - getIngredientTokenBalance(ingredientTokens3, SageGameTest.INGREDIENT1), ).toBe(SageGameTest.defaultUpgradeMultiplier); expect( getIngredientTokenBalance(ingredientTokens4, SageGameTest.INGREDIENT2) - getIngredientTokenBalance(ingredientTokens3, SageGameTest.INGREDIENT2), ).toBe(SageGameTest.defaultUpgradeMultiplier * 0.5); // close all but the last one const closeIxs = []; for ( let index = 0; index < craftingInstances.slice(0, -2).length; index++ ) { const craftingInstance = craftingInstances[index]; closeIxs.push( Starbase.closeStarbaseUpgradeCraftingProcess( sageGameTest.program, sageGameTest.craftingProgram, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), 'funder', newSector.starbasePlayers[0].starbasePlayer, newSector.starbases[0], craftingInstance.key, craftingInstance.data.craftingProcess, upgradeFacility.publicKey(), recipeForResourceSubmission.key, sageGameTest.gameId, sageGameTest.gameState as PublicKey, { keyIndex: sageGameTest.profiles.player1.index, }, ), ); } await sageGameTest.sendManyToChain(closeIxs); for ( let index = 0; index < craftingInstances.slice(0, -2).length; index++ ) { const craftingInstance = craftingInstances[index]; const craftingProcessAcct = await readFromRPCNullable( sageGameTest.connection, sageGameTest.craftingProgram, craftingInstance.data.craftingProcess, CraftingProcess, 'confirmed', ); expect(craftingProcessAcct).toBeNull; const craftingInstanceAcct = await readFromRPCNullable( sageGameTest.connection, sageGameTest.program, craftingInstance.key, CraftingInstance, 'confirmed', ); expect(craftingInstanceAcct).toBeNull; } }); it('complete starbase upgrade', async () => { if ( !sageGameTest.gameState || !sageGameTest.cargoStatsDefinition || !sageGameTest.craftingDomain || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.fleets || !sageGameTest.recipes ) { throw 'game incorrectly set up'; } const newSector = sageGameTest.sectors[1]; const gameStateAccount = await sageGameTest.loadGameState(); const starbaseAcct = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); const levels = gameStateAccount.data.fleet .starbaseLevels as FactionsStarbaseLevelInfo; const nextLevel = levels.mud[starbaseAcct.data.level + 1]; const craftingFacilityAcct = await readFromRPCOrError( connection, sageGameTest.craftingProgram, craftingFacility.publicKey(), CraftingFacility, ); await sageGameTest.sendManyToChain( Starbase.completeStarbaseUpgrade( sageGameTest.program, sageGameTest.craftingProgram, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), newSector.starbasePlayers[0].starbasePlayer, newSector.starbases[0], sageGameTest.gameId, sageGameTest.gameState, craftingFacility.publicKey(), upgradeFacility.publicKey(), nextLevel.recipeCategoryForLevel, recipeForStarbaseUpgradeItself.key, sageGameTest.craftingDomain, { keyIndex: sageGameTest.profiles.player1.index }, ), ); const starbaseAcct2 = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); expect(starbaseAcct2.data.level).toBe(starbaseAcct.data.level + 1); expect(starbaseAcct2.data.hp.eq(nextLevel.hp)).toBe(true); expect(starbaseAcct2.data.sp.eq(nextLevel.sp)).toBe(true); expect(starbaseAcct2.data.sectorRingAvailable).toBe( nextLevel.sectorRingAvailable, ); expect( starbaseAcct2.data.builtDestroyedTimestamp.gt( starbaseAcct.data.builtDestroyedTimestamp, ), ).toBe(true); expect(starbaseAcct2.data.upgradeState).toBe( StarbaseUpgradeState.Completed, ); const craftingFacilityAcc2 = await readFromRPCOrError( connection, sageGameTest.craftingProgram, craftingFacility.publicKey(), CraftingFacility, ); expect(craftingFacilityAcc2.recipeCategories.length).toBe( craftingFacilityAcct.recipeCategories.length + 1, ); const recipeCategoryForNextLevel = craftingFacilityAcc2.recipeCategories.find((it) => it.equals(nextLevel.recipeCategoryForLevel), ); if (!recipeCategoryForNextLevel) { throw 'recipeCategoryForNextLevel not found'; } }); it('submit stale starbase resource crafting process', async () => { if ( !sageGameTest.gameState || !sageGameTest.cargoStatsDefinition || !sageGameTest.craftingDomain || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.fleets || !sageGameTest.recipes ) { throw 'game incorrectly set up'; } const recipeForResourceSubmission = await sageGameTest.loadRecipe( sageGameTest.recipes[0].recipes[0], ); const newSector = sageGameTest.sectors[1]; const ingredientTokens = await getIngredientTokens( newSector.starbasePlayers[0].cargoPod, ); const starbaseAcc = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); // submit resource after process was completed await submitResource( craftingInstances[3], recipeForResourceSubmission, newSector, ); const starbaseAcc2 = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); expect( new BN(starbaseAcc2.data.upgradeIngredientsChecksum, 10, 'le').eq( new BN(starbaseAcc.data.upgradeIngredientsChecksum, 10, 'le'), ), ).toBe(true); expect( arrayDeepEquals( starbaseAcc2.upgradeIngredientAmounts, starbaseAcc.upgradeIngredientAmounts, (a, b) => a.eq(b), ), ).toBe(true); // entire amount was refunded const ingredientTokens2 = await getIngredientTokens( newSector.starbasePlayers[0].cargoPod, ); expect( getIngredientTokenBalance(ingredientTokens2, SageGameTest.INGREDIENT1) - getIngredientTokenBalance(ingredientTokens, SageGameTest.INGREDIENT1), ).toBe(SageGameTest.defaultUpgradeMultiplier); expect( getIngredientTokenBalance(ingredientTokens2, SageGameTest.INGREDIENT2) - getIngredientTokenBalance(ingredientTokens, SageGameTest.INGREDIENT2), ).toBe(SageGameTest.defaultUpgradeMultiplier * 0.5); // close the process const craftingInstance = craftingInstances[3]; await sageGameTest.sendManyToChain( Starbase.closeStarbaseUpgradeCraftingProcess( sageGameTest.program, sageGameTest.craftingProgram, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), 'funder', newSector.starbasePlayers[0].starbasePlayer, newSector.starbases[0], craftingInstance.key, craftingInstance.data.craftingProcess, upgradeFacility.publicKey(), recipeForResourceSubmission.key, sageGameTest.gameId, sageGameTest.gameState as PublicKey, { keyIndex: sageGameTest.profiles.player1.index, }, ), ); const craftingProcessAcct = await readFromRPCNullable( sageGameTest.connection, sageGameTest.craftingProgram, craftingInstance.data.craftingProcess, CraftingProcess, 'confirmed', ); expect(craftingProcessAcct).toBeNull; const craftingInstanceAcct = await readFromRPCNullable( sageGameTest.connection, sageGameTest.program, craftingInstance.key, CraftingInstance, 'confirmed', ); expect(craftingInstanceAcct).toBeNull; }); it('reset upgrade process by starting process', async () => { if ( !sageGameTest.gameState || !sageGameTest.cargoStatsDefinition || !sageGameTest.craftingDomain || !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.fleets || !sageGameTest.recipes ) { throw 'game incorrectly set up'; } const newSector = sageGameTest.sectors[1]; const starbaseAcct0 = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); expect( starbaseAcct0.data.upgradeState === StarbaseUpgradeState.Completed, ).toBe(true); expect( new BN(starbaseAcct0.data.upgradeIngredientsChecksum, 10, 'le').eq( new BN(3), ), ).toBe(true); expect( arrayDeepEquals( starbaseAcct0.upgradeIngredientAmounts, [ new BN( SageGameTest.defaultConsumableAmount * SageGameTest.defaultUpgradeMultiplier, ), new BN( SageGameTest.defaultNonConsumableAmount * SageGameTest.defaultUpgradeMultiplier, ), ], (a, b) => a.eq(b), ), ).toBe(true); await sageGameTest.sendManyToChain( Starbase.startStarbaseUpgrade( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), newSector.starbasePlayers[0].starbasePlayer, newSector.starbases[0], sageGameTest.gameId, sageGameTest.gameState, upgradeFacility.publicKey(), recipeForStarbaseUpgradeItself.key, { keyIndex: sageGameTest.profiles.player1.index }, ), true, ); const starbaseAcct = await readFromRPCOrError( connection, sageGameTest.program, newSector.starbases[0], Starbase, ); expect( starbaseAcct.data.upgradeState === StarbaseUpgradeState.Started, ).toBe(true); expect( new BN(starbaseAcct.data.upgradeIngredientsChecksum, 10, 'le').eq( new BN(0), ), ).toBe(true); expect(starbaseAcct.data.numUpgradeIngredients).toBe( recipeForStarbaseUpgradeItself.data.consumablesCount, ); const upgradeIngredientAmounts = [ ...new Set( starbaseAcct.upgradeIngredientAmounts.map((it) => it.toNumber()), ), ]; expect(upgradeIngredientAmounts.length).toBe(1); expect(upgradeIngredientAmounts[0]).toBe(0); }); });