浏览代码

:recycle: Authority & Session checks

Danilo Guanabara 2 月之前
父节点
当前提交
729e51de53

+ 2 - 0
Cargo.lock

@@ -799,6 +799,7 @@ dependencies = [
  "bolt-utils",
  "proc-macro2",
  "quote",
+ "sha2 0.10.8",
  "syn 1.0.109",
 ]
 
@@ -7023,6 +7024,7 @@ dependencies = [
  "anchor-lang",
  "bolt-component",
  "bolt-system",
+ "session-keys",
  "solana-security-txt",
  "tuple-conv",
 ]

+ 1 - 0
Cargo.toml

@@ -65,6 +65,7 @@ which = "^7"
 tokio = { version = "^1", features = ["full"] }
 sysinfo = "^0"
 bytemuck_derive = "^1"
+sha2 = { version = "^0.10" }
 
 [profile.release]
 overflow-checks = true

+ 2 - 1
clients/typescript/src/generated/index.ts

@@ -11,7 +11,8 @@ import gpl_session from "./idl/gpl_session.json";
 export * from "./accounts";
 export * from "./errors";
 export * from "./instructions";
-export * from "./types";
+// Re-export the IDL type under a distinct name to avoid name collisions with account types
+export type { World as WorldIdl } from "./types";
 
 /**
  * Program address

+ 7 - 19
clients/typescript/src/world/transactions.ts

@@ -27,7 +27,7 @@ import {
   Transaction,
   type TransactionInstruction,
 } from "@solana/web3.js";
-import type WorldProgram from "../generated";
+import type { World as WorldProgram } from "../generated/types/world";
 import {
   createInitializeRegistryInstruction,
   PROGRAM_ID,
@@ -155,9 +155,7 @@ export async function AddAuthority({
   instruction: TransactionInstruction;
   transaction: Transaction;
 }> {
-  const program = new Program(
-    worldIdl as Idl,
-  ) as unknown as Program<WorldProgram>;
+  const program = new Program(worldIdl as Idl) as unknown as Program;
   const worldInstance = await World.fromAccountAddress(connection, world);
   const worldId = new BN(worldInstance.id);
   const instruction = await program.methods
@@ -197,9 +195,7 @@ export async function RemoveAuthority({
   instruction: TransactionInstruction;
   transaction: Transaction;
 }> {
-  const program = new Program(
-    worldIdl as Idl,
-  ) as unknown as Program<WorldProgram>;
+  const program = new Program(worldIdl as Idl) as unknown as Program;
   const worldInstance = await World.fromAccountAddress(connection, world);
   const worldId = new BN(worldInstance.id);
   const instruction = await program.methods
@@ -236,9 +232,7 @@ export async function ApproveSystem({
   instruction: TransactionInstruction;
   transaction: Transaction;
 }> {
-  const program = new Program(
-    worldIdl as Idl,
-  ) as unknown as Program<WorldProgram>;
+  const program = new Program(worldIdl as Idl) as unknown as Program;
   const instruction = await program.methods
     .approveSystem()
     .accounts({
@@ -273,9 +267,7 @@ export async function RemoveSystem({
   instruction: TransactionInstruction;
   transaction: Transaction;
 }> {
-  const program = new Program(
-    worldIdl as Idl,
-  ) as unknown as Program<WorldProgram>;
+  const program = new Program(worldIdl as Idl) as unknown as Program;
   const instruction = await program.methods
     .removeSystem()
     .accounts({
@@ -359,9 +351,7 @@ export async function DestroyComponent({
   instruction: TransactionInstruction;
   transaction: Transaction;
 }> {
-  const program = new Program(
-    worldIdl as Idl,
-  ) as unknown as Program<WorldProgram>;
+  const program = new Program(worldIdl as Idl) as unknown as Program;
   const componentProgramData = FindComponentProgramDataPda({
     programId: componentId,
   });
@@ -450,9 +440,7 @@ async function createApplySystemInstruction({
   extraAccounts,
   args,
 }: ApplySystemInstruction): Promise<web3.TransactionInstruction> {
-  const program = new Program(
-    worldIdl as Idl,
-  ) as unknown as Program<WorldProgram>;
+  const program = new Program(worldIdl as Idl) as unknown as Program;
   let componentCount = 0;
   entities.forEach(function (entity) {
     componentCount += entity.components.length;

+ 0 - 1
clients/typescript/test/framework.ts

@@ -91,5 +91,4 @@ export class Framework {
     console.log(`Total CPI Consumed: ${total}`);
     console.log(`Number of Instructions: ${numberOfInstructions}`);
     console.log(`World Report: ${worldReport}`);
-  }
 }

+ 0 - 3
clients/typescript/test/low-level/ecs.ts

@@ -222,9 +222,6 @@ export function ecs(framework) {
       expect(position.z.toNumber()).to.equal(0);
     });
 
-    // FIXME: Remove this.
-    return;
-
     it("Apply Simple Movement System (Right) on Entity 1", async () => {
       const instruction = await framework.worldProgram.methods
         .apply(SerializeArgs({ direction: Direction.Right }))

+ 2 - 2
clients/typescript/test/low-level/index.ts

@@ -8,6 +8,6 @@ describe("Low level API", () => {
   const framework: Framework = new Framework();
   world(framework);
   ecs(framework);
-  // session(framework);
-  // permissioning(framework);
+  session(framework);
+  permissioning(framework);
 });

+ 3 - 0
clients/typescript/test/low-level/permissioning/component.ts

@@ -5,6 +5,7 @@ import {
   FindComponentPda,
   SerializeArgs,
   CPI_AUTH_ADDRESS,
+  FindBufferPda,
 } from "../../../lib";
 import { assert, expect } from "chai";
 
@@ -65,6 +66,7 @@ export function component(framework) {
       const instruction = await framework.worldProgram.methods
         .apply(SerializeArgs())
         .accounts({
+          buffer: FindBufferPda(),
           authority: keypair.publicKey,
           boltSystem: framework.systemFly.programId,
           world: framework.worldPda,
@@ -115,6 +117,7 @@ export function component(framework) {
       const instruction = await framework.worldProgram.methods
         .apply(SerializeArgs())
         .accounts({
+          buffer: FindBufferPda(),
           authority: framework.provider.wallet.publicKey,
           boltSystem: framework.systemFly.programId,
           world: framework.worldPda,

+ 8 - 1
clients/typescript/test/low-level/permissioning/world.ts

@@ -1,5 +1,10 @@
 import { expect } from "chai";
-import { anchor, CPI_AUTH_ADDRESS, SerializeArgs } from "../../../lib";
+import {
+  anchor,
+  CPI_AUTH_ADDRESS,
+  FindBufferPda,
+  SerializeArgs,
+} from "../../../lib";
 
 export function world(framework) {
   describe("World authority", () => {
@@ -124,6 +129,7 @@ export function world(framework) {
       const instruction = await framework.worldProgram.methods
         .apply(SerializeArgs())
         .accounts({
+          buffer: FindBufferPda(),
           authority: framework.provider.wallet.publicKey,
           boltSystem: framework.systemFly.programId,
           world: framework.worldPda,
@@ -172,6 +178,7 @@ export function world(framework) {
       const instruction = await framework.worldProgram.methods
         .apply(SerializeArgs())
         .accounts({
+          buffer: FindBufferPda(),
           authority: framework.provider.wallet.publicKey,
           boltSystem: framework.systemFly.programId,
           world: framework.worldPda,

+ 3 - 0
clients/typescript/test/low-level/session.ts

@@ -8,6 +8,7 @@ import {
   FindSessionTokenPda,
   BN,
   CPI_AUTH_ADDRESS,
+  FindBufferPda,
 } from "../../lib";
 import { Keypair } from "@solana/web3.js";
 
@@ -90,6 +91,7 @@ export function session(framework) {
       const instruction = await framework.worldProgram.methods
         .applyWithSession(SerializeArgs())
         .accounts({
+          buffer: FindBufferPda(),
           authority: sessionSigner.publicKey,
           boltSystem: framework.systemFly.programId,
           world: framework.worldPda,
@@ -179,6 +181,7 @@ export function session(framework) {
       const instruction = await framework.worldProgram.methods
         .applyWithSession(SerializeArgs())
         .accounts({
+          buffer: FindBufferPda(),
           authority: sessionSigner.publicKey,
           boltSystem: framework.systemFly.programId,
           world: framework.worldPda,

+ 1 - 1
clients/typescript/test/main.ts

@@ -1,2 +1,2 @@
 import "./low-level";
-// import "./intermediate-level";
+import "./intermediate-level";

+ 16 - 16
clients/typescript/yarn.lock

@@ -82,7 +82,7 @@
     bs58 "^5.0.0"
     debug "^4.3.4"
 
-"@metaplex-foundation/beet@>=0.1.0", "@metaplex-foundation/beet@^0.7.1", "@metaplex-foundation/beet@^0.7.2":
+"@metaplex-foundation/beet@^0.7.1", "@metaplex-foundation/beet@^0.7.2", "@metaplex-foundation/beet@>=0.1.0":
   version "0.7.2"
   resolved "https://registry.npmjs.org/@metaplex-foundation/beet/-/beet-0.7.2.tgz"
   integrity sha512-K+g3WhyFxKPc0xIvcIjNyV1eaTVJTiuaHZpig7Xx0MuYRMoJLLvhLTnUXhFdR5Tu2l2QSyKwfyXDgZlzhULqFg==
@@ -126,7 +126,7 @@
   dependencies:
     "@noble/hashes" "1.7.1"
 
-"@noble/hashes@1.7.1", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0":
+"@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0", "@noble/hashes@1.7.1":
   version "1.7.1"
   resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz"
   integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==
@@ -202,14 +202,6 @@
   dependencies:
     "@types/node" "*"
 
-JSONStream@^1.3.5:
-  version "1.3.5"
-  resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz"
-  integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==
-  dependencies:
-    jsonparse "^1.2.0"
-    through ">=2.2.7 <3"
-
 agentkeepalive@^4.5.0:
   version "4.6.0"
   resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz"
@@ -348,7 +340,7 @@ buffer-layout@^1.2.0, buffer-layout@^1.2.2:
   resolved "https://registry.npmjs.org/buffer-layout/-/buffer-layout-1.2.2.tgz"
   integrity sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==
 
-buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3:
+buffer@^6.0.3, buffer@~6.0.3, buffer@6.0.3:
   version "6.0.3"
   resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz"
   integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
@@ -766,13 +758,13 @@ jayson@^4.1.1:
     "@types/connect" "^3.4.33"
     "@types/node" "^12.12.54"
     "@types/ws" "^7.4.4"
-    JSONStream "^1.3.5"
     commander "^2.20.3"
     delay "^5.0.0"
     es6-promisify "^5.0.0"
     eyes "^0.1.8"
     isomorphic-ws "^4.0.1"
     json-stringify-safe "^5.0.1"
+    JSONStream "^1.3.5"
     uuid "^8.3.2"
     ws "^7.5.10"
 
@@ -796,6 +788,14 @@ jsonparse@^1.2.0:
   resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz"
   integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
 
+JSONStream@^1.3.5:
+  version "1.3.5"
+  resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz"
+  integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==
+  dependencies:
+    jsonparse "^1.2.0"
+    through ">=2.2.7 <3"
+
 loglevel@^1.9.2:
   version "1.9.2"
   resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz"
@@ -1147,7 +1147,7 @@ typedoc-plugin-markdown@^3.17.1:
   dependencies:
     handlebars "^4.7.7"
 
-typedoc@^0.25.4:
+typedoc@^0.25.4, typedoc@>=0.24.0:
   version "0.25.13"
   resolved "https://registry.npmjs.org/typedoc/-/typedoc-0.25.13.tgz"
   integrity sha512-pQqiwiJ+Z4pigfOnnysObszLiU3mVLWAExSPf+Mu06G/qsc3wzbuM56SZQvONhHLncLUhYzOVkjFFpFfL5AzhQ==
@@ -1157,7 +1157,7 @@ typedoc@^0.25.4:
     minimatch "^9.0.3"
     shiki "^0.14.7"
 
-typescript@^4.5.5:
+typescript@^4.5.5, "typescript@4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x":
   version "4.9.5"
   resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz"
   integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
@@ -1167,7 +1167,7 @@ uglify-js@^3.1.4:
   resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz"
   integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==
 
-utf-8-validate@^5.0.2:
+utf-8-validate@^5.0.2, utf-8-validate@>=5.0.2:
   version "5.0.10"
   resolved "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz"
   integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==
@@ -1255,7 +1255,7 @@ wrap-ansi@^8.1.0:
     string-width "^5.0.1"
     strip-ansi "^7.0.1"
 
-ws@^7.5.10:
+ws@*, ws@^7.5.10:
   version "7.5.10"
   resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz"
   integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==

+ 9 - 6
crates/bolt-cli/src/templates/component/mod.rs

@@ -46,12 +46,14 @@ pub fn component_type(idl: &Idl, component_id: &str) -> Result<String> {
     Ok(format!(
         r#"use bolt_lang::*;
 
-#[component_deserialize]
-{}
+#[component_deserialize({component_name})]
+{component_code}
 
-{}
+{types_code}
 "#,
-        component_code, types_code
+        component_name = component_account.name,
+        component_code = component_code,
+        types_code = types_code
     ))
 }
 
@@ -162,8 +164,9 @@ fn component_type_to_rust_code(component_type: &IdlTypeDef) -> String {
     };
     if let IdlTypeDefTy::Struct { fields } = &component_type.ty {
         code += &format!(
-            "#[component_deserialize]\n#[derive(Clone, Copy)]\npub struct {}{} {{\n",
-            component_type.name, generics
+            "#[component_deserialize({name})]\n#[derive(Clone, Copy)]\npub struct {name}{generics} {{\n",
+            name = component_type.name,
+            generics = generics
         );
         code += &*component_fields_to_rust_code(fields);
         code += "}\n\n";

+ 1 - 0
crates/bolt-lang/attribute/component-deserialize/Cargo.toml

@@ -16,3 +16,4 @@ syn = { workspace = true }
 bolt-utils = { workspace = true }
 quote = { workspace = true }
 proc-macro2 = { workspace = true }
+sha2 = { workspace = true }

+ 39 - 2
crates/bolt-lang/attribute/component-deserialize/src/lib.rs

@@ -3,6 +3,9 @@ use proc_macro::TokenStream;
 use quote::quote;
 use syn::{parse_macro_input, Attribute, DeriveInput};
 
+// For computing Anchor discriminator at compile time
+use sha2::{Digest, Sha256};
+
 /// This macro is used to defined a struct as a BOLT component and automatically implements the
 /// `ComponentDeserialize` and `AccountDeserialize` traits for the struct.
 ///
@@ -14,7 +17,7 @@ use syn::{parse_macro_input, Attribute, DeriveInput};
 /// }
 /// ```
 #[proc_macro_attribute]
-pub fn component_deserialize(_attr: TokenStream, item: TokenStream) -> TokenStream {
+pub fn component_deserialize(attr: TokenStream, item: TokenStream) -> TokenStream {
     let mut input = parse_macro_input!(item as DeriveInput);
 
     // Add the AnchorDeserialize and AnchorSerialize derives to the struct
@@ -38,6 +41,38 @@ pub fn component_deserialize(_attr: TokenStream, item: TokenStream) -> TokenStre
             }
         };
     }
+    // Determine which struct name to use for Anchor discriminator calculation.
+    // If the attribute is written as #[component_deserialize(Position)], then use "Position".
+    // Otherwise, fall back to the actual type name (which may be Component<Pubkey> style).
+    let discriminator_type_name: String = if !attr.is_empty() {
+        // Parse the attribute as a path and use its last segment as the name
+        if let Ok(path) = syn::parse::<syn::Path>(attr.clone()) {
+            path.segments
+                .last()
+                .map(|seg| seg.ident.to_string())
+                .unwrap_or_else(|| name.to_string())
+        } else {
+            name.to_string()
+        }
+    } else {
+        name.to_string()
+    };
+
+    // Compute Anchor discriminator: first 8 bytes of sha256(b"account:" + type_name)
+    let mut hasher = Sha256::new();
+    hasher.update(b"account:");
+    hasher.update(discriminator_type_name.as_bytes());
+    let digest = hasher.finalize();
+    let discriminator_bytes: [u8; 8] = {
+        let mut arr = [0u8; 8];
+        arr.copy_from_slice(&digest[..8]);
+        arr
+    };
+    let discriminator_tokens = discriminator_bytes
+        .iter()
+        .map(|b| quote! { #b })
+        .collect::<Vec<_>>();
+
     let expanded = quote! {
         #input
 
@@ -76,7 +111,9 @@ pub fn component_deserialize(_attr: TokenStream, item: TokenStream) -> TokenStre
 
         #[automatically_derived]
         impl anchor_lang::Discriminator for #name {
-            const DISCRIMINATOR: &'static [u8] = &[1, 1, 1, 1, 1, 1, 1, 1];
+            const DISCRIMINATOR: &'static [u8] = &[
+                #(#discriminator_tokens),*
+            ];
         }
 
         #owner_definition

+ 1 - 1
crates/bolt-lang/utils/src/metadata.rs

@@ -16,7 +16,7 @@ pub fn add_bolt_metadata(input: &mut DeriveInput) {
     };
     if let syn::Data::Struct(ref mut data) = input.data {
         if let syn::Fields::Named(ref mut fields) = data.fields {
-            fields.named.push(authority_field);
+            fields.named.insert(0, authority_field);
         }
     }
 }

+ 0 - 26
crates/programs/bolt-component/src/lib.rs

@@ -98,29 +98,3 @@ pub trait CpiContextBuilder<'a, 'b, 'c, 'info>:
         signer_seeds: &'a [&'b [&'c [u8]]],
     ) -> CpiContext<'a, 'b, 'c, 'info, Self>;
 }
-
-// #[cfg(feature = "cpi")]
-// impl<'a, 'b, 'c, 'info> CpiContextBuilder<'a, 'b, 'c, 'info> for cpi::accounts::Update<'info> {
-//     fn build_cpi_context(
-//         self,
-//         program: AccountInfo<'info>,
-//         signer_seeds: &'a [&'b [&'c [u8]]],
-//     ) -> CpiContext<'a, 'b, 'c, 'info, Self> {
-//         let cpi_program = program.to_account_info();
-//         CpiContext::new_with_signer(cpi_program, self, signer_seeds)
-//     }
-// }
-
-// #[cfg(feature = "cpi")]
-// impl<'a, 'b, 'c, 'info> CpiContextBuilder<'a, 'b, 'c, 'info>
-//     for cpi::accounts::UpdateWithSession<'info>
-// {
-//     fn build_cpi_context(
-//         self,
-//         program: AccountInfo<'info>,
-//         signer_seeds: &'a [&'b [&'c [u8]]],
-//     ) -> CpiContext<'a, 'b, 'c, 'info, Self> {
-//         let cpi_program = program.to_account_info();
-//         CpiContext::new_with_signer(cpi_program, self, signer_seeds)
-//     }
-// }

+ 1 - 0
crates/programs/world/Cargo.toml

@@ -29,3 +29,4 @@ bolt-component.workspace = true
 bolt-system.workspace = true
 solana-security-txt.workspace = true
 tuple-conv.workspace = true
+session-keys.workspace = true

+ 55 - 22
crates/programs/world/src/lib.rs

@@ -282,6 +282,7 @@ pub mod world {
             ctx.accounts.build(),
             args,
             &ctx.accounts.system_program,
+            None,
             ctx.remaining_accounts.to_vec(),
         )?;
         Ok(())
@@ -331,6 +332,7 @@ pub mod world {
             ctx.accounts.build(),
             args,
             &ctx.accounts.system_program,
+            Some(&ctx.accounts.session_token),
             ctx.remaining_accounts.to_vec(),
         )?;
         Ok(())
@@ -352,9 +354,8 @@ pub mod world {
         pub cpi_auth: UncheckedAccount<'info>,
         #[account()]
         pub world: Account<'info, World>,
-        #[account()]
-        /// CHECK: The session token
-        pub session_token: UncheckedAccount<'info>,
+        #[account(constraint = session_token.to_account_info().owner == &session_keys::ID)]
+        pub session_token: Account<'info, session_keys::SessionToken>,
         pub system_program: Program<'info, System>,
     }
 
@@ -381,6 +382,7 @@ fn apply_impl<'info>(
     cpi_context: CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::BoltExecute<'info>>,
     args: Vec<u8>,
     system_program: &Program<'info, System>,
+    session_token: Option<&Account<'info, session_keys::SessionToken>>,
     mut remaining_accounts: Vec<AccountInfo<'info>>,
 ) -> Result<()> {
     if !authority.is_signer && authority.key != &ID {
@@ -405,6 +407,37 @@ fn apply_impl<'info>(
         pairs.push((program, component));
     }
 
+    // Authority check against component metadata (partial deserialize)
+    for (_, component) in &pairs {
+        let data_ref = component.try_borrow_data()?;
+        // Expect at least Anchor discriminator (8) + BoltMetadata (32)
+        if data_ref.len() < 8 + 32 {
+            return Err(WorldError::InvalidAuthority.into());
+        }
+        // BoltMetadata.authority is the last 32 bytes of the serialized component
+        let start = 8; // Skip the discriminator
+        let mut key_bytes = [0u8; 32];
+        key_bytes.copy_from_slice(&data_ref[start..start+32]);
+        let component_authority = Pubkey::new_from_array(key_bytes);
+
+        if let Some(session_token) = session_token {
+            if component_authority == ID {
+                require!(Clock::get()?.unix_timestamp < session_token.valid_until, session_keys::SessionError::InvalidToken);
+            } else {
+                let validity_ctx = session_keys::ValidityChecker {
+                    session_token: session_token.clone(),
+                    session_signer: authority.clone(),
+                    authority: component_authority.clone(),
+                    target_program: ID,
+                };
+                require!(session_token.validate(validity_ctx)?, session_keys::SessionError::InvalidToken);
+                require_eq!(component_authority, session_token.authority, session_keys::SessionError::InvalidToken);
+            }
+        } else {
+            require!(component_authority == ID || (component_authority == *authority.key && authority.is_signer), WorldError::InvalidAuthority);
+        }
+    }
+
     let mut components_accounts = pairs
         .iter()
         .map(|(_, component)| component)
@@ -413,21 +446,25 @@ fn apply_impl<'info>(
     components_accounts.append(&mut remaining_accounts);
     let remaining_accounts = components_accounts;
 
-    let size = 0;
-    let lamports = Rent::get()?.minimum_balance(size);
-    system_program::create_account(
-        CpiContext::new_with_signer(
-            system_program.to_account_info(),
-            system_program::CreateAccount {
-                from: authority.to_account_info(),
-                to: buffer.to_account_info(),
-            },
-            &[World::buffer_seeds().as_slice()],
-        ),
-        lamports,
-        size as u64,
-        &ID,
-    )?;
+    // Create the buffer account only if it does not already exist.
+    // Subsequent applies reuse the same PDA and only reallocate its data.
+    if buffer.lamports() == 0 {
+        let size = 0;
+        let lamports = Rent::get()?.minimum_balance(size);
+        system_program::create_account(
+            CpiContext::new_with_signer(
+                system_program.to_account_info(),
+                system_program::CreateAccount {
+                    from: authority.to_account_info(),
+                    to: buffer.to_account_info(),
+                },
+                &[World::buffer_seeds().as_slice()],
+            ),
+            lamports,
+            size as u64,
+            &ID,
+        )?;
+    }
 
     for (program, component) in &pairs {
         buffer.realloc(component.data_len(), false)?;
@@ -461,15 +498,11 @@ fn apply_impl<'info>(
         )?;
     }
 
-    msg!("Executing bolt system");
-
     bolt_system::cpi::bolt_execute(
         cpi_context.with_remaining_accounts(remaining_accounts),
         args,
     )?;
 
-    msg!("Executed bolt system");
-
     for (program, component) in &pairs {
         buffer.realloc(component.data_len(), false)?;
         {

+ 1 - 1
crates/types/src/component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs

@@ -1,6 +1,6 @@
 use bolt_lang::*;
 
-#[component_deserialize]
+#[component_deserialize(Position)]
 pub struct ComponentFn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ {
     pub x: i64,
     pub y: i64,

+ 0 - 5
examples/system-simple-movement/src/lib.rs

@@ -14,11 +14,6 @@ pub mod system_simple_movement {
         };
         ctx.accounts.position.x += dx;
         ctx.accounts.position.y += dy;
-        msg!("Position: {:?}", ctx.accounts.position.to_account_info().try_borrow_data()?);
-        ctx.accounts.position.exit(&crate::id())?;
-        msg!("Position: {:?}", ctx.accounts.position.to_account_info().try_borrow_data()?);
-        panic!();
-        use std::io::Write;
         Ok(())
     }
 

+ 0 - 6
scripts/test.sh

@@ -1,6 +0,0 @@
-SCRIPT_DIR=$(dirname "$0")
-PROJECT_DIR="$SCRIPT_DIR/.."
-pushd "$PROJECT_DIR"
-cp tests/keys/* target/deploy/
-bolt test
-popd