diff --git a/Anchor.toml b/Anchor.toml index 045a0c8..0fe098a 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -36,7 +36,14 @@ members = [ ] [scripts] -test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/bolt.ts" +test = """ +echo "Waiting for 2 seconds..." +sleep 2 +echo "Running low level API tests..." +yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/bolt.low-level.api.ts +echo "Running intermediate level API tests..." +yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/bolt.intermediate-level.api.ts +""" [test] startup_wait = 5000 diff --git a/clients/bolt-sdk/src/generated/idl/world.json b/clients/bolt-sdk/src/generated/idl/world.json index 10ff410..004b962 100644 --- a/clients/bolt-sdk/src/generated/idl/world.json +++ b/clients/bolt-sdk/src/generated/idl/world.json @@ -2,7 +2,7 @@ "address": "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n", "metadata": { "name": "world", - "version": "0.1.12", + "version": "0.2.0", "spec": "0.1.0", "description": "Bolt World program", "repository": "https://github.com/magicblock-labs/bolt" diff --git a/clients/bolt-sdk/src/generated/types/index.ts b/clients/bolt-sdk/src/generated/types/index.ts new file mode 100644 index 0000000..335f102 --- /dev/null +++ b/clients/bolt-sdk/src/generated/types/index.ts @@ -0,0 +1 @@ +export * from "./world"; diff --git a/clients/bolt-sdk/src/generated/types/world.ts b/clients/bolt-sdk/src/generated/types/world.ts index 9871d00..28ec70b 100644 --- a/clients/bolt-sdk/src/generated/types/world.ts +++ b/clients/bolt-sdk/src/generated/types/world.ts @@ -8,7 +8,7 @@ export type World = { address: "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n"; metadata: { name: "world"; - version: "0.1.12"; + version: "0.2.0"; spec: "0.1.0"; description: "Bolt World program"; repository: "https://github.com/magicblock-labs/bolt"; diff --git a/clients/bolt-sdk/src/index.ts b/clients/bolt-sdk/src/index.ts index 23ab3fe..899ca0c 100644 --- a/clients/bolt-sdk/src/index.ts +++ b/clients/bolt-sdk/src/index.ts @@ -1,6 +1,7 @@ import { PublicKey } from "@solana/web3.js"; import type BN from "bn.js"; -import { PROGRAM_ID } from "./generated"; +import { PROGRAM_ID as WORLD_PROGRAM_ID } from "./generated"; +import { World as WORLD_PROGRAM_IDL } from "./generated/types"; export * from "./generated/accounts"; export * from "./generated/instructions"; export * from "./world/transactions"; @@ -12,6 +13,7 @@ export { DELEGATION_PROGRAM_ID } from "@magicblock-labs/ephemeral-rollups-sdk"; import * as anchor from "@coral-xyz/anchor"; export { anchor }; export { Provider, Program, Wallet, web3, workspace } from "@coral-xyz/anchor"; +export { WORLD_PROGRAM_ID, WORLD_PROGRAM_IDL }; export const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey( "Sysvar1nstructions1111111111111111111111111", @@ -20,7 +22,7 @@ export const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey( export function FindRegistryPda({ programId }: { programId?: PublicKey }) { return PublicKey.findProgramAddressSync( [Buffer.from("registry")], - programId ?? PROGRAM_ID, + programId ?? WORLD_PROGRAM_ID, )[0]; } @@ -34,7 +36,7 @@ export function FindWorldPda({ const idBuffer = Buffer.from(worldId.toArrayLike(Buffer, "be", 8)); return PublicKey.findProgramAddressSync( [Buffer.from("world"), idBuffer], - programId ?? PROGRAM_ID, + programId ?? WORLD_PROGRAM_ID, )[0]; } @@ -60,9 +62,13 @@ export function FindEntityPda({ } else { throw new Error("An entity must have either an Id or a Seed"); } - return PublicKey.findProgramAddressSync(seeds, programId ?? PROGRAM_ID)[0]; + return PublicKey.findProgramAddressSync( + seeds, + programId ?? WORLD_PROGRAM_ID, + )[0]; } +// TODO: seed must be Uint8Array like the other FindPda functions export function FindComponentPda({ componentId, entity, diff --git a/clients/bolt-sdk/src/world/transactions.ts b/clients/bolt-sdk/src/world/transactions.ts index 8dc85c9..88d780b 100644 --- a/clients/bolt-sdk/src/world/transactions.ts +++ b/clients/bolt-sdk/src/world/transactions.ts @@ -21,11 +21,34 @@ import { type TransactionInstruction, } from "@solana/web3.js"; import type WorldProgram from "../generated"; -import { PROGRAM_ID, worldIdl } from "../generated"; +import { + createInitializeRegistryInstruction, + PROGRAM_ID, + worldIdl, +} from "../generated"; import { type Idl, Program } from "@coral-xyz/anchor"; const MAX_COMPONENTS = 5; +export async function InitializeRegistry({ + payer, + connection, +}: { + payer: PublicKey; + connection: Connection; +}): Promise<{ + instruction: TransactionInstruction; + transaction: Transaction; +}> { + const registry = FindRegistryPda({}); + const instruction = createInitializeRegistryInstruction({ registry, payer }); + const transaction = new Transaction().add(instruction); + return { + instruction, + transaction, + }; +} + /** * Create the transaction to Initialize a new world * @param payer @@ -311,47 +334,6 @@ export async function InitializeComponent({ }; } -export async function Apply({ - authority, - boltSystem, - boltComponent, - componentProgram, - anchorRemainingAccounts, - world, - args, -}: { - authority: PublicKey; - boltSystem: PublicKey; - boltComponent: PublicKey; - componentProgram: PublicKey; - world: PublicKey; - anchorRemainingAccounts?: web3.AccountMeta[]; - args: Uint8Array; -}): Promise<{ - instruction: TransactionInstruction; - transaction: Transaction; -}> { - const instruction = createApplyInstruction( - { - authority, - boltSystem, - boltComponent, - componentProgram, - instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY, - anchorRemainingAccounts, - world, - }, - { - args, - }, - ); - const transaction = new Transaction().add(instruction); - return { - instruction, - transaction, - }; -} - interface ApplySystemInstruction { authority: PublicKey; systemId: PublicKey; @@ -398,7 +380,6 @@ async function createApplySystemInstruction({ const applyAccounts = { authority: authority ?? PROGRAM_ID, boltSystem: systemId, - instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY, world, }; diff --git a/crates/programs/bolt-component/Cargo.toml b/crates/programs/bolt-component/Cargo.toml index 8a5f372..8509503 100644 --- a/crates/programs/bolt-component/Cargo.toml +++ b/crates/programs/bolt-component/Cargo.toml @@ -24,5 +24,5 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { workspace = true } -bolt-system = { workspace = true, cpi = true } \ No newline at end of file +anchor-lang.workspace = true +bolt-system.workspace = true \ No newline at end of file diff --git a/crates/programs/bolt-system/Cargo.toml b/crates/programs/bolt-system/Cargo.toml index 776a47a..156c464 100644 --- a/crates/programs/bolt-system/Cargo.toml +++ b/crates/programs/bolt-system/Cargo.toml @@ -24,5 +24,5 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { workspace = true } -bolt-helpers-system-template = { workspace = true, cpi = true } \ No newline at end of file +anchor-lang.workspace = true +bolt-helpers-system-template.workspace = true diff --git a/crates/programs/world/Cargo.toml b/crates/programs/world/Cargo.toml index 5ee72a9..8e1baa1 100644 --- a/crates/programs/world/Cargo.toml +++ b/crates/programs/world/Cargo.toml @@ -24,10 +24,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { workspace = true } -bolt-component = { workspace = true, cpi = true } -bolt-helpers-world-apply = { workspace = true } -bolt-system = { workspace = true, cpi = true } -solana-security-txt = { workspace = true } -tuple-conv = { workspace = true } +anchor-lang.workspace = true +bolt-component.workspace = true +bolt-helpers-world-apply.workspace = true +bolt-system.workspace = true +solana-security-txt.workspace = true +tuple-conv.workspace = true diff --git a/tests/bolt.ts b/tests/bolt.intermediate-level.api.ts similarity index 98% rename from tests/bolt.ts rename to tests/bolt.intermediate-level.api.ts index 6e285ff..871a41c 100644 --- a/tests/bolt.ts +++ b/tests/bolt.intermediate-level.api.ts @@ -3,16 +3,14 @@ import { type Position } from "../target/types/position"; import { type Velocity } from "../target/types/velocity"; import { type BoltComponent } from "../target/types/bolt_component"; import { type SystemSimpleMovement } from "../target/types/system_simple_movement"; -import { type World } from "../target/types/world"; import { type SystemFly } from "../target/types/system_fly"; import { type SystemApplyVelocity } from "../target/types/system_apply_velocity"; import { expect } from "chai"; import type BN from "bn.js"; import { AddEntity, - createInitializeRegistryInstruction, DELEGATION_PROGRAM_ID, - FindRegistryPda, + InitializeRegistry, InitializeComponent, InitializeNewWorld, ApplySystem, @@ -24,6 +22,7 @@ import { type Program, anchor, web3, + WORLD_PROGRAM_IDL as World, } from "../clients/bolt-sdk"; enum Direction { @@ -106,13 +105,15 @@ describe("bolt", () => { const secondAuthority = Keypair.generate().publicKey; it("InitializeRegistry", async () => { - const registryPda = FindRegistryPda({}); - const initializeRegistryIx = createInitializeRegistryInstruction({ - registry: registryPda, + const initializeRegistry = await InitializeRegistry({ payer: provider.wallet.publicKey, + connection: provider.connection, }); - const tx = new anchor.web3.Transaction().add(initializeRegistryIx); - await provider.sendAndConfirm(tx); + try { + await provider.sendAndConfirm(initializeRegistry.transaction); + } catch (error) { + // This is expected to fail because the registry already exists if another api level test ran before + } }); it("InitializeNewWorld", async () => { @@ -216,7 +217,7 @@ describe("bolt", () => { const addEntity = await AddEntity({ payer: provider.wallet.publicKey, world: worldPda, - seed: Buffer.from("extra-seed"), + seed: Buffer.from("custom-seed"), connection: provider.connection, }); await provider.sendAndConfirm(addEntity.transaction); @@ -325,9 +326,7 @@ describe("bolt", () => { }, ], world: worldPda, - args: { - direction: Direction.Up, - }, + args: { direction: Direction.Up }, }); await provider.sendAndConfirm(apply.transaction); diff --git a/tests/bolt.low-level.api.ts b/tests/bolt.low-level.api.ts new file mode 100644 index 0000000..e01ddf9 --- /dev/null +++ b/tests/bolt.low-level.api.ts @@ -0,0 +1,870 @@ +import { Keypair, type PublicKey } from "@solana/web3.js"; +import { type Position } from "../target/types/position"; +import { type Velocity } from "../target/types/velocity"; +import { type BoltComponent } from "../target/types/bolt_component"; +import { type SystemSimpleMovement } from "../target/types/system_simple_movement"; +import { type SystemFly } from "../target/types/system_fly"; +import { type SystemApplyVelocity } from "../target/types/system_apply_velocity"; +import { expect } from "chai"; +import BN from "bn.js"; +import { + DELEGATION_PROGRAM_ID, + DelegateComponent, + type Program, + anchor, + web3, + FindRegistryPda, + FindWorldPda, + FindEntityPda, + FindComponentPda, + SerializeArgs, + WORLD_PROGRAM_IDL as World, +} from "../clients/bolt-sdk"; + +enum Direction { + Left = "Left", + Right = "Right", + Up = "Up", + Down = "Down", +} + +function padCenter(value: string, width: number) { + const length = value.length; + if (width <= length) { + return value; + } + const padding = (width - length) / 2; + const align = width - padding; + return value.padStart(align, " ").padEnd(width, " "); +} + +function logPosition(title: string, { x, y, z }: { x: BN; y: BN; z: BN }) { + console.log(" +----------------------------------+"); + console.log(` | ${padCenter(title, 32)} |`); + console.log(" +-----------------+----------------+"); + console.log(` | X Position | ${String(x).padEnd(14, " ")} |`); + console.log(` | Y Position | ${String(y).padEnd(14, " ")} |`); + console.log(` | Z Position | ${String(z).padEnd(14, " ")} |`); + console.log(" +-----------------+----------------+"); +} + +function logVelocity( + title: string, + { x, y, z, lastApplied }: { x: BN; y: BN; z: BN; lastApplied: BN }, +) { + console.log(" +----------------------------------+"); + console.log(` | ${padCenter(title, 32)} |`); + console.log(" +-----------------+----------------+"); + console.log(` | X Velocity | ${String(x).padEnd(14, " ")} |`); + console.log(` | Y Velocity | ${String(y).padEnd(14, " ")} |`); + console.log(` | Z Velocity | ${String(z).padEnd(14, " ")} |`); + console.log(` | Last Applied | ${String(lastApplied).padEnd(14, " ")} |`); + console.log(" +-----------------+----------------+"); +} + +describe("bolt", () => { + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + + const worldProgram = anchor.workspace.World as Program; + + const boltComponentProgram = anchor.workspace + .BoltComponent as Program; + + const exampleComponentPosition = anchor.workspace + .Position as Program; + const exampleComponentVelocity = anchor.workspace + .Velocity as Program; + + const exampleSystemSimpleMovement = ( + anchor.workspace.SystemSimpleMovement as Program + ).programId; + const exampleSystemFly = (anchor.workspace.SystemFly as Program) + .programId; + const exampleSystemApplyVelocity = ( + anchor.workspace.SystemApplyVelocity as Program + ).programId; + + let worldPda: PublicKey; + let worldId: BN; + + let entity1Pda: PublicKey; + let entity2Pda: PublicKey; + let entity4Pda: PublicKey; + let entity5Pda: PublicKey; + + let componentPositionEntity1Pda: PublicKey; + let componentVelocityEntity1Pda: PublicKey; + + let componentPositionEntity4Pda: PublicKey; + let componentPositionEntity5Pda: PublicKey; + + const secondAuthority = Keypair.generate().publicKey; + + it("InitializeRegistry", async () => { + const registryPda = FindRegistryPda({}); + const instruction = await worldProgram.methods + .initializeRegistry() + .accounts({ + registry: registryPda, + payer: provider.wallet.publicKey, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await provider.sendAndConfirm(transaction); + }); + + it("InitializeNewWorld", async () => { + const registryPda = FindRegistryPda({}); + const registry = await worldProgram.account.registry.fetch(registryPda); + worldId = new BN(registry.worlds); + worldPda = FindWorldPda({ worldId }); + const instruction = await worldProgram.methods + .initializeNewWorld() + .accounts({ + payer: provider.wallet.publicKey, + world: worldPda, + registry: registryPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log("InitializeNewWorld signature: ", signature); + }); + + it("Add authority", async () => { + const instruction = await worldProgram.methods + .addAuthority(worldId) + .accounts({ + authority: provider.wallet.publicKey, + newAuthority: provider.wallet.publicKey, + world: worldPda, + }) + .instruction(); + + const transaction = new anchor.web3.Transaction().add(instruction); + await provider.sendAndConfirm(transaction, [], { skipPreflight: true }); + const worldAccount = await worldProgram.account.world.fetch(worldPda); + expect( + worldAccount.authorities.some((auth) => + auth.equals(provider.wallet.publicKey), + ), + ); + }); + + it("Add a second authority", async () => { + const instruction = await worldProgram.methods + .addAuthority(worldId) + .accounts({ + authority: provider.wallet.publicKey, + newAuthority: secondAuthority, + world: worldPda, + }) + .instruction(); + + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log(`Add Authority signature: ${signature}`); + const worldAccount = await worldProgram.account.world.fetch(worldPda); + expect( + worldAccount.authorities.some((auth) => auth.equals(secondAuthority)), + ); + }); + + it("Remove an authority", async () => { + const instruction = await worldProgram.methods + .removeAuthority(worldId) + .accounts({ + authority: provider.wallet.publicKey, + authorityToDelete: secondAuthority, + world: worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log(`Remove Authority signature: ${signature}`); + const worldAccount = await worldProgram.account.world.fetch(worldPda); + expect( + !worldAccount.authorities.some((auth) => auth.equals(secondAuthority)), + ); + }); + + it("InitializeNewWorld 2", async () => { + const registryPda = FindRegistryPda({}); + const registry = await worldProgram.account.registry.fetch(registryPda); + const worldId = new BN(registry.worlds); + const worldPda = FindWorldPda({ worldId }); + const instruction = await worldProgram.methods + .initializeNewWorld() + .accounts({ + payer: provider.wallet.publicKey, + world: worldPda, + registry: registryPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log("InitializeNewWorld 2 signature: ", signature); + }); + + it("Add entity 1", async () => { + const world = await worldProgram.account.world.fetch(worldPda); + entity1Pda = FindEntityPda({ worldId: world.id, entityId: world.entities }); + const instruction = await worldProgram.methods + .addEntity(null) + .accounts({ + payer: provider.wallet.publicKey, + world: worldPda, + entity: entity1Pda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log("Add Entity 1 signature: ", signature); + }); + + it("Add entity 2", async () => { + const world = await worldProgram.account.world.fetch(worldPda); + entity2Pda = FindEntityPda({ worldId: world.id, entityId: world.entities }); + const instruction = await worldProgram.methods + .addEntity(null) + .accounts({ + payer: provider.wallet.publicKey, + world: worldPda, + entity: entity2Pda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log("Add Entity 2 signature: ", signature); + }); + + it("Add entity 3", async () => { + const world = await worldProgram.account.world.fetch(worldPda); + const entity3Pda = FindEntityPda({ + worldId: world.id, + entityId: world.entities, + }); + const instruction = await worldProgram.methods + .addEntity(null) + .accounts({ + payer: provider.wallet.publicKey, + world: worldPda, + entity: entity3Pda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log("Add Entity 3 signature: ", signature); + }); + + it("Add entity 4 (with seed)", async () => { + const world = await worldProgram.account.world.fetch(worldPda); + const seed = Buffer.from("custom-seed"); + entity4Pda = FindEntityPda({ worldId: world.id, seed }); + const instruction = await worldProgram.methods + .addEntity(seed) + .accounts({ + payer: provider.wallet.publicKey, + world: worldPda, + entity: entity4Pda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log("Add Entity 4 signature: ", signature); + }); + + it("Add entity 5", async () => { + const world = await worldProgram.account.world.fetch(worldPda); + entity5Pda = FindEntityPda({ worldId: world.id, entityId: world.entities }); + const instruction = await worldProgram.methods + .addEntity(null) + .accounts({ + payer: provider.wallet.publicKey, + world: worldPda, + entity: entity5Pda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log("Add Entity 5 signature: ", signature); + }); + + it("Initialize Original Component on Entity 1, through the world instance", async () => { + const componentId = boltComponentProgram.programId; + const componentPda = FindComponentPda({ + componentId, + entity: entity1Pda, + seed: "origin-component", + }); + const instruction = await worldProgram.methods + .initializeComponent() + .accounts({ + payer: provider.wallet.publicKey, + entity: entity1Pda, + data: componentPda, + componentProgram: componentId, + authority: provider.wallet.publicKey, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log( + "Initialize Original Component on Entity 1 signature: ", + signature, + ); + }); + + it("Initialize Original Component on Entity 2, trough the world instance", async () => { + const componentId = boltComponentProgram.programId; + const componentPda = FindComponentPda({ + componentId, + entity: entity2Pda, + seed: "origin-component", + }); + const instruction = await worldProgram.methods + .initializeComponent() + .accounts({ + payer: provider.wallet.publicKey, + entity: entity2Pda, + data: componentPda, + componentProgram: componentId, + authority: provider.wallet.publicKey, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log( + "Initialize Original Component on Entity 2 signature: ", + signature, + ); + }); + + it("Initialize Position Component on Entity 1", async () => { + const componentId = exampleComponentPosition.programId; + componentPositionEntity1Pda = FindComponentPda({ + componentId, + entity: entity1Pda, + }); + const instruction = await worldProgram.methods + .initializeComponent() + .accounts({ + payer: provider.wallet.publicKey, + entity: entity1Pda, + data: componentPositionEntity1Pda, + componentProgram: componentId, + authority: worldProgram.programId, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log( + "Initialize Position Component on Entity 1 signature: ", + signature, + ); + }); + + it("Initialize Velocity Component on Entity 1 (with seed)", async () => { + const componentId = exampleComponentVelocity.programId; + componentVelocityEntity1Pda = FindComponentPda({ + componentId, + entity: entity1Pda, + seed: "component-velocity", + }); + const instruction = await worldProgram.methods + .initializeComponent() + .accounts({ + payer: provider.wallet.publicKey, + entity: entity1Pda, + data: componentVelocityEntity1Pda, + componentProgram: componentId, + authority: worldProgram.programId, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log( + "Initialize Velocity Component on Entity 1 signature: ", + signature, + ); + }); + + it("Initialize Position Component on Entity 2", async () => { + const componentId = exampleComponentPosition.programId; + const componentPositionEntity2Pda = FindComponentPda({ + componentId, + entity: entity2Pda, + }); + const instruction = await worldProgram.methods + .initializeComponent() + .accounts({ + payer: provider.wallet.publicKey, + entity: entity2Pda, + data: componentPositionEntity2Pda, + componentProgram: componentId, + authority: worldProgram.programId, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log( + "Initialize Position Component on Entity 2 signature: ", + signature, + ); + }); + + it("Initialize Position Component on Entity 4", async () => { + const componentId = exampleComponentPosition.programId; + componentPositionEntity4Pda = FindComponentPda({ + componentId, + entity: entity4Pda, + }); + const instruction = await worldProgram.methods + .initializeComponent() + .accounts({ + payer: provider.wallet.publicKey, + entity: entity4Pda, + data: componentPositionEntity4Pda, + componentProgram: componentId, + authority: worldProgram.programId, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log( + "Initialize Position Component on Entity 4 signature: ", + signature, + ); + }); + + it("Initialize Position Component on Entity 5 (with authority)", async () => { + const componentId = exampleComponentPosition.programId; + componentPositionEntity5Pda = FindComponentPda({ + componentId, + entity: entity5Pda, + }); + const instruction = await worldProgram.methods + .initializeComponent() + .accounts({ + payer: provider.wallet.publicKey, + entity: entity5Pda, + data: componentPositionEntity5Pda, + componentProgram: componentId, + authority: provider.wallet.publicKey, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log( + "Initialize Position Component on Entity 5 signature: ", + signature, + ); + }); + + it("Check Position on Entity 1 is default", async () => { + const position = await exampleComponentPosition.account.position.fetch( + componentPositionEntity1Pda, + ); + logPosition("Default State: Entity 1", position); + expect(position.x.toNumber()).to.equal(0); + expect(position.y.toNumber()).to.equal(0); + expect(position.z.toNumber()).to.equal(0); + }); + + it("Apply Simple Movement System (Up) on Entity 1 using Apply", async () => { + const instruction = await worldProgram.methods + .apply(SerializeArgs({ direction: Direction.Up })) + .accounts({ + authority: provider.wallet.publicKey, + boltSystem: exampleSystemSimpleMovement, + boltComponent: componentPositionEntity1Pda, + componentProgram: exampleComponentPosition.programId, + world: worldPda, + }) + .instruction(); + + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log( + "Apply Simple Movement System (Up) on Entity 1 signature: ", + signature, + ); + + const position = await exampleComponentPosition.account.position.fetch( + componentPositionEntity1Pda, + ); + logPosition("Movement System: Entity 1", position); + expect(position.x.toNumber()).to.equal(0); + expect(position.y.toNumber()).to.equal(1); + expect(position.z.toNumber()).to.equal(0); + }); + + it("Apply Simple Movement System (Up) on Entity 1", async () => { + const instruction = await worldProgram.methods + .apply(SerializeArgs({ direction: Direction.Up })) + .accounts({ + authority: provider.wallet.publicKey, + boltSystem: exampleSystemSimpleMovement, + boltComponent: componentPositionEntity1Pda, + componentProgram: exampleComponentPosition.programId, + world: worldPda, + }) + .instruction(); + + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log( + "Apply Simple Movement System (Up) on Entity 1 signature: ", + signature, + ); + + const position = await exampleComponentPosition.account.position.fetch( + componentPositionEntity1Pda, + ); + logPosition("Movement System: Entity 1", position); + expect(position.x.toNumber()).to.equal(0); + expect(position.y.toNumber()).to.equal(2); + expect(position.z.toNumber()).to.equal(0); + }); + + it("Apply Simple Movement System (Right) on Entity 1", async () => { + const instruction = await worldProgram.methods + .apply(SerializeArgs({ direction: Direction.Right })) + .accounts({ + authority: provider.wallet.publicKey, + boltSystem: exampleSystemSimpleMovement, + boltComponent: componentPositionEntity1Pda, + componentProgram: exampleComponentPosition.programId, + world: worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log( + "Apply Simple Movement System (Right) on Entity 1 signature: ", + signature, + ); + + const position = await exampleComponentPosition.account.position.fetch( + componentPositionEntity1Pda, + ); + logPosition("Movement System: Entity 1", position); + expect(position.x.toNumber()).to.equal(1); + expect(position.y.toNumber()).to.equal(2); + expect(position.z.toNumber()).to.equal(0); + }); + + it("Apply Fly System on Entity 1", async () => { + const instruction = await worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: provider.wallet.publicKey, + boltSystem: exampleSystemFly, + boltComponent: componentPositionEntity1Pda, + componentProgram: exampleComponentPosition.programId, + world: worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log("Apply Fly System on Entity 1 signature: ", signature); + + const position = await exampleComponentPosition.account.position.fetch( + componentPositionEntity1Pda, + ); + logPosition("Fly System: Entity 1", position); + expect(position.x.toNumber()).to.equal(1); + expect(position.y.toNumber()).to.equal(2); + expect(position.z.toNumber()).to.equal(1); + }); + + it("Apply System Velocity on Entity 1", async () => { + const instruction = await worldProgram.methods + .apply2(SerializeArgs()) + .accounts({ + authority: provider.wallet.publicKey, + boltSystem: exampleSystemApplyVelocity, + boltComponent1: componentVelocityEntity1Pda, + componentProgram1: exampleComponentVelocity.programId, + boltComponent2: componentPositionEntity1Pda, + componentProgram2: exampleComponentPosition.programId, + world: worldPda, + }) + .remainingAccounts([]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction); + console.log("Apply System Velocity on Entity 1 signature: ", signature); + + const velocity = await exampleComponentVelocity.account.velocity.fetch( + componentVelocityEntity1Pda, + ); + logVelocity("Apply System Velocity: Entity 1", velocity); + expect(velocity.x.toNumber()).to.equal(10); + expect(velocity.y.toNumber()).to.equal(0); + expect(velocity.z.toNumber()).to.equal(0); + expect(velocity.lastApplied.toNumber()).to.not.equal(0); + + const position = await exampleComponentPosition.account.position.fetch( + componentPositionEntity1Pda, + ); + logPosition("Apply System Velocity: Entity 1", position); + expect(position.x.toNumber()).to.greaterThan(1); + expect(position.y.toNumber()).to.equal(2); + expect(position.z.toNumber()).to.equal(1); + }); + + it("Apply System Velocity on Entity 1, with Clock external account", async () => { + const instruction = await worldProgram.methods + .apply2(SerializeArgs()) + .accounts({ + authority: provider.wallet.publicKey, + boltSystem: exampleSystemApplyVelocity, + boltComponent1: componentVelocityEntity1Pda, + componentProgram1: exampleComponentVelocity.programId, + boltComponent2: componentPositionEntity1Pda, + componentProgram2: exampleComponentPosition.programId, + world: worldPda, + }) + .remainingAccounts([ + { + pubkey: new web3.PublicKey( + "SysvarC1ock11111111111111111111111111111111", + ), + isWritable: false, + isSigner: false, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await provider.sendAndConfirm(transaction); + + const position = await exampleComponentPosition.account.position.fetch( + componentPositionEntity1Pda, + ); + logPosition("Apply System Velocity: Entity 1", position); + expect(position.x.toNumber()).to.greaterThan(1); + expect(position.y.toNumber()).to.equal(2); + expect(position.z.toNumber()).to.equal(300); + }); + + it("Apply Fly System on Entity 4", async () => { + const instruction = await worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: provider.wallet.publicKey, + boltSystem: exampleSystemFly, + boltComponent: componentPositionEntity4Pda, + componentProgram: exampleComponentPosition.programId, + world: worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await provider.sendAndConfirm(transaction); + + const position = await exampleComponentPosition.account.position.fetch( + componentPositionEntity4Pda, + ); + logPosition("Fly System: Entity 4", position); + expect(position.x.toNumber()).to.equal(0); + expect(position.y.toNumber()).to.equal(0); + expect(position.z.toNumber()).to.equal(1); + }); + + it("Apply Fly System on Entity 5 (should fail with wrong authority)", async () => { + const positionBefore = + await exampleComponentPosition.account.position.fetch( + componentPositionEntity5Pda, + ); + + const instruction = await worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: provider.wallet.publicKey, + boltSystem: exampleSystemFly, + boltComponent: componentPositionEntity5Pda, + componentProgram: exampleComponentPosition.programId, + world: worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + + let failed = false; + try { + await provider.sendAndConfirm(transaction); + } catch (error) { + failed = true; + // console.log("error", error); + expect(error.logs.join("\n")).to.contain("Error Code: InvalidAuthority"); + } + expect(failed).to.equal(true); + + const positionAfter = await exampleComponentPosition.account.position.fetch( + componentPositionEntity5Pda, + ); + + expect(positionBefore.x.toNumber()).to.equal(positionAfter.x.toNumber()); + expect(positionBefore.y.toNumber()).to.equal(positionAfter.y.toNumber()); + expect(positionBefore.z.toNumber()).to.equal(positionAfter.z.toNumber()); + }); + + it("Whitelist System", async () => { + const instruction = await worldProgram.methods + .approveSystem() + .accounts({ + authority: provider.wallet.publicKey, + system: exampleSystemFly, + world: worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction, [], { + skipPreflight: true, + }); + console.log(`Whitelist 2 system approval signature: ${signature}`); + + // Get World and check permissionless and systems + const worldAccount = await worldProgram.account.world.fetch(worldPda); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); + + it("Whitelist System 2", async () => { + const instruction = await worldProgram.methods + .approveSystem() + .accounts({ + authority: provider.wallet.publicKey, + system: exampleSystemApplyVelocity, + world: worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction, [], { + skipPreflight: true, + }); + console.log(`Whitelist 2 system approval signature: ${signature}`); + + // Get World and check permissionless and systems + const worldAccount = await worldProgram.account.world.fetch(worldPda); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); + + it("Apply Fly System on Entity 1", async () => { + const instruction = await worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: provider.wallet.publicKey, + boltSystem: exampleSystemFly, + boltComponent: componentPositionEntity1Pda, + componentProgram: exampleComponentPosition.programId, + world: worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await provider.sendAndConfirm(transaction); + }); + + it("Remove System 1", async () => { + const instruction = await worldProgram.methods + .removeSystem() + .accounts({ + authority: provider.wallet.publicKey, + system: exampleSystemFly, + world: worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + const signature = await provider.sendAndConfirm(transaction, [], { + skipPreflight: true, + }); + console.log(`Remove System 1 signature: ${signature}`); + + // Get World and check permissionless and systems + const worldAccount = await worldProgram.account.world.fetch(worldPda); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); + + it("Apply Invalid Fly System on Entity 1", async () => { + const instruction = await worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: provider.wallet.publicKey, + boltSystem: exampleSystemFly, + boltComponent: componentPositionEntity1Pda, + componentProgram: exampleComponentPosition.programId, + world: worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + let invalid = false; + try { + await provider.sendAndConfirm(transaction); + } catch (error) { + expect(error.logs.join(" ")).to.contain("Error Code: SystemNotApproved"); + invalid = true; + } + expect(invalid).to.equal(true); + }); + + it("Check invalid component init without CPI", async () => { + let invalid = false; + try { + await exampleComponentPosition.methods + .initialize() + .accounts({ + payer: provider.wallet.publicKey, + data: componentPositionEntity5Pda, + entity: entity5Pda, + authority: provider.wallet.publicKey, + }) + .rpc(); + } catch (error) { + // console.log("error", error); + expect(error.message).to.contain("Error Code: InvalidCaller"); + invalid = true; + } + expect(invalid).to.equal(true); + }); + + it("Check invalid component update without CPI", async () => { + let invalid = false; + try { + await boltComponentProgram.methods + .update(Buffer.from("")) + .accounts({ + boltComponent: componentPositionEntity4Pda, + authority: provider.wallet.publicKey, + }) + .rpc(); + } catch (error) { + // console.log("error", error); + expect(error.message).to.contain( + "bolt_component. Error Code: AccountOwnedByWrongProgram", + ); + invalid = true; + } + expect(invalid).to.equal(true); + }); + + it("Check component delegation", async () => { + const delegateComponent = await DelegateComponent({ + payer: provider.wallet.publicKey, + entity: entity1Pda, + componentId: exampleComponentPosition.programId, + }); + const instruction = delegateComponent.transaction; + const transaction = new anchor.web3.Transaction().add(instruction); + const txSign = await provider.sendAndConfirm(transaction, [], { + skipPreflight: true, + commitment: "confirmed", + }); + console.log(`Delegation signature: ${txSign}`); + const acc = await provider.connection.getAccountInfo( + delegateComponent.componentPda, + ); + expect(acc?.owner.toBase58()).to.equal(DELEGATION_PROGRAM_ID.toBase58()); + }); +});