From 2c7161360fd83bd24dad092ba5e205c5ea183412 Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Fri, 24 Jan 2025 09:20:35 -0300 Subject: [PATCH] :sparkles: Session Keys --- clients/bolt-sdk/src/index.ts | 20 + clients/bolt-sdk/src/world/transactions.ts | 49 ++ .../attribute/bolt-program/src/lib.rs | 20 +- crates/programs/bolt-component/src/lib.rs | 2 + scripts/lint.sh | 6 +- tests/framework.ts | 6 - tests/intermediate-level/acceleration.ts | 12 +- .../permissioning/component.ts | 185 ++++---- .../intermediate-level/permissioning/index.ts | 1 - .../intermediate-level/permissioning/world.ts | 329 ++++++------- tests/intermediate-level/session.ts | 158 ++++++- tests/low-level/acceleration.ts | 2 +- tests/low-level/permissioning/component.ts | 281 +++++------ tests/low-level/permissioning/index.ts | 1 - tests/low-level/permissioning/world.ts | 445 +++++++++--------- tests/low-level/session.ts | 204 +++++++- 16 files changed, 1036 insertions(+), 685 deletions(-) diff --git a/clients/bolt-sdk/src/index.ts b/clients/bolt-sdk/src/index.ts index 1f1429b..4050591 100644 --- a/clients/bolt-sdk/src/index.ts +++ b/clients/bolt-sdk/src/index.ts @@ -76,6 +76,26 @@ export function FindEntityPda({ )[0]; } +export function FindSessionTokenPda({ + targetProgram, + sessionSigner, + authority, +}: { + targetProgram: PublicKey; + sessionSigner: PublicKey; + authority: PublicKey; +}) { + return PublicKey.findProgramAddressSync( + [ + Buffer.from("session_token"), + targetProgram.toBytes(), + sessionSigner.toBytes(), + authority.toBytes(), + ], + SessionProgram.programId, + )[0]; +} + // TODO: seed must be Uint8Array like the other FindPda functions export function FindComponentPda({ componentId, diff --git a/clients/bolt-sdk/src/world/transactions.ts b/clients/bolt-sdk/src/world/transactions.ts index 3a930a5..4c656c6 100644 --- a/clients/bolt-sdk/src/world/transactions.ts +++ b/clients/bolt-sdk/src/world/transactions.ts @@ -11,11 +11,14 @@ import { SerializeArgs, SYSVAR_INSTRUCTIONS_PUBKEY, World, + SessionProgram, + FindSessionTokenPda, } from "../index"; import BN from "bn.js"; import type web3 from "@solana/web3.js"; import { type Connection, + Keypair, type PublicKey, Transaction, type TransactionInstruction, @@ -47,6 +50,49 @@ export async function InitializeRegistry({ }; } +export async function CreateSession({ + sessionSigner, + authority, + targetProgram, + topUp, + validity, +}: { + sessionSigner?: Keypair; + authority: PublicKey; + targetProgram: PublicKey; + topUp?: boolean; + validity?: BN; +}): Promise<{ + instruction: TransactionInstruction; + transaction: Transaction; + sessionToken: PublicKey; + sessionSigner: Keypair; +}> { + sessionSigner = sessionSigner ?? Keypair.generate(); + const sessionToken = FindSessionTokenPda({ + targetProgram, + sessionSigner: sessionSigner.publicKey, + authority, + }); + topUp = topUp ?? false; + let instruction = await SessionProgram.methods + .createSession(topUp, validity ?? null) + .accounts({ + sessionSigner: sessionSigner.publicKey, + authority, + targetProgram, + sessionToken, + }) + .instruction(); + const transaction = new Transaction().add(instruction); + return { + instruction, + transaction, + sessionToken, + sessionSigner, + }; +} + /** * Create the transaction to Initialize a new world * @param payer @@ -438,6 +484,7 @@ export async function ApplySystem({ world, extraAccounts, args, + sessionToken, }: { authority: PublicKey; systemId: PublicKey; @@ -445,6 +492,7 @@ export async function ApplySystem({ world: PublicKey; extraAccounts?: web3.AccountMeta[]; args?: object; + sessionToken?: PublicKey; }): Promise<{ instruction: TransactionInstruction; transaction: Transaction }> { const instruction = await createApplySystemInstruction({ authority, @@ -453,6 +501,7 @@ export async function ApplySystem({ world, extraAccounts, args, + sessionToken, }); const transaction = new Transaction().add(instruction); return { diff --git a/crates/bolt-lang/attribute/bolt-program/src/lib.rs b/crates/bolt-lang/attribute/bolt-program/src/lib.rs index bf3ca4e..c2f2d13 100644 --- a/crates/bolt-lang/attribute/bolt-program/src/lib.rs +++ b/crates/bolt-lang/attribute/bolt-program/src/lib.rs @@ -150,14 +150,18 @@ fn generate_update(component_type: &Type) -> (TokenStream2, TokenStream2) { pub fn update(ctx: Context, data: Vec) -> Result<()> { // TODO: Should we also check if the session token authority can be the World::id? if let Some(session_token) = &ctx.accounts.session_token { - let validity_ctx = bolt_lang::session_keys::ValidityChecker { - session_token: session_token.clone(), - session_signer: ctx.accounts.authority.clone(), - authority: ctx.accounts.bolt_component.bolt_metadata.authority.clone(), - target_program: crate::id(), - }; - require!(session_token.validate(validity_ctx)?, bolt_lang::session_keys::SessionError::InvalidToken); - require_eq!(ctx.accounts.bolt_component.bolt_metadata.authority, session_token.authority, bolt_lang::session_keys::SessionError::InvalidToken); + if ctx.accounts.bolt_component.bolt_metadata.authority == World::id() { + require!(Clock::get()?.unix_timestamp < session_token.valid_until, bolt_lang::session_keys::SessionError::InvalidToken); + } else { + let validity_ctx = bolt_lang::session_keys::ValidityChecker { + session_token: session_token.clone(), + session_signer: ctx.accounts.authority.clone(), + authority: ctx.accounts.bolt_component.bolt_metadata.authority.clone(), + target_program: crate::id(), + }; + require!(session_token.validate(validity_ctx)?, bolt_lang::session_keys::SessionError::InvalidToken); + require_eq!(ctx.accounts.bolt_component.bolt_metadata.authority, session_token.authority, bolt_lang::session_keys::SessionError::InvalidToken); + } } else { require!(ctx.accounts.bolt_component.bolt_metadata.authority == World::id() || (ctx.accounts.bolt_component.bolt_metadata.authority == *ctx.accounts.authority.key && ctx.accounts.authority.is_signer), BoltError::InvalidAuthority); } diff --git a/crates/programs/bolt-component/src/lib.rs b/crates/programs/bolt-component/src/lib.rs index 8ea9724..b81d64a 100644 --- a/crates/programs/bolt-component/src/lib.rs +++ b/crates/programs/bolt-component/src/lib.rs @@ -17,6 +17,7 @@ pub mod bolt_component { #[derive(Accounts)] pub struct Update<'info> { #[account(mut)] + /// CHECK: The component to update pub bolt_component: UncheckedAccount<'info>, #[account()] /// CHECK: The authority of the component @@ -34,6 +35,7 @@ pub struct Initialize<'info> { #[account(mut)] pub payer: Signer<'info>, #[account(mut)] + /// CHECK: The component to initialize pub data: UncheckedAccount<'info>, #[account()] /// CHECK: A generic entity account diff --git a/scripts/lint.sh b/scripts/lint.sh index 73468d1..ea7fd04 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -3,11 +3,11 @@ SCRIPT_DIR=$(dirname "$0") PROJECT_DIR="$SCRIPT_DIR/.." pushd "$PROJECT_DIR" echo "### Checking formatting..." -cargo fmt -- --check --verbose +cargo fmt -- --verbose echo "### Checking clippy..." -cargo clippy -- --deny=warnings +cargo clippy --fix -- --deny=warnings echo "### Checking yarn lint..." -yarn lint +yarn lint --write popd diff --git a/tests/framework.ts b/tests/framework.ts index 60ab6ec..7b47d89 100644 --- a/tests/framework.ts +++ b/tests/framework.ts @@ -71,20 +71,14 @@ export class Framework { entity1Pda: PublicKey; entity2Pda: PublicKey; entity4Pda: PublicKey; - entity5Pda: PublicKey; componentPositionEntity1Pda: PublicKey; componentVelocityEntity1Pda: PublicKey; componentPositionEntity4Pda: PublicKey; - componentPositionEntity5Pda: PublicKey; - - sessionSigner: Keypair; - sessionToken: PublicKey; constructor() { this.secondAuthority = Keypair.generate().publicKey; - this.sessionSigner = Keypair.generate(); this.worldProgram = anchor.workspace.World; this.exampleComponentPosition = anchor.workspace.Position; this.exampleComponentVelocity = anchor.workspace.Velocity; diff --git a/tests/intermediate-level/acceleration.ts b/tests/intermediate-level/acceleration.ts index 18a1af0..0acd2bc 100644 --- a/tests/intermediate-level/acceleration.ts +++ b/tests/intermediate-level/acceleration.ts @@ -13,10 +13,14 @@ export function acceleration(framework) { componentId: framework.exampleComponentPosition.programId, }); - await framework.provider.sendAndConfirm(delegateComponent.transaction, [], { - skipPreflight: true, - commitment: "confirmed", - }); + await framework.provider.sendAndConfirm( + delegateComponent.transaction, + [], + { + skipPreflight: true, + commitment: "confirmed", + }, + ); const acc = await framework.provider.connection.getAccountInfo( delegateComponent.componentPda, ); diff --git a/tests/intermediate-level/permissioning/component.ts b/tests/intermediate-level/permissioning/component.ts index bb2e07a..d9c2a0d 100644 --- a/tests/intermediate-level/permissioning/component.ts +++ b/tests/intermediate-level/permissioning/component.ts @@ -1,106 +1,115 @@ import { expect } from "chai"; -import { AddEntity, ApplySystem, InitializeComponent } from "../../../clients/bolt-sdk/lib"; +import { + anchor, + AddEntity, + ApplySystem, + InitializeComponent, +} from "../../../clients/bolt-sdk/lib"; import { Keypair } from "@solana/web3.js"; export function component(framework) { - describe("Component authority", () => { - it("Add entity 5", async () => { - const addEntity = await AddEntity({ - payer: framework.provider.wallet.publicKey, - world: framework.worldPda, - connection: framework.provider.connection, - }); - await framework.provider.sendAndConfirm(addEntity.transaction); - framework.entity5Pda = addEntity.entityPda; // Saved for later - }); - - it("Initialize Position Component on Entity 5 (with authority)", async () => { - const initializeComponent = await InitializeComponent({ - payer: framework.provider.wallet.publicKey, - entity: framework.entity5Pda, - componentId: framework.exampleComponentPosition.programId, - authority: framework.provider.wallet.publicKey, - }); - await framework.provider.sendAndConfirm(initializeComponent.transaction); - framework.componentPositionEntity5Pda = initializeComponent.componentPda; // Saved for later - }); - - it("Apply Fly System on Entity 5 (should fail with wrong authority)", async () => { - const positionBefore = - await framework.exampleComponentPosition.account.position.fetch( - framework.componentPositionEntity5Pda, - ); + describe("Component authority", () => { + let entity: anchor.web3.PublicKey; + let component: anchor.web3.PublicKey; - let keypair = Keypair.generate(); + it("Add authority test entity", async () => { + const addEntity = await AddEntity({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addEntity.transaction); + entity = addEntity.entityPda; // Saved for later + }); + + it("Initialize position component with authority on authority test entity", async () => { + const initializeComponent = await InitializeComponent({ + payer: framework.provider.wallet.publicKey, + entity: entity, + componentId: framework.exampleComponentPosition.programId, + authority: framework.provider.wallet.publicKey, + }); + await framework.provider.sendAndConfirm(initializeComponent.transaction); + component = initializeComponent.componentPda; // Saved for later + }); - const applySystem = await ApplySystem({ - authority: keypair.publicKey, - systemId: framework.systemFly.programId, - world: framework.worldPda, - entities: [ - { - entity: framework.entity5Pda, - components: [ - { componentId: framework.exampleComponentPosition.programId }, - ], - }, + it("Shouldn't apply fly system on authority test entity with wrong authority", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + let keypair = Keypair.generate(); + + const applySystem = await ApplySystem({ + authority: keypair.publicKey, + systemId: framework.systemFly.programId, + world: framework.worldPda, + entities: [ + { + entity: entity, + components: [ + { componentId: framework.exampleComponentPosition.programId }, ], - }); - applySystem.transaction.recentBlockhash = ( - await framework.provider.connection.getLatestBlockhash() - ).blockhash; - applySystem.transaction.feePayer = framework.provider.wallet.publicKey; - applySystem.transaction.sign(keypair); + }, + ], + }); - let failed = false; - try { - await framework.provider.sendAndConfirm(applySystem.transaction); - } catch (error) { - failed = true; - expect(error.logs.join("\n")).to.contain("Error Code: InvalidAuthority"); - } - expect(failed).to.equal(true); + let failed = false; + try { + await framework.provider.sendAndConfirm(applySystem.transaction, [ + keypair, + ]); + } catch (error) { + failed = true; + expect(error.logs.join("\n")).to.contain( + "Error Code: InvalidAuthority", + ); + } + expect(failed).to.equal(true); - const positionAfter = - await framework.exampleComponentPosition.account.position.fetch( - framework.componentPositionEntity5Pda, - ); + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); - 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()); - }); + 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("Apply Fly System on Entity 5 should succeed with correct authority", async () => { - const positionBefore = - await framework.exampleComponentPosition.account.position.fetch( - framework.componentPositionEntity5Pda, - ); + it("Apply Fly System on authority test entity should succeed with correct authority", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); - const applySystem = await ApplySystem({ - authority: framework.provider.wallet.publicKey, - systemId: framework.systemFly.programId, - world: framework.worldPda, - entities: [ - { - entity: framework.entity5Pda, - components: [ - { componentId: framework.exampleComponentPosition.programId }, - ], - }, + const applySystem = await ApplySystem({ + authority: framework.provider.wallet.publicKey, + systemId: framework.systemFly.programId, + world: framework.worldPda, + entities: [ + { + entity, + components: [ + { componentId: framework.exampleComponentPosition.programId }, ], - }); + }, + ], + }); - await framework.provider.sendAndConfirm(applySystem.transaction); + await framework.provider.sendAndConfirm(applySystem.transaction); - const positionAfter = - await framework.exampleComponentPosition.account.position.fetch( - framework.componentPositionEntity5Pda, - ); + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); - expect(positionAfter.x.toNumber()).to.equal(positionBefore.x.toNumber()); - expect(positionAfter.y.toNumber()).to.equal(positionBefore.y.toNumber()); - expect(positionAfter.z.toNumber()).to.equal(positionBefore.z.toNumber() + 1); - }); + expect(positionAfter.x.toNumber()).to.equal(positionBefore.x.toNumber()); + expect(positionAfter.y.toNumber()).to.equal(positionBefore.y.toNumber()); + expect(positionAfter.z.toNumber()).to.equal( + positionBefore.z.toNumber() + 1, + ); }); + }); } diff --git a/tests/intermediate-level/permissioning/index.ts b/tests/intermediate-level/permissioning/index.ts index 75d7452..4658b11 100644 --- a/tests/intermediate-level/permissioning/index.ts +++ b/tests/intermediate-level/permissioning/index.ts @@ -7,4 +7,3 @@ export function permissioning(framework) { world(framework); }); } - diff --git a/tests/intermediate-level/permissioning/world.ts b/tests/intermediate-level/permissioning/world.ts index eda6937..73dfc5f 100644 --- a/tests/intermediate-level/permissioning/world.ts +++ b/tests/intermediate-level/permissioning/world.ts @@ -8,196 +8,161 @@ import { } from "../../../clients/bolt-sdk/lib"; export function world(framework) { - describe("World authority", () => { - it("Add authority", async () => { - const addAuthority = await AddAuthority({ - authority: framework.provider.wallet.publicKey, - newAuthority: framework.provider.wallet.publicKey, - world: framework.worldPda, - connection: framework.provider.connection, - }); - await framework.provider.sendAndConfirm(addAuthority.transaction, [], { - skipPreflight: true, - }); - const worldAccount = await framework.worldProgram.account.world.fetch( - framework.worldPda, - ); - expect( - worldAccount.authorities.some((auth) => - auth.equals(framework.provider.wallet.publicKey), - ), - ); - }); - - it("Add a second authority", async () => { - const addAuthority = await AddAuthority({ - authority: framework.provider.wallet.publicKey, - newAuthority: framework.secondAuthority, - world: framework.worldPda, - connection: framework.provider.connection, - }); - await framework.provider.sendAndConfirm(addAuthority.transaction); - const worldAccount = await framework.worldProgram.account.world.fetch( - framework.worldPda, - ); - expect( - worldAccount.authorities.some((auth) => - auth.equals(framework.secondAuthority), - ), - ); - }); - - it("Remove an authority", async () => { - const removeAuthority = await RemoveAuthority({ - authority: framework.provider.wallet.publicKey, - authorityToDelete: framework.secondAuthority, - world: framework.worldPda, - connection: framework.provider.connection, - }); - await framework.provider.sendAndConfirm(removeAuthority.transaction); - const worldAccount = await framework.worldProgram.account.world.fetch( - framework.worldPda, - ); - expect( - !worldAccount.authorities.some((auth) => - auth.equals(framework.secondAuthority), - ), - ); - }); - - it("Whitelist System", async () => { - const approveSystem = await ApproveSystem({ - authority: framework.provider.wallet.publicKey, - systemToApprove: framework.systemFly.programId, - world: framework.worldPda, - }); - - await framework.provider.sendAndConfirm(approveSystem.transaction, [], { - skipPreflight: true, - }); + describe("World authority", () => { + it("Add authority", async () => { + const addAuthority = await AddAuthority({ + authority: framework.provider.wallet.publicKey, + newAuthority: framework.provider.wallet.publicKey, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addAuthority.transaction, [], { + skipPreflight: true, + }); + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect( + worldAccount.authorities.some((auth) => + auth.equals(framework.provider.wallet.publicKey), + ), + ); + }); - // Get World and check permissionless and systems - const worldAccount = await framework.worldProgram.account.world.fetch( - framework.worldPda, - ); - expect(worldAccount.permissionless).to.equal(false); - expect(worldAccount.systems.length).to.be.greaterThan(0); - }); + it("Add a second authority", async () => { + const addAuthority = await AddAuthority({ + authority: framework.provider.wallet.publicKey, + newAuthority: framework.secondAuthority, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addAuthority.transaction); + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect( + worldAccount.authorities.some((auth) => + auth.equals(framework.secondAuthority), + ), + ); + }); - it("Whitelist System 2", async () => { - const approveSystem = await ApproveSystem({ - authority: framework.provider.wallet.publicKey, - systemToApprove: framework.systemApplyVelocity.programId, - world: framework.worldPda, - }); + it("Remove an authority", async () => { + const removeAuthority = await RemoveAuthority({ + authority: framework.provider.wallet.publicKey, + authorityToDelete: framework.secondAuthority, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(removeAuthority.transaction); + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect( + !worldAccount.authorities.some((auth) => + auth.equals(framework.secondAuthority), + ), + ); + }); - await framework.provider.sendAndConfirm(approveSystem.transaction, [], { - skipPreflight: true, - }); + it("Whitelist Fly System", async () => { + const approveSystem = await ApproveSystem({ + authority: framework.provider.wallet.publicKey, + systemToApprove: framework.systemFly.programId, + world: framework.worldPda, + }); + + await framework.provider.sendAndConfirm(approveSystem.transaction, [], { + skipPreflight: true, + }); + + // Get World and check permissionless and systems + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); - // Get World and check permissionless and systems - const worldAccount = await framework.worldProgram.account.world.fetch( - framework.worldPda, - ); - expect(worldAccount.permissionless).to.equal(false); - expect(worldAccount.systems.length).to.be.greaterThan(0); - }); + it("Whitelist Apply Velocity System system", async () => { + const approveSystem = await ApproveSystem({ + authority: framework.provider.wallet.publicKey, + systemToApprove: framework.systemApplyVelocity.programId, + world: framework.worldPda, + }); + + await framework.provider.sendAndConfirm(approveSystem.transaction, [], { + skipPreflight: true, + }); + + // Get World and check permissionless and systems + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); - it("Apply Fly System on Entity 1", async () => { - const applySystem = await ApplySystem({ - authority: framework.provider.wallet.publicKey, - systemId: framework.systemFly.programId, - world: framework.worldPda, - entities: [ - { - entity: framework.entity1Pda, - components: [ - { componentId: framework.exampleComponentPosition.programId }, - ], - }, + it("Apply Fly System on Entity 1", async () => { + const applySystem = await ApplySystem({ + authority: framework.provider.wallet.publicKey, + systemId: framework.systemFly.programId, + world: framework.worldPda, + entities: [ + { + entity: framework.entity1Pda, + components: [ + { componentId: framework.exampleComponentPosition.programId }, ], - }); - await framework.provider.sendAndConfirm(applySystem.transaction); - }); - - it("Remove System 1", async () => { - const removeSystem = await RemoveSystem({ - authority: framework.provider.wallet.publicKey, - systemToRemove: framework.systemFly.programId, - world: framework.worldPda, - }); - - await framework.provider.sendAndConfirm(removeSystem.transaction, [], { - skipPreflight: true, - }); + }, + ], + }); + await framework.provider.sendAndConfirm(applySystem.transaction); + }); - // Get World and check permissionless and systems - const worldAccount = await framework.worldProgram.account.world.fetch( - framework.worldPda, - ); - expect(worldAccount.permissionless).to.equal(false); - expect(worldAccount.systems.length).to.be.greaterThan(0); - }); + it("Remove Fly System", async () => { + const removeSystem = await RemoveSystem({ + authority: framework.provider.wallet.publicKey, + systemToRemove: framework.systemFly.programId, + world: framework.worldPda, + }); + + await framework.provider.sendAndConfirm(removeSystem.transaction, [], { + skipPreflight: true, + }); + + // Get World and check permissionless and systems + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); - it("Apply unauthorized Fly System on Entity 1", async () => { - const applySystem = await ApplySystem({ - authority: framework.provider.wallet.publicKey, - systemId: framework.systemFly.programId, - world: framework.worldPda, - entities: [ - { - entity: framework.entity1Pda, - components: [ - { componentId: framework.exampleComponentPosition.programId }, - ], - }, + it("Apply unauthorized Fly System on Entity 1", async () => { + const applySystem = await ApplySystem({ + authority: framework.provider.wallet.publicKey, + systemId: framework.systemFly.programId, + world: framework.worldPda, + entities: [ + { + entity: framework.entity1Pda, + components: [ + { componentId: framework.exampleComponentPosition.programId }, ], - }); - let invalid = false; - try { - await framework.provider.sendAndConfirm(applySystem.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 framework.exampleComponentPosition.methods - .initialize() - .accounts({ - payer: framework.provider.wallet.publicKey, - data: framework.componentPositionEntity5Pda, - entity: framework.entity5Pda, - authority: framework.provider.wallet.publicKey, - }) - .rpc(); - } catch (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 framework.exampleComponentPosition.methods - .update(Buffer.from("")) - .accounts({ - boltComponent: framework.componentPositionEntity4Pda, - authority: framework.provider.wallet.publicKey, - sessionToken: null, - }) - .rpc(); - } catch (error) { - expect(error.message).to.contain("Error Code: InvalidCaller"); - invalid = true; - } - expect(invalid).to.equal(true); - }); + }, + ], + }); + let invalid = false; + try { + await framework.provider.sendAndConfirm(applySystem.transaction); + } catch (error) { + expect(error.logs.join(" ")).to.contain( + "Error Code: SystemNotApproved", + ); + invalid = true; + } + expect(invalid).to.equal(true); }); + }); } diff --git a/tests/intermediate-level/session.ts b/tests/intermediate-level/session.ts index 2d5ac03..bf02f4a 100644 --- a/tests/intermediate-level/session.ts +++ b/tests/intermediate-level/session.ts @@ -1,29 +1,151 @@ -import { anchor, SessionProgram } from "../../clients/bolt-sdk/lib"; +import { Keypair } from "@solana/web3.js"; +import { + AddEntity, + CreateSession, + InitializeComponent, + ApplySystem, + anchor, +} from "../../clients/bolt-sdk/lib"; +import { expect } from "chai"; // TODO: Create the API for it. export function session(framework) { describe("Session", () => { + let sessionSigner: Keypair; + let sessionToken: anchor.web3.PublicKey; + let entity: anchor.web3.PublicKey; + let component: anchor.web3.PublicKey; + let entityWithAuthority: anchor.web3.PublicKey; + let componentWithAuthority: anchor.web3.PublicKey; + it("Create Session", async () => { - const airdrop = await framework.provider.connection.requestAirdrop( - framework.sessionSigner.publicKey, - anchor.web3.LAMPORTS_PER_SOL, - ); + const createSession = await CreateSession({ + authority: framework.provider.wallet.publicKey, + targetProgram: framework.exampleComponentPosition.programId, + topUp: true, + }); + sessionSigner = createSession.sessionSigner; + sessionToken = createSession.sessionToken; + await framework.provider.sendAndConfirm(createSession.transaction, [ + sessionSigner, + ]); + }); + + it("Add entity 1", async () => { + const addEntity = await AddEntity({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addEntity.transaction); + entity = addEntity.entityPda; + }); + + it("Initialize position component", async () => { + const initializeComponent = await InitializeComponent({ + payer: sessionSigner.publicKey, + entity: entity, + componentId: framework.exampleComponentPosition.programId, + }); + await framework.provider.sendAndConfirm(initializeComponent.transaction, [ + sessionSigner, + ]); + component = initializeComponent.componentPda; + }); + + it("Apply Fly System on component using session token", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + const applySystem = await ApplySystem({ + authority: sessionSigner.publicKey, + systemId: framework.systemFly.programId, + world: framework.worldPda, + sessionToken, + entities: [ + { + entity: entity, + components: [ + { componentId: framework.exampleComponentPosition.programId }, + ], + }, + ], + }); + await framework.provider.sendAndConfirm(applySystem.transaction, [ + sessionSigner, + ]); + + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); - await framework.provider.connection.confirmTransaction( - airdrop, - "confirmed", + expect(positionAfter.x.toNumber()).to.equal(positionBefore.x.toNumber()); + expect(positionAfter.y.toNumber()).to.equal(positionBefore.y.toNumber()); + expect(positionAfter.z.toNumber()).to.equal( + positionBefore.z.toNumber() + 1, ); + }); + + it("Add entity for authority test", async () => { + const addEntity = await AddEntity({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addEntity.transaction); + entityWithAuthority = addEntity.entityPda; + }); + + it("Initialize position component with authority", async () => { + const initializeComponent = await InitializeComponent({ + payer: sessionSigner.publicKey, + entity: entityWithAuthority, + componentId: framework.exampleComponentPosition.programId, + authority: framework.provider.wallet.publicKey, + }); + await framework.provider.sendAndConfirm(initializeComponent.transaction, [ + sessionSigner, + ]); + componentWithAuthority = initializeComponent.componentPda; + }); - const keys = await SessionProgram.methods - .createSession(true, null) - .accounts({ - sessionSigner: framework.sessionSigner.publicKey, - authority: framework.provider.wallet.publicKey, - targetProgram: framework.exampleComponentPosition.programId, - }) - .signers([framework.sessionSigner]) - .rpcAndKeys(); - framework.sessionToken = keys.pubkeys.sessionToken as anchor.web3.PublicKey; + it("Apply Fly System on component with authority using session token", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + componentWithAuthority, + ); + + const applySystem = await ApplySystem({ + authority: sessionSigner.publicKey, + systemId: framework.systemFly.programId, + world: framework.worldPda, + sessionToken, + entities: [ + { + entity: entityWithAuthority, + components: [ + { componentId: framework.exampleComponentPosition.programId }, + ], + }, + ], + }); + await framework.provider.sendAndConfirm(applySystem.transaction, [ + sessionSigner, + ]); + + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + componentWithAuthority, + ); + + expect(positionAfter.x.toNumber()).to.equal(positionBefore.x.toNumber()); + expect(positionAfter.y.toNumber()).to.equal(positionBefore.y.toNumber()); + expect(positionAfter.z.toNumber()).to.equal( + positionBefore.z.toNumber() + 1, + ); }); }); } diff --git a/tests/low-level/acceleration.ts b/tests/low-level/acceleration.ts index b183d3a..6f77068 100644 --- a/tests/low-level/acceleration.ts +++ b/tests/low-level/acceleration.ts @@ -25,4 +25,4 @@ export function acceleration(framework) { expect(acc?.owner.toBase58()).to.equal(DELEGATION_PROGRAM_ID.toBase58()); }); }); -}; +} diff --git a/tests/low-level/permissioning/component.ts b/tests/low-level/permissioning/component.ts index 218800a..f6e1f13 100644 --- a/tests/low-level/permissioning/component.ts +++ b/tests/low-level/permissioning/component.ts @@ -1,142 +1,149 @@ import { Keypair } from "@solana/web3.js"; -import { anchor, FindEntityPda, FindComponentPda, SerializeArgs } from "../../../clients/bolt-sdk/lib"; +import { + anchor, + FindEntityPda, + FindComponentPda, + SerializeArgs, +} from "../../../clients/bolt-sdk/lib"; import { expect } from "chai"; export function component(framework) { - describe("Component authority", () => { - it("Add entity 5", async () => { - const world = await framework.worldProgram.account.world.fetch( - framework.worldPda, - ); - framework.entity5Pda = FindEntityPda({ - worldId: world.id, - entityId: world.entities, - }); - const instruction = await framework.worldProgram.methods - .addEntity(null) - .accounts({ - payer: framework.provider.wallet.publicKey, - world: framework.worldPda, - entity: framework.entity5Pda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - await framework.provider.sendAndConfirm(transaction); - }); - - it("Initialize Position Component on Entity 5 (with authority)", async () => { - const componentId = framework.exampleComponentPosition.programId; - framework.componentPositionEntity5Pda = FindComponentPda({ - componentId, - entity: framework.entity5Pda, - }); - const instruction = await framework.worldProgram.methods - .initializeComponent() - .accounts({ - payer: framework.provider.wallet.publicKey, - entity: framework.entity5Pda, - data: framework.componentPositionEntity5Pda, - componentProgram: componentId, - authority: framework.provider.wallet.publicKey, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - await framework.provider.sendAndConfirm(transaction); - }); - - it("Apply Fly System on Entity 5 (should fail with wrong authority)", async () => { - const positionBefore = - await framework.exampleComponentPosition.account.position.fetch( - framework.componentPositionEntity5Pda, - ); - - let keypair = Keypair.generate(); - - const instruction = await framework.worldProgram.methods - .apply(SerializeArgs()) - .accounts({ - authority: keypair.publicKey, - boltSystem: framework.systemFly.programId, - world: framework.worldPda, - sessionToken: null, - }) - .remainingAccounts([ - { - pubkey: framework.exampleComponentPosition.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: framework.componentPositionEntity5Pda, - isSigner: false, - isWritable: true, - }, - ]) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - transaction.recentBlockhash = ( - await framework.provider.connection.getLatestBlockhash() - ).blockhash; - transaction.feePayer = framework.provider.wallet.publicKey; - transaction.sign(keypair); - - let failed = false; - try { - await framework.provider.sendAndConfirm(transaction); - } catch (error) { - failed = true; - expect(error.logs.join("\n")).to.contain("Error Code: InvalidAuthority"); - } - expect(failed).to.equal(true); - - const positionAfter = - await framework.exampleComponentPosition.account.position.fetch( - framework.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("Apply Fly System on Entity 5 should succeed with correct authority", async () => { - const positionBefore = - await framework.exampleComponentPosition.account.position.fetch( - framework.componentPositionEntity5Pda, - ); - - const instruction = await framework.worldProgram.methods - .apply(SerializeArgs()) - .accounts({ - authority: framework.provider.wallet.publicKey, - boltSystem: framework.systemFly.programId, - world: framework.worldPda, - sessionToken: null, - }) - .remainingAccounts([ - { - pubkey: framework.exampleComponentPosition.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: framework.componentPositionEntity5Pda, - isSigner: false, - isWritable: true, - }, - ]) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - - await framework.provider.sendAndConfirm(transaction); - const positionAfter = - await framework.exampleComponentPosition.account.position.fetch( - framework.componentPositionEntity5Pda, - ); - - expect(positionAfter.x.toNumber()).to.equal(positionBefore.x.toNumber()); - expect(positionAfter.y.toNumber()).to.equal(positionBefore.y.toNumber()); - expect(positionAfter.z.toNumber()).to.equal(positionBefore.z.toNumber() + 1); - }); + let entity: anchor.web3.PublicKey; + let component: anchor.web3.PublicKey; + + describe("Component authority", () => { + it("Add entity 5", async () => { + const world = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + entity = FindEntityPda({ + worldId: world.id, + entityId: world.entities, + }); + const instruction = await framework.worldProgram.methods + .addEntity(null) + .accounts({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + entity: entity, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Initialize position component with authority on authority test entity", async () => { + const componentId = framework.exampleComponentPosition.programId; + component = FindComponentPda({ + componentId, + entity: entity, + }); + const instruction = await framework.worldProgram.methods + .initializeComponent() + .accounts({ + payer: framework.provider.wallet.publicKey, + entity: entity, + data: component, + componentProgram: componentId, + authority: framework.provider.wallet.publicKey, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Shouldn't apply fly system on authority test entity with wrong authority", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + let keypair = Keypair.generate(); + + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: keypair.publicKey, + boltSystem: framework.systemFly.programId, + world: framework.worldPda, + sessionToken: null, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: component, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + + let failed = false; + try { + await framework.provider.sendAndConfirm(transaction, [keypair]); + } catch (error) { + failed = true; + expect(error.logs.join("\n")).to.contain( + "Error Code: InvalidAuthority", + ); + } + expect(failed).to.equal(true); + + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + 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("Apply Fly System on authority test entity should succeed with correct authority", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: framework.provider.wallet.publicKey, + boltSystem: framework.systemFly.programId, + world: framework.worldPda, + sessionToken: null, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: component, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + + await framework.provider.sendAndConfirm(transaction); + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + expect(positionAfter.x.toNumber()).to.equal(positionBefore.x.toNumber()); + expect(positionAfter.y.toNumber()).to.equal(positionBefore.y.toNumber()); + expect(positionAfter.z.toNumber()).to.equal( + positionBefore.z.toNumber() + 1, + ); }); -} \ No newline at end of file + }); +} diff --git a/tests/low-level/permissioning/index.ts b/tests/low-level/permissioning/index.ts index 75d7452..4658b11 100644 --- a/tests/low-level/permissioning/index.ts +++ b/tests/low-level/permissioning/index.ts @@ -7,4 +7,3 @@ export function permissioning(framework) { world(framework); }); } - diff --git a/tests/low-level/permissioning/world.ts b/tests/low-level/permissioning/world.ts index 832c9fe..c8984a3 100644 --- a/tests/low-level/permissioning/world.ts +++ b/tests/low-level/permissioning/world.ts @@ -1,236 +1,237 @@ import { expect } from "chai"; import { anchor, SerializeArgs } from "../../../clients/bolt-sdk/lib"; - export function world(framework) { - describe("World authority", () => { - it("Add authority", async () => { - const instruction = await framework.worldProgram.methods - .addAuthority(framework.worldId) - .accounts({ - authority: framework.provider.wallet.publicKey, - newAuthority: framework.provider.wallet.publicKey, - world: framework.worldPda, - }) - .instruction(); - - const transaction = new anchor.web3.Transaction().add(instruction); - await framework.provider.sendAndConfirm(transaction, [], { - skipPreflight: true, - }); - const worldAccount = await framework.worldProgram.account.world.fetch( - framework.worldPda, - ); - expect( - worldAccount.authorities.some((auth) => - auth.equals(framework.provider.wallet.publicKey), - ), - ); - }); + describe("World authority", () => { + it("Add authority", async () => { + const instruction = await framework.worldProgram.methods + .addAuthority(framework.worldId) + .accounts({ + authority: framework.provider.wallet.publicKey, + newAuthority: framework.provider.wallet.publicKey, + world: framework.worldPda, + }) + .instruction(); + + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [], { + skipPreflight: true, + }); + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect( + worldAccount.authorities.some((auth) => + auth.equals(framework.provider.wallet.publicKey), + ), + ); + }); - it("Add a second authority", async () => { - const instruction = await framework.worldProgram.methods - .addAuthority(framework.worldId) - .accounts({ - authority: framework.provider.wallet.publicKey, - newAuthority: framework.secondAuthority, - world: framework.worldPda, - }) - .instruction(); - - const transaction = new anchor.web3.Transaction().add(instruction); - await framework.provider.sendAndConfirm(transaction); - const worldAccount = await framework.worldProgram.account.world.fetch( - framework.worldPda, - ); - expect( - worldAccount.authorities.some((auth) => - auth.equals(framework.secondAuthority), - ), - ); - }); + it("Add a second authority", async () => { + const instruction = await framework.worldProgram.methods + .addAuthority(framework.worldId) + .accounts({ + authority: framework.provider.wallet.publicKey, + newAuthority: framework.secondAuthority, + world: framework.worldPda, + }) + .instruction(); - it("Remove an authority", async () => { - const instruction = await framework.worldProgram.methods - .removeAuthority(framework.worldId) - .accounts({ - authority: framework.provider.wallet.publicKey, - authorityToDelete: framework.secondAuthority, - world: framework.worldPda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - await framework.provider.sendAndConfirm(transaction); - const worldAccount = await framework.worldProgram.account.world.fetch( - framework.worldPda, - ); - expect( - !worldAccount.authorities.some((auth) => - auth.equals(framework.secondAuthority), - ), - ); - }); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect( + worldAccount.authorities.some((auth) => + auth.equals(framework.secondAuthority), + ), + ); + }); - it("Whitelist System", async () => { - const instruction = await framework.worldProgram.methods - .approveSystem() - .accounts({ - authority: framework.provider.wallet.publicKey, - system: framework.systemFly.programId, - world: framework.worldPda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - await framework.provider.sendAndConfirm(transaction, [], { - skipPreflight: true, - }); - - // Get World and check permissionless and systems - const worldAccount = await framework.worldProgram.account.world.fetch( - framework.worldPda, - ); - expect(worldAccount.permissionless).to.equal(false); - expect(worldAccount.systems.length).to.be.greaterThan(0); - }); + it("Remove an authority", async () => { + const instruction = await framework.worldProgram.methods + .removeAuthority(framework.worldId) + .accounts({ + authority: framework.provider.wallet.publicKey, + authorityToDelete: framework.secondAuthority, + world: framework.worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect( + !worldAccount.authorities.some((auth) => + auth.equals(framework.secondAuthority), + ), + ); + }); - it("Whitelist System 2", async () => { - const instruction = await framework.worldProgram.methods - .approveSystem() - .accounts({ - authority: framework.provider.wallet.publicKey, - system: framework.systemApplyVelocity.programId, - world: framework.worldPda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - await framework.provider.sendAndConfirm(transaction, [], { - skipPreflight: true, - }); - - // Get World and check permissionless and systems - const worldAccount = await framework.worldProgram.account.world.fetch( - framework.worldPda, - ); - expect(worldAccount.permissionless).to.equal(false); - expect(worldAccount.systems.length).to.be.greaterThan(0); - }); + it("Whitelist Fly System", async () => { + const instruction = await framework.worldProgram.methods + .approveSystem() + .accounts({ + authority: framework.provider.wallet.publicKey, + system: framework.systemFly.programId, + world: framework.worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [], { + skipPreflight: true, + }); - it("Apply Fly System on Entity 1", async () => { - const instruction = await framework.worldProgram.methods - .apply(SerializeArgs()) - .accounts({ - authority: framework.provider.wallet.publicKey, - boltSystem: framework.systemFly.programId, - world: framework.worldPda, - sessionToken: null, - }) - .remainingAccounts([ - { - pubkey: framework.exampleComponentPosition.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: framework.componentPositionEntity1Pda, - isSigner: false, - isWritable: true, - }, - ]) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - await framework.provider.sendAndConfirm(transaction); - }); + // Get World and check permissionless and systems + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); - it("Remove System 1", async () => { - const instruction = await framework.worldProgram.methods - .removeSystem() - .accounts({ - authority: framework.provider.wallet.publicKey, - system: framework.systemFly.programId, - world: framework.worldPda, - }) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - await framework.provider.sendAndConfirm(transaction, [], { - skipPreflight: true, - }); - - // Get World and check permissionless and systems - const worldAccount = await framework.worldProgram.account.world.fetch( - framework.worldPda, - ); - expect(worldAccount.permissionless).to.equal(false); - expect(worldAccount.systems.length).to.be.greaterThan(0); - }); + it("Whitelist Apply Velocity System system", async () => { + const instruction = await framework.worldProgram.methods + .approveSystem() + .accounts({ + authority: framework.provider.wallet.publicKey, + system: framework.systemApplyVelocity.programId, + world: framework.worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [], { + skipPreflight: true, + }); - it("Apply unauthorized Fly System on Entity 1", async () => { - const instruction = await framework.worldProgram.methods - .apply(SerializeArgs()) - .accounts({ - authority: framework.provider.wallet.publicKey, - boltSystem: framework.systemFly.programId, - world: framework.worldPda, - sessionToken: null, - }) - .remainingAccounts([ - { - pubkey: framework.exampleComponentPosition.programId, - isSigner: false, - isWritable: false, - }, - { - pubkey: framework.componentPositionEntity1Pda, - isSigner: false, - isWritable: true, - }, - ]) - .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); - let invalid = false; - try { - await framework.provider.sendAndConfirm(transaction); - } catch (error) { - expect(error.logs.join(" ")).to.contain("Error Code: SystemNotApproved"); - invalid = true; - } - expect(invalid).to.equal(true); - }); + // Get World and check permissionless and systems + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); - it("Check invalid component init without CPI", async () => { - let invalid = false; - try { - await framework.exampleComponentPosition.methods - .initialize() - .accounts({ - payer: framework.provider.wallet.publicKey, - data: framework.componentPositionEntity5Pda, - entity: framework.entity5Pda, - authority: framework.provider.wallet.publicKey, - }) - .rpc(); - } catch (error) { - expect(error.message).to.contain("Error Code: InvalidCaller"); - invalid = true; - } - expect(invalid).to.equal(true); - }); + it("Apply Fly System on Entity 1", async () => { + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: framework.provider.wallet.publicKey, + boltSystem: framework.systemFly.programId, + world: framework.worldPda, + sessionToken: null, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: framework.componentPositionEntity1Pda, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction); + }); + + it("Remove Fly System", async () => { + const instruction = await framework.worldProgram.methods + .removeSystem() + .accounts({ + authority: framework.provider.wallet.publicKey, + system: framework.systemFly.programId, + world: framework.worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [], { + skipPreflight: true, + }); + + // Get World and check permissionless and systems + const worldAccount = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + expect(worldAccount.permissionless).to.equal(false); + expect(worldAccount.systems.length).to.be.greaterThan(0); + }); + + it("Apply unauthorized Fly System on Entity 1", async () => { + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: framework.provider.wallet.publicKey, + boltSystem: framework.systemFly.programId, + world: framework.worldPda, + sessionToken: null, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: framework.componentPositionEntity1Pda, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + let invalid = false; + try { + await framework.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 framework.exampleComponentPosition.methods + .initialize() + .accounts({ + payer: framework.provider.wallet.publicKey, + data: framework.componentPositionEntity1Pda, + entity: framework.entity1Pda, + authority: framework.provider.wallet.publicKey, + }) + .rpc(); + } catch (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 framework.exampleComponentPosition.methods - .update(Buffer.from("")) - .accounts({ - boltComponent: framework.componentPositionEntity4Pda, - authority: framework.provider.wallet.publicKey, - sessionToken: null, - }) - .rpc(); - } catch (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 framework.exampleComponentPosition.methods + .update(Buffer.from("")) + .accounts({ + boltComponent: framework.componentPositionEntity4Pda, + authority: framework.provider.wallet.publicKey, + sessionToken: null, + }) + .rpc(); + } catch (error) { + expect(error.message).to.contain("Error Code: InvalidCaller"); + invalid = true; + } + expect(invalid).to.equal(true); }); -} \ No newline at end of file + }); +} diff --git a/tests/low-level/session.ts b/tests/low-level/session.ts index 3c1aeff..b96ceb1 100644 --- a/tests/low-level/session.ts +++ b/tests/low-level/session.ts @@ -1,28 +1,204 @@ -import { anchor, SessionProgram } from "../../clients/bolt-sdk/lib"; +import { expect } from "chai"; +import { + anchor, + FindComponentPda, + FindEntityPda, + SerializeArgs, + SessionProgram, + FindSessionTokenPda, +} from "../../clients/bolt-sdk/lib"; +import { Keypair } from "@solana/web3.js"; export function session(framework) { describe("Session", () => { + const sessionSigner: Keypair = Keypair.generate(); + let sessionToken: anchor.web3.PublicKey; + let entity: anchor.web3.PublicKey; + let component: anchor.web3.PublicKey; + let entityWithAuthority: anchor.web3.PublicKey; + let componentWithAuthority: anchor.web3.PublicKey; + it("Create Session", async () => { - const airdrop = await framework.provider.connection.requestAirdrop( - framework.sessionSigner.publicKey, - anchor.web3.LAMPORTS_PER_SOL, + sessionToken = FindSessionTokenPda({ + targetProgram: framework.exampleComponentPosition.programId, + sessionSigner: sessionSigner.publicKey, + authority: framework.provider.wallet.publicKey, + }); + let instruction = await SessionProgram.methods + .createSession(true, null) + .accounts({ + sessionSigner: sessionSigner.publicKey, + authority: framework.provider.wallet.publicKey, + targetProgram: framework.exampleComponentPosition.programId, + sessionToken, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [sessionSigner]); + }); + + it("Add entity", async () => { + const world = await framework.worldProgram.account.world.fetch( + framework.worldPda, + ); + entity = FindEntityPda({ + worldId: world.id, + entityId: world.entities, + }); + const instruction = await framework.worldProgram.methods + .addEntity(null) + .accounts({ + payer: sessionSigner.publicKey, + entity: entity, + world: framework.worldPda, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [sessionSigner]); + }); + + it("Initialize position component", async () => { + const componentId = framework.exampleComponentPosition.programId; + component = FindComponentPda({ + componentId, + entity, + }); + const instruction = await framework.worldProgram.methods + .initializeComponent() + .accounts({ + payer: sessionSigner.publicKey, + entity: entity, + data: component, + componentProgram: componentId, + authority: framework.worldProgram.programId, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [sessionSigner]); + }); + + it("Apply Fly System on component using session token", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: sessionSigner.publicKey, + boltSystem: framework.systemFly.programId, + world: framework.worldPda, + sessionToken, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: component, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + + await framework.provider.sendAndConfirm(transaction, [sessionSigner]); + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + component, + ); + + expect(positionAfter.x.toNumber()).to.equal(positionBefore.x.toNumber()); + expect(positionAfter.y.toNumber()).to.equal(positionBefore.y.toNumber()); + expect(positionAfter.z.toNumber()).to.equal( + positionBefore.z.toNumber() + 1, ); + }); - await framework.provider.connection.confirmTransaction( - airdrop, - "confirmed", + it("Add entity for authority test", async () => { + const world = await framework.worldProgram.account.world.fetch( + framework.worldPda, ); + entityWithAuthority = FindEntityPda({ + worldId: world.id, + entityId: world.entities, + }); + const instruction = await framework.worldProgram.methods + .addEntity(null) + .accounts({ + payer: sessionSigner.publicKey, + world: framework.worldPda, + entity: entityWithAuthority, + }) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [sessionSigner]); + }); - const keys = await SessionProgram.methods - .createSession(true, null) + it("Initialize position component with authority", async () => { + const componentId = framework.exampleComponentPosition.programId; + componentWithAuthority = FindComponentPda({ + componentId, + entity: entityWithAuthority, + }); + const instruction = await framework.worldProgram.methods + .initializeComponent() .accounts({ - sessionSigner: framework.sessionSigner.publicKey, + payer: sessionSigner.publicKey, + entity: entityWithAuthority, + data: componentWithAuthority, + componentProgram: componentId, authority: framework.provider.wallet.publicKey, - targetProgram: framework.exampleComponentPosition.programId, }) - .signers([framework.sessionSigner]) - .rpcAndKeys(); - framework.sessionToken = keys.pubkeys.sessionToken as anchor.web3.PublicKey; + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [sessionSigner]); + }); + + it("Apply Fly System on component with authority using session token", async () => { + const positionBefore = + await framework.exampleComponentPosition.account.position.fetch( + componentWithAuthority, + ); + + const instruction = await framework.worldProgram.methods + .apply(SerializeArgs()) + .accounts({ + authority: sessionSigner.publicKey, + boltSystem: framework.systemFly.programId, + world: framework.worldPda, + sessionToken, + }) + .remainingAccounts([ + { + pubkey: framework.exampleComponentPosition.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: componentWithAuthority, + isSigner: false, + isWritable: true, + }, + ]) + .instruction(); + const transaction = new anchor.web3.Transaction().add(instruction); + + await framework.provider.sendAndConfirm(transaction, [sessionSigner]); + const positionAfter = + await framework.exampleComponentPosition.account.position.fetch( + componentWithAuthority, + ); + + expect(positionAfter.x.toNumber()).to.equal(positionBefore.x.toNumber()); + expect(positionAfter.y.toNumber()).to.equal(positionBefore.y.toNumber()); + expect(positionAfter.z.toNumber()).to.equal( + positionBefore.z.toNumber() + 1, + ); }); }); }