From 22b314f95a7f0c156828ad29f70d865ac7920ecf Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Wed, 13 Aug 2025 16:31:22 -0300 Subject: [PATCH 01/21] :construction_worker: Adding CU measurement --- .gitignore | 1 + clients/typescript/test/intermediate-level/ecs.ts | 10 +++++++++- .../typescript/test/low-level/permissioning/world.ts | 4 ++-- examples/system-apply-velocity/src/lib.rs | 5 ----- examples/system-simple-movement/Cargo.toml | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 5402ad1..f5bcf4a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.vscode .idea* .idea .anchor diff --git a/clients/typescript/test/intermediate-level/ecs.ts b/clients/typescript/test/intermediate-level/ecs.ts index db9695e..5d3283a 100644 --- a/clients/typescript/test/intermediate-level/ecs.ts +++ b/clients/typescript/test/intermediate-level/ecs.ts @@ -249,7 +249,15 @@ export function ecs(framework: Framework) { }, ], }); - await framework.provider.sendAndConfirm(applySystem.transaction); + let signature = await framework.provider.sendAndConfirm( + applySystem.transaction, + ); + + let transactionResponse = + await framework.provider.connection.getTransaction(signature, { + commitment: "confirmed", + }); + console.log(transactionResponse?.meta?.logMessages); // Reference CU is 27771 const position = await framework.exampleComponentPosition.account.position.fetch( diff --git a/clients/typescript/test/low-level/permissioning/world.ts b/clients/typescript/test/low-level/permissioning/world.ts index 3044ac0..fd1a5e8 100644 --- a/clients/typescript/test/low-level/permissioning/world.ts +++ b/clients/typescript/test/low-level/permissioning/world.ts @@ -101,9 +101,9 @@ export function world(framework) { world: framework.worldPda, }) .instruction(); - const transaction = new anchor.web3.Transaction().add(instruction); + let transaction = new anchor.web3.Transaction().add(instruction); await framework.provider.sendAndConfirm(transaction, [], { - skipPreflight: true, + skipPreflight: true }); // Get World and check permissionless and systems diff --git a/examples/system-apply-velocity/src/lib.rs b/examples/system-apply-velocity/src/lib.rs index 405351f..ee1db4e 100644 --- a/examples/system-apply-velocity/src/lib.rs +++ b/examples/system-apply-velocity/src/lib.rs @@ -16,11 +16,6 @@ pub mod system_apply_velocity { } ctx.accounts.velocity.last_applied = clock.unix_timestamp; ctx.accounts.position.x += 10 * (ctx.accounts.velocity.x + 2) + 3; - msg!("last applied: {}", ctx.accounts.velocity.last_applied); - msg!("Position: {}", ctx.accounts.position.x); - msg!("Remaining accounts len: {}", ctx.remaining_accounts.len()); - msg!("Remaining accounts: {:?}", ctx.remaining_accounts); - msg!("Authority: {}", ctx.accounts.authority.key); Ok(ctx.accounts) } diff --git a/examples/system-simple-movement/Cargo.toml b/examples/system-simple-movement/Cargo.toml index d1eb9fe..5a48ad8 100644 --- a/examples/system-simple-movement/Cargo.toml +++ b/examples/system-simple-movement/Cargo.toml @@ -26,4 +26,4 @@ custom-panic = [] [dependencies] serde.workspace = true bolt-lang.workspace = true -bolt-types = { version = "0.2.3", path = "../../crates/types" } +bolt-types = { version = "0.2.4", path = "../../crates/types" } From f66535056e36ae7db85932331a3ae8de42f37108 Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Thu, 21 Aug 2025 15:37:12 -0300 Subject: [PATCH 02/21] :white_check_mark: Adding CU measurement in tests --- Anchor.toml | 23 +++--- Cargo.lock | 32 ++++++++ Cargo.toml | 3 + clients/typescript/test/framework.ts | 75 ++++++++++++++++- .../typescript/test/intermediate-level/ecs.ts | 80 +++++++++++++++++-- .../test/low-level/permissioning/world.ts | 2 +- examples/component-large/Cargo.toml | 22 +++++ examples/component-large/Xargo.toml | 2 + examples/component-large/src/lib.rs | 17 ++++ examples/component-small/Cargo.toml | 23 ++++++ examples/component-small/Xargo.toml | 2 + examples/component-small/src/lib.rs | 9 +++ .../system-with-few-components/Cargo.toml | 26 ++++++ .../system-with-few-components/Xargo.toml | 2 + .../system-with-few-components/src/lib.rs | 23 ++++++ .../system-with-many-components/Cargo.toml | 25 ++++++ .../system-with-many-components/Xargo.toml | 2 + .../system-with-many-components/src/lib.rs | 27 +++++++ 18 files changed, 373 insertions(+), 22 deletions(-) create mode 100644 examples/component-large/Cargo.toml create mode 100644 examples/component-large/Xargo.toml create mode 100644 examples/component-large/src/lib.rs create mode 100644 examples/component-small/Cargo.toml create mode 100644 examples/component-small/Xargo.toml create mode 100644 examples/component-small/src/lib.rs create mode 100644 examples/system-with-few-components/Cargo.toml create mode 100644 examples/system-with-few-components/Xargo.toml create mode 100644 examples/system-with-few-components/src/lib.rs create mode 100644 examples/system-with-many-components/Cargo.toml create mode 100644 examples/system-with-many-components/Xargo.toml create mode 100644 examples/system-with-many-components/src/lib.rs diff --git a/Anchor.toml b/Anchor.toml index a57581d..a56f235 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -10,30 +10,25 @@ world = "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n" [programs.localnet] bolt-component = "CmP2djJgABZ4cRokm4ndxuq6LerqpNHLBsaUv2XKEJua" bolt-system = "7X4EFsDJ5aYTcEjKzJ94rD8FRKgQeXC89fkpeTS4KaqP" +component-large = "FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX" +component-small = "9yBADAhoTWCkNRB6hbfpwUgPpxyJiF9uEiWVPR6k7A4y" position = "Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ" system-apply-velocity = "6LHhFVwif6N9Po3jHtSmMVtPjF6zRfL3xMosSzcrQAS8" system-fly = "HT2YawJjkNmqWcLNfPAMvNsLdWwPvvvbKA5bpMw4eUpq" system-simple-movement = "FSa6qoJXFBR3a7ThQkTAMrC15p6NkchPEjBdd4n6dXxA" +system-with-few-components = "A3kNNSgmkTNA5V1qtnrbtNeqKrYHNxUMCTkqTDaQzE97" +system-with-many-components = "Hi4sMEb3uXhWCiLyrF7t3Z384an7YZsTj774cabAAPQB" velocity = "CbHEFbSQdRN4Wnoby9r16umnJ1zWbULBHg4yqzGQonU1" [registry] url = "https://api.apr.dev" [provider] -cluster = "Localnet" +cluster = "localnet" wallet = "./tests/fixtures/provider.json" [workspace] -members = [ - "crates/programs/bolt-component", - "crates/programs/bolt-system", - "crates/programs/world", - "examples/component-position", - "examples/component-velocity", - "examples/system-apply-velocity", - "examples/system-fly", - "examples/system-simple-movement" -] +members = ["crates/programs/bolt-component", "crates/programs/bolt-system", "crates/programs/world", "examples/component-position", "examples/component-velocity", "examples/system-apply-velocity", "examples/system-fly", "examples/system-simple-movement", "examples/system-with-many-components", "examples/system-with-few-components", "examples/component-large", "examples/component-small"] [scripts] test = "tests/script.sh" @@ -49,8 +44,8 @@ program = "tests/fixtures/delegation.so" upgradeable = false [[test.genesis]] -address="KeyspM2ssCJbqUhQ4k7sveSiY4WjnYsrXkC8oDbwde5" -program="tests/fixtures/session_keys.so" +address = "KeyspM2ssCJbqUhQ4k7sveSiY4WjnYsrXkC8oDbwde5" +program = "tests/fixtures/session_keys.so" upgradeable = false [test.validator] @@ -76,4 +71,4 @@ filename = "tests/fixtures/vault1.json" [[test.validator.account]] address = "7JrkjmZPprHwtuvtuGTXp9hwfGYFAQLnLeFM52kqAgXg" -filename = "tests/fixtures/vault2.json" \ No newline at end of file +filename = "tests/fixtures/vault2.json" diff --git a/Cargo.lock b/Cargo.lock index 0691f45..a85d55e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1258,6 +1258,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "component-large" +version = "0.2.4" +dependencies = [ + "bolt-lang", +] + +[[package]] +name = "component-small" +version = "0.2.4" +dependencies = [ + "bolt-lang", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -5903,6 +5917,24 @@ dependencies = [ "serde", ] +[[package]] +name = "system-with-few-components" +version = "0.2.4" +dependencies = [ + "bolt-lang", + "component-large", + "serde", +] + +[[package]] +name = "system-with-many-components" +version = "0.2.4" +dependencies = [ + "bolt-lang", + "component-small", + "serde", +] + [[package]] name = "tar" version = "0.4.43" diff --git a/Cargo.toml b/Cargo.toml index 333e26f..6bf9029 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "crates/programs/world", "crates/types", "examples/*", + "examples/system-with-many-components", ] [workspace.package] @@ -34,6 +35,8 @@ bolt-utils = { path = "crates/bolt-lang/utils", version = "=0.2.4" } world = { path = "crates/programs/world", features = ["cpi"], version = "=0.2.4"} bolt-system = { path = "crates/programs/bolt-system", features = ["cpi"], version = "=0.2.4"} bolt-component = { path = "crates/programs/bolt-component", features = ["cpi"], version = "=0.2.4"} +component-large = { path = "examples/component-large", features = ["cpi"], version = "=0.2.4"} +component-small = { path = "examples/component-small", features = ["cpi"], version = "=0.2.4"} ## External crates session-keys = { version = "^2", features = ["no-entrypoint"] } diff --git a/clients/typescript/test/framework.ts b/clients/typescript/test/framework.ts index 770f5be..628dd62 100644 --- a/clients/typescript/test/framework.ts +++ b/clients/typescript/test/framework.ts @@ -13,6 +13,10 @@ import { type SystemSimpleMovement } from "../../../target/types/system_simple_m import { type SystemFly } from "../../../target/types/system_fly"; import { type SystemApplyVelocity } from "../../../target/types/system_apply_velocity"; import { Connection, Keypair, PublicKey } from "@solana/web3.js"; +import { SystemWithManyComponents } from "../../../target/types/system_with_many_components"; +import { Large } from "../../../target/types/large"; +import { Small } from "../../../target/types/small"; +import { SystemWithFewComponents } from "../../../target/types/system_with_few_components"; export class Framework { provider: anchor.AnchorProvider; @@ -23,7 +27,10 @@ export class Framework { systemSimpleMovement: anchor.Program; systemFly: anchor.Program; systemApplyVelocity: anchor.Program; - + systemWithManyComponents: anchor.Program; + systemWithFewComponents: anchor.Program; + componentLarge: anchor.Program; + componentSmall: anchor.Program; worldPda: PublicKey; worldId: BN; @@ -37,8 +44,9 @@ export class Framework { acceleratedComponentPositionPda: PublicKey; componentPositionEntity1Pda: PublicKey; componentVelocityEntity1Pda: PublicKey; - + componentLargeEntity1Pda: PublicKey; componentPositionEntity4Pda: PublicKey; + componentSmallEntity1Pda: PublicKey; constructor() { this.secondAuthority = Keypair.generate().publicKey; @@ -48,6 +56,10 @@ export class Framework { this.systemSimpleMovement = anchor.workspace.SystemSimpleMovement; this.systemFly = anchor.workspace.SystemFly; this.systemApplyVelocity = anchor.workspace.SystemApplyVelocity; + this.systemWithManyComponents = anchor.workspace.SystemWithManyComponents; + this.systemWithFewComponents = anchor.workspace.SystemWithFewComponents; + this.componentLarge = anchor.workspace.Large; + this.componentSmall = anchor.workspace.Small; this.provider = anchor.AnchorProvider.local(); anchor.setProvider(this.provider); @@ -57,4 +69,63 @@ export class Framework { anchor.Wallet.local(), ); } + + + consume(line: string): number { + let consumed = line.split(" consumed ")[1].split(" of ")[0]; + return parseInt(consumed); + } + + report(log: string[]) { + var cpi: number[] = []; + var worldReport: number = 0; + for (let index in log) { + let line = log[index]; + if (line.includes(" consumed ")) { + if (!line.includes("WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n")) { + cpi.push(this.consume(line)); + } else { + worldReport = this.consume(line); + } + } + } + let total = cpi.reduce((a, b) => a + b, 0); + let numberOfInstructions = cpi.length; + console.log(`Total CPI Consumed: ${total}`); + console.log(`Number of Instructions: ${numberOfInstructions}`); + console.log(`World Report: ${worldReport}`); + } } + +// example input +// [ +// 'Program WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n invoke [1]', +// 'Program log: Instruction: Apply', +// 'Program A3kNNSgmkTNA5V1qtnrbtNeqKrYHNxUMCTkqTDaQzE97 invoke [2]', +// 'Program log: Instruction: BoltExecute', +// 'Program A3kNNSgmkTNA5V1qtnrbtNeqKrYHNxUMCTkqTDaQzE97 consumed 3679 of 191671 compute units', +// 'Program return: A3kNNSgmkTNA5V1qtnrbtNeqKrYHNxUMCTkqTDaQzE97 BQAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAei0UnvLCu8bsxLLo92w4UVq1z4LrLIGvtTpkos13GLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6LRSe8sK7xuzEsuj3bDhRWrXPgussga+1OmSizXcYtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHotFJ7ywrvG7MSy6PdsOFFatc+C6yyBr7U6ZKLNdxi0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAei0UnvLCu8bsxLLo92w4UVq1z4LrLIGvtTpkos13GLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6LRSe8sK7xuzEsuj3bDhRWrXPgussga+1OmSizXcYs=', +// 'Program A3kNNSgmkTNA5V1qtnrbtNeqKrYHNxUMCTkqTDaQzE97 success', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX invoke [2]', +// 'Program log: Instruction: Update', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX consumed 4732 of 183865 compute units', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX success', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX invoke [2]', +// 'Program log: Instruction: Update', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX consumed 4732 of 176143 compute units', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX success', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX invoke [2]', +// 'Program log: Instruction: Update', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX consumed 4732 of 168421 compute units', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX success', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX invoke [2]', +// 'Program log: Instruction: Update', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX consumed 4732 of 160699 compute units', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX success', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX invoke [2]', +// 'Program log: Instruction: Update', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX consumed 4732 of 152977 compute units', +// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX success', +// 'Program WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n consumed 52251 of 200000 compute units', +// 'Program WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n success' +// ] \ No newline at end of file diff --git a/clients/typescript/test/intermediate-level/ecs.ts b/clients/typescript/test/intermediate-level/ecs.ts index 5d3283a..bf59958 100644 --- a/clients/typescript/test/intermediate-level/ecs.ts +++ b/clients/typescript/test/intermediate-level/ecs.ts @@ -253,11 +253,13 @@ export function ecs(framework: Framework) { applySystem.transaction, ); - let transactionResponse = - await framework.provider.connection.getTransaction(signature, { - commitment: "confirmed", - }); - console.log(transactionResponse?.meta?.logMessages); // Reference CU is 27771 + let transactionResponse: any; + do { + transactionResponse = + await framework.provider.connection.getTransaction(signature, { + commitment: "confirmed", + }); + } while (transactionResponse?.meta?.logMessages === undefined); const position = await framework.exampleComponentPosition.account.position.fetch( @@ -268,6 +270,74 @@ export function ecs(framework: Framework) { expect(position.z.toNumber()).to.equal(300); }); + it("Initialize Large Component on Entity 1", async () => { + const initializeComponent = await InitializeComponent({ + payer: framework.provider.wallet.publicKey, + entity: framework.entity1Pda, + componentId: framework.componentLarge.programId, + }); + await framework.provider.sendAndConfirm(initializeComponent.transaction); + framework.componentLargeEntity1Pda = initializeComponent.componentPda; // Saved for later + }); + + it("Initialize Small Component on Entity 1", async () => { + const initializeComponent = await InitializeComponent({ + payer: framework.provider.wallet.publicKey, + entity: framework.entity1Pda, + componentId: framework.componentSmall.programId, + }); + await framework.provider.sendAndConfirm(initializeComponent.transaction); + framework.componentSmallEntity1Pda = initializeComponent.componentPda; // Saved for later + }); + + it("Apply System With Few Components on Entity 1", async () => { + const applySystem = await ApplySystem({ + authority: framework.provider.wallet.publicKey, + systemId: framework.systemWithFewComponents.programId, + world: framework.worldPda, + entities: new Array(5).fill(0).map(() => ({ + entity: framework.entity1Pda, + components: [{ componentId: framework.componentLarge.programId }], + })), + }); + let signature = await framework.provider.sendAndConfirm( + applySystem.transaction, + ); + + let transactionResponse: any; + do { + transactionResponse = + await framework.provider.connection.getTransaction(signature, { + commitment: "confirmed", + }); + } while (transactionResponse?.meta?.logMessages === undefined); + framework.report(transactionResponse?.meta?.logMessages); + }); + + it("Apply System With Many Components on Entity 1", async () => { + const applySystem = await ApplySystem({ + authority: framework.provider.wallet.publicKey, + systemId: framework.systemWithManyComponents.programId, + world: framework.worldPda, + entities: new Array(10).fill(0).map(() => ({ + entity: framework.entity1Pda, + components: [{ componentId: framework.componentSmall.programId }], + })), + }); + let signature = await framework.provider.sendAndConfirm( + applySystem.transaction, + ); + + let transactionResponse: any; + do { + transactionResponse = + await framework.provider.connection.getTransaction(signature, { + commitment: "confirmed", + }); + } while (transactionResponse?.meta?.logMessages === undefined); + framework.report(transactionResponse?.meta?.logMessages); + }); + it("Apply Fly System on Entity 4", async () => { const applySystem = await ApplySystem({ authority: framework.provider.wallet.publicKey, diff --git a/clients/typescript/test/low-level/permissioning/world.ts b/clients/typescript/test/low-level/permissioning/world.ts index fd1a5e8..bdff592 100644 --- a/clients/typescript/test/low-level/permissioning/world.ts +++ b/clients/typescript/test/low-level/permissioning/world.ts @@ -103,7 +103,7 @@ export function world(framework) { .instruction(); let transaction = new anchor.web3.Transaction().add(instruction); await framework.provider.sendAndConfirm(transaction, [], { - skipPreflight: true + skipPreflight: true, }); // Get World and check permissionless and systems diff --git a/examples/component-large/Cargo.toml b/examples/component-large/Cargo.toml new file mode 100644 index 0000000..599dbd7 --- /dev/null +++ b/examples/component-large/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "component-large" +version.workspace = true +edition.workspace = true + +[lib] +crate-type = ["cdylib", "lib"] +name = "component_large" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] +idl-build = ["bolt-lang/idl-build"] +anchor-debug = ["bolt-lang/anchor-debug"] +custom-heap = [] +custom-panic = [] + +[dependencies] +bolt-lang.workspace = true diff --git a/examples/component-large/Xargo.toml b/examples/component-large/Xargo.toml new file mode 100644 index 0000000..475fb71 --- /dev/null +++ b/examples/component-large/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/examples/component-large/src/lib.rs b/examples/component-large/src/lib.rs new file mode 100644 index 0000000..8045051 --- /dev/null +++ b/examples/component-large/src/lib.rs @@ -0,0 +1,17 @@ +use bolt_lang::*; + +declare_id!("FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX"); + +#[component] +pub struct Large { + pub value: [u8; 32], +} + +impl Default for Large { + fn default() -> Self { + Self { + bolt_metadata: BoltMetadata::default(), + value: [0; 32], + } + } +} diff --git a/examples/component-small/Cargo.toml b/examples/component-small/Cargo.toml new file mode 100644 index 0000000..4b8a50f --- /dev/null +++ b/examples/component-small/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "component-small" +version = "0.2.4" +description = "Created with Bolt" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "component_small" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] +idl-build = ["bolt-lang/idl-build"] +anchor-debug = ["bolt-lang/anchor-debug"] +custom-heap = [] +custom-panic = [] + +[dependencies] +bolt-lang.workspace = true diff --git a/examples/component-small/Xargo.toml b/examples/component-small/Xargo.toml new file mode 100644 index 0000000..475fb71 --- /dev/null +++ b/examples/component-small/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/examples/component-small/src/lib.rs b/examples/component-small/src/lib.rs new file mode 100644 index 0000000..f11cb1d --- /dev/null +++ b/examples/component-small/src/lib.rs @@ -0,0 +1,9 @@ +use bolt_lang::*; + +declare_id!("9yBADAhoTWCkNRB6hbfpwUgPpxyJiF9uEiWVPR6k7A4y"); + +#[component] +#[derive(Default)] +pub struct Small { + // Just the BoltMetadata which is 32 bytes +} \ No newline at end of file diff --git a/examples/system-with-few-components/Cargo.toml b/examples/system-with-few-components/Cargo.toml new file mode 100644 index 0000000..440c920 --- /dev/null +++ b/examples/system-with-few-components/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "system-with-few-components" +version = "0.2.4" +description = "Created with Bolt" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "system_with_few_components" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] +idl-build = ["bolt-lang/idl-build"] +anchor-debug = ["bolt-lang/anchor-debug"] +custom-heap = [] +custom-panic = [] + + +[dependencies] +bolt-lang.workspace = true +serde.workspace = true +component-large.workspace = true diff --git a/examples/system-with-few-components/Xargo.toml b/examples/system-with-few-components/Xargo.toml new file mode 100644 index 0000000..475fb71 --- /dev/null +++ b/examples/system-with-few-components/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/examples/system-with-few-components/src/lib.rs b/examples/system-with-few-components/src/lib.rs new file mode 100644 index 0000000..bae63d7 --- /dev/null +++ b/examples/system-with-few-components/src/lib.rs @@ -0,0 +1,23 @@ +use bolt_lang::*; +use component_large::Large; + +declare_id!("A3kNNSgmkTNA5V1qtnrbtNeqKrYHNxUMCTkqTDaQzE97"); + + +#[system] +pub mod system_with_few_components { + + pub fn execute(ctx: Context, _args_p: Vec) -> Result { + Ok(ctx.accounts) + } + + #[system_input] + pub struct Components { + pub large1: Large, + pub large2: Large, + pub large3: Large, + pub large4: Large, + pub large5: Large, + } + +} \ No newline at end of file diff --git a/examples/system-with-many-components/Cargo.toml b/examples/system-with-many-components/Cargo.toml new file mode 100644 index 0000000..75d3440 --- /dev/null +++ b/examples/system-with-many-components/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "system-with-many-components" +version.workspace = true +edition.workspace = true + +[lib] +crate-type = ["cdylib", "lib"] +name = "system_with_many_components" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] +idl-build = ["bolt-lang/idl-build"] +anchor-debug = ["bolt-lang/anchor-debug"] +custom-heap = [] +custom-panic = [] + + +[dependencies] +bolt-lang.workspace = true +component-small.workspace = true +serde.workspace = true diff --git a/examples/system-with-many-components/Xargo.toml b/examples/system-with-many-components/Xargo.toml new file mode 100644 index 0000000..475fb71 --- /dev/null +++ b/examples/system-with-many-components/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/examples/system-with-many-components/src/lib.rs b/examples/system-with-many-components/src/lib.rs new file mode 100644 index 0000000..28b3650 --- /dev/null +++ b/examples/system-with-many-components/src/lib.rs @@ -0,0 +1,27 @@ +use bolt_lang::*; +use component_small::Small; + +declare_id!("Hi4sMEb3uXhWCiLyrF7t3Z384an7YZsTj774cabAAPQB"); + +#[system] +pub mod system_with_many_components { + + pub fn execute(ctx: Context, _args_p: Vec) -> Result { + Ok(ctx.accounts) + } + + #[system_input] + pub struct Components { + pub small1: Small, + pub small2: Small, + pub small3: Small, + pub small4: Small, + pub small5: Small, + pub small6: Small, + pub small7: Small, + pub small8: Small, + pub small9: Small, + pub small10: Small, + } + +} \ No newline at end of file From 34cd89746741a1d6de5be8d0ee1505d706555069 Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Wed, 13 Aug 2025 16:31:22 -0300 Subject: [PATCH 03/21] :construction_worker: Adding CU measurement --- clients/typescript/test/low-level/permissioning/world.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/clients/typescript/test/low-level/permissioning/world.ts b/clients/typescript/test/low-level/permissioning/world.ts index bdff592..5ff88b1 100644 --- a/clients/typescript/test/low-level/permissioning/world.ts +++ b/clients/typescript/test/low-level/permissioning/world.ts @@ -102,10 +102,16 @@ export function world(framework) { }) .instruction(); let transaction = new anchor.web3.Transaction().add(instruction); - await framework.provider.sendAndConfirm(transaction, [], { + let signature = await framework.provider.sendAndConfirm(transaction, [], { skipPreflight: true, }); + let transactionResponse = + await framework.provider.connection.getTransaction(signature, { + commitment: "confirmed", + }); + console.log(transactionResponse.meta?.logMessages); // Reference CU is 29222 + // Get World and check permissionless and systems const worldAccount = await framework.worldProgram.account.world.fetch( framework.worldPda, From 5d1a60177b7658b98aaf4409568fccd3ae1f5b30 Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Wed, 6 Aug 2025 11:50:46 -0300 Subject: [PATCH 04/21] :construction: WIP - System directly updates components --- Cargo.lock | 2 + .../typescript/src/generated/idl/world.json | 21 ++ clients/typescript/src/generated/index.ts | 4 +- .../src/generated/instructions/apply2.ts | 133 ---------- .../src/generated/instructions/apply3.ts | 147 ----------- .../src/generated/instructions/apply4.ts | 161 ------------ .../src/generated/instructions/apply5.ts | 175 ------------- .../src/generated/instructions/index.ts | 4 - .../typescript/src/generated/types/world.ts | 21 ++ clients/typescript/src/index.ts | 9 +- clients/typescript/src/world/transactions.ts | 3 + clients/typescript/test/low-level/ecs.ts | 10 + clients/typescript/test/low-level/index.ts | 4 +- clients/typescript/test/main.ts | 2 +- .../attribute/bolt-program/Cargo.toml | 1 + .../attribute/bolt-program/src/lib.rs | 30 +-- .../component-deserialize/src/lib.rs | 2 +- .../bolt-lang/attribute/component/src/lib.rs | 2 +- .../attribute/system-input/src/lib.rs | 33 +-- crates/bolt-lang/attribute/system/Cargo.toml | 3 +- crates/bolt-lang/attribute/system/src/lib.rs | 57 ++--- crates/bolt-lang/src/account.rs | 110 +++++++++ crates/bolt-lang/src/cpi.rs | 9 + crates/bolt-lang/src/instructions/mod.rs | 5 + crates/bolt-lang/src/instructions/set_data.rs | 11 + .../bolt-lang/src/instructions/set_owner.rs | 8 + crates/bolt-lang/src/lib.rs | 6 + .../bolt-lang/utils/src/instructions/mod.rs | 12 + .../utils/src/instructions/set_data.rs | 27 ++ .../utils/src/instructions/set_owner.rs | 25 ++ crates/bolt-lang/utils/src/lib.rs | 24 +- crates/bolt-lang/utils/src/metadata.rs | 22 ++ crates/programs/bolt-component/src/lib.rs | 100 ++++---- crates/programs/bolt-system/src/lib.rs | 33 ++- crates/programs/world/src/error.rs | 2 + crates/programs/world/src/lib.rs | 233 ++++++++++++------ examples/system-apply-velocity/src/lib.rs | 4 +- examples/system-fly/src/lib.rs | 4 +- examples/system-simple-movement/src/lib.rs | 4 +- 39 files changed, 600 insertions(+), 863 deletions(-) delete mode 100644 clients/typescript/src/generated/instructions/apply2.ts delete mode 100644 clients/typescript/src/generated/instructions/apply3.ts delete mode 100644 clients/typescript/src/generated/instructions/apply4.ts delete mode 100644 clients/typescript/src/generated/instructions/apply5.ts create mode 100644 crates/bolt-lang/src/account.rs create mode 100644 crates/bolt-lang/src/cpi.rs create mode 100644 crates/bolt-lang/src/instructions/mod.rs create mode 100644 crates/bolt-lang/src/instructions/set_data.rs create mode 100644 crates/bolt-lang/src/instructions/set_owner.rs create mode 100644 crates/bolt-lang/utils/src/instructions/mod.rs create mode 100644 crates/bolt-lang/utils/src/instructions/set_data.rs create mode 100644 crates/bolt-lang/utils/src/instructions/set_owner.rs create mode 100644 crates/bolt-lang/utils/src/metadata.rs diff --git a/Cargo.lock b/Cargo.lock index a85d55e..6542dfc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -833,6 +833,7 @@ dependencies = [ name = "bolt-attribute-bolt-program" version = "0.2.4" dependencies = [ + "bolt-utils", "proc-macro2", "quote", "syn 1.0.109", @@ -842,6 +843,7 @@ dependencies = [ name = "bolt-attribute-bolt-system" version = "0.2.4" dependencies = [ + "bolt-utils", "proc-macro2", "quote", "syn 1.0.109", diff --git a/clients/typescript/src/generated/idl/world.json b/clients/typescript/src/generated/idl/world.json index 3fc9bfc..a9521b4 100644 --- a/clients/typescript/src/generated/idl/world.json +++ b/clients/typescript/src/generated/idl/world.json @@ -116,6 +116,10 @@ 225 ], "accounts": [ + { + "name": "buffer", + "writable": true + }, { "name": "bolt_system" }, @@ -128,6 +132,10 @@ }, { "name": "world" + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" } ], "args": [ @@ -150,6 +158,10 @@ 103 ], "accounts": [ + { + "name": "buffer", + "writable": true + }, { "name": "bolt_system" }, @@ -165,6 +177,10 @@ }, { "name": "session_token" + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" } ], "args": [ @@ -549,6 +565,11 @@ "code": 6005, "name": "SystemNotApproved", "msg": "The system is not approved in this world instance" + }, + { + "code": 6006, + "name": "InvalidComponentOwner", + "msg": "The component owner does not match the program" } ], "types": [ diff --git a/clients/typescript/src/generated/index.ts b/clients/typescript/src/generated/index.ts index d5c5784..52d8819 100644 --- a/clients/typescript/src/generated/index.ts +++ b/clients/typescript/src/generated/index.ts @@ -6,12 +6,12 @@ */ import { PublicKey } from "@solana/web3.js"; -import { type World as WorldProgram } from "./types/world"; import idl from "./idl/world.json"; import gpl_session from "./idl/gpl_session.json"; export * from "./accounts"; export * from "./errors"; export * from "./instructions"; +export * from "./types"; /** * Program address @@ -28,7 +28,5 @@ export const PROGRAM_ADDRESS = "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n"; * @category generated */ export const PROGRAM_ID = new PublicKey(PROGRAM_ADDRESS); - -export default WorldProgram; export { idl as worldIdl }; export { gpl_session as sessionIdl }; diff --git a/clients/typescript/src/generated/instructions/apply2.ts b/clients/typescript/src/generated/instructions/apply2.ts deleted file mode 100644 index 1983156..0000000 --- a/clients/typescript/src/generated/instructions/apply2.ts +++ /dev/null @@ -1,133 +0,0 @@ -/** - * This code was GENERATED using the solita package. - * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. - * - * See: https://github.com/metaplex-foundation/solita - */ - -import * as beet from "@metaplex-foundation/beet"; -import * as web3 from "@solana/web3.js"; - -/** - * @category Instructions - * @category Apply2 - * @category generated - */ -export interface Apply2InstructionArgs { - args: Uint8Array; -} -/** - * @category Instructions - * @category Apply2 - * @category generated - */ -export const apply2Struct = new beet.FixableBeetArgsStruct< - Apply2InstructionArgs & { - instructionDiscriminator: number[] /* size: 8 */; - } ->( - [ - ["instructionDiscriminator", beet.uniformFixedSizeArray(beet.u8, 8)], - ["args", beet.bytes], - ], - "Apply2InstructionArgs", -); -/** - * Accounts required by the _apply2_ instruction - * - * @property [] boltSystem - * @property [] componentProgram1 - * @property [_writable_] boltComponent1 - * @property [] componentProgram2 - * @property [_writable_] boltComponent2 - * @property [] authority - * @property [] instructionSysvarAccount - * @category Instructions - * @category Apply2 - * @category generated - */ -export interface Apply2InstructionAccounts { - boltSystem: web3.PublicKey; - componentProgram1: web3.PublicKey; - boltComponent1: web3.PublicKey; - componentProgram2: web3.PublicKey; - boltComponent2: web3.PublicKey; - authority: web3.PublicKey; - instructionSysvarAccount: web3.PublicKey; - anchorRemainingAccounts?: web3.AccountMeta[]; -} - -export const apply2InstructionDiscriminator = [ - 120, 32, 116, 154, 158, 159, 208, 73, -]; - -/** - * Creates a _Apply2_ instruction. - * - * @param accounts that will be accessed while the instruction is processed - * @param args to provide as instruction data to the program - * - * @category Instructions - * @category Apply2 - * @category generated - */ -export function createApply2Instruction( - accounts: Apply2InstructionAccounts, - args: Apply2InstructionArgs, - programId = new web3.PublicKey("WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n"), -) { - const [data] = apply2Struct.serialize({ - instructionDiscriminator: apply2InstructionDiscriminator, - ...args, - }); - const keys: web3.AccountMeta[] = [ - { - pubkey: accounts.boltSystem, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.componentProgram1, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.boltComponent1, - isWritable: true, - isSigner: false, - }, - { - pubkey: accounts.componentProgram2, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.boltComponent2, - isWritable: true, - isSigner: false, - }, - { - pubkey: accounts.authority, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.instructionSysvarAccount, - isWritable: false, - isSigner: false, - }, - ]; - - if (accounts.anchorRemainingAccounts != null) { - for (const acc of accounts.anchorRemainingAccounts) { - keys.push(acc); - } - } - - const ix = new web3.TransactionInstruction({ - programId, - keys, - data, - }); - return ix; -} diff --git a/clients/typescript/src/generated/instructions/apply3.ts b/clients/typescript/src/generated/instructions/apply3.ts deleted file mode 100644 index d80f54c..0000000 --- a/clients/typescript/src/generated/instructions/apply3.ts +++ /dev/null @@ -1,147 +0,0 @@ -/** - * This code was GENERATED using the solita package. - * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. - * - * See: https://github.com/metaplex-foundation/solita - */ - -import * as beet from "@metaplex-foundation/beet"; -import * as web3 from "@solana/web3.js"; - -/** - * @category Instructions - * @category Apply3 - * @category generated - */ -export interface Apply3InstructionArgs { - args: Uint8Array; -} -/** - * @category Instructions - * @category Apply3 - * @category generated - */ -export const apply3Struct = new beet.FixableBeetArgsStruct< - Apply3InstructionArgs & { - instructionDiscriminator: number[] /* size: 8 */; - } ->( - [ - ["instructionDiscriminator", beet.uniformFixedSizeArray(beet.u8, 8)], - ["args", beet.bytes], - ], - "Apply3InstructionArgs", -); -/** - * Accounts required by the _apply3_ instruction - * - * @property [] boltSystem - * @property [] componentProgram1 - * @property [_writable_] boltComponent1 - * @property [] componentProgram2 - * @property [_writable_] boltComponent2 - * @property [] componentProgram3 - * @property [_writable_] boltComponent3 - * @property [] authority - * @property [] instructionSysvarAccount - * @category Instructions - * @category Apply3 - * @category generated - */ -export interface Apply3InstructionAccounts { - boltSystem: web3.PublicKey; - componentProgram1: web3.PublicKey; - boltComponent1: web3.PublicKey; - componentProgram2: web3.PublicKey; - boltComponent2: web3.PublicKey; - componentProgram3: web3.PublicKey; - boltComponent3: web3.PublicKey; - authority: web3.PublicKey; - instructionSysvarAccount: web3.PublicKey; - anchorRemainingAccounts?: web3.AccountMeta[]; -} - -export const apply3InstructionDiscriminator = [ - 254, 146, 49, 7, 236, 131, 105, 221, -]; - -/** - * Creates a _Apply3_ instruction. - * - * @param accounts that will be accessed while the instruction is processed - * @param args to provide as instruction data to the program - * - * @category Instructions - * @category Apply3 - * @category generated - */ -export function createApply3Instruction( - accounts: Apply3InstructionAccounts, - args: Apply3InstructionArgs, - programId = new web3.PublicKey("WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n"), -) { - const [data] = apply3Struct.serialize({ - instructionDiscriminator: apply3InstructionDiscriminator, - ...args, - }); - const keys: web3.AccountMeta[] = [ - { - pubkey: accounts.boltSystem, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.componentProgram1, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.boltComponent1, - isWritable: true, - isSigner: false, - }, - { - pubkey: accounts.componentProgram2, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.boltComponent2, - isWritable: true, - isSigner: false, - }, - { - pubkey: accounts.componentProgram3, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.boltComponent3, - isWritable: true, - isSigner: false, - }, - { - pubkey: accounts.authority, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.instructionSysvarAccount, - isWritable: false, - isSigner: false, - }, - ]; - - if (accounts.anchorRemainingAccounts != null) { - for (const acc of accounts.anchorRemainingAccounts) { - keys.push(acc); - } - } - - const ix = new web3.TransactionInstruction({ - programId, - keys, - data, - }); - return ix; -} diff --git a/clients/typescript/src/generated/instructions/apply4.ts b/clients/typescript/src/generated/instructions/apply4.ts deleted file mode 100644 index 5f8c65c..0000000 --- a/clients/typescript/src/generated/instructions/apply4.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * This code was GENERATED using the solita package. - * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. - * - * See: https://github.com/metaplex-foundation/solita - */ - -import * as beet from "@metaplex-foundation/beet"; -import * as web3 from "@solana/web3.js"; - -/** - * @category Instructions - * @category Apply4 - * @category generated - */ -export interface Apply4InstructionArgs { - args: Uint8Array; -} -/** - * @category Instructions - * @category Apply4 - * @category generated - */ -export const apply4Struct = new beet.FixableBeetArgsStruct< - Apply4InstructionArgs & { - instructionDiscriminator: number[] /* size: 8 */; - } ->( - [ - ["instructionDiscriminator", beet.uniformFixedSizeArray(beet.u8, 8)], - ["args", beet.bytes], - ], - "Apply4InstructionArgs", -); -/** - * Accounts required by the _apply4_ instruction - * - * @property [] boltSystem - * @property [] componentProgram1 - * @property [_writable_] boltComponent1 - * @property [] componentProgram2 - * @property [_writable_] boltComponent2 - * @property [] componentProgram3 - * @property [_writable_] boltComponent3 - * @property [] componentProgram4 - * @property [_writable_] boltComponent4 - * @property [] authority - * @property [] instructionSysvarAccount - * @category Instructions - * @category Apply4 - * @category generated - */ -export interface Apply4InstructionAccounts { - boltSystem: web3.PublicKey; - componentProgram1: web3.PublicKey; - boltComponent1: web3.PublicKey; - componentProgram2: web3.PublicKey; - boltComponent2: web3.PublicKey; - componentProgram3: web3.PublicKey; - boltComponent3: web3.PublicKey; - componentProgram4: web3.PublicKey; - boltComponent4: web3.PublicKey; - authority: web3.PublicKey; - instructionSysvarAccount: web3.PublicKey; - anchorRemainingAccounts?: web3.AccountMeta[]; -} - -export const apply4InstructionDiscriminator = [ - 223, 104, 24, 79, 252, 196, 14, 109, -]; - -/** - * Creates a _Apply4_ instruction. - * - * @param accounts that will be accessed while the instruction is processed - * @param args to provide as instruction data to the program - * - * @category Instructions - * @category Apply4 - * @category generated - */ -export function createApply4Instruction( - accounts: Apply4InstructionAccounts, - args: Apply4InstructionArgs, - programId = new web3.PublicKey("WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n"), -) { - const [data] = apply4Struct.serialize({ - instructionDiscriminator: apply4InstructionDiscriminator, - ...args, - }); - const keys: web3.AccountMeta[] = [ - { - pubkey: accounts.boltSystem, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.componentProgram1, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.boltComponent1, - isWritable: true, - isSigner: false, - }, - { - pubkey: accounts.componentProgram2, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.boltComponent2, - isWritable: true, - isSigner: false, - }, - { - pubkey: accounts.componentProgram3, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.boltComponent3, - isWritable: true, - isSigner: false, - }, - { - pubkey: accounts.componentProgram4, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.boltComponent4, - isWritable: true, - isSigner: false, - }, - { - pubkey: accounts.authority, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.instructionSysvarAccount, - isWritable: false, - isSigner: false, - }, - ]; - - if (accounts.anchorRemainingAccounts != null) { - for (const acc of accounts.anchorRemainingAccounts) { - keys.push(acc); - } - } - - const ix = new web3.TransactionInstruction({ - programId, - keys, - data, - }); - return ix; -} diff --git a/clients/typescript/src/generated/instructions/apply5.ts b/clients/typescript/src/generated/instructions/apply5.ts deleted file mode 100644 index f8e1695..0000000 --- a/clients/typescript/src/generated/instructions/apply5.ts +++ /dev/null @@ -1,175 +0,0 @@ -/** - * This code was GENERATED using the solita package. - * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. - * - * See: https://github.com/metaplex-foundation/solita - */ - -import * as beet from "@metaplex-foundation/beet"; -import * as web3 from "@solana/web3.js"; - -/** - * @category Instructions - * @category Apply5 - * @category generated - */ -export interface Apply5InstructionArgs { - args: Uint8Array; -} -/** - * @category Instructions - * @category Apply5 - * @category generated - */ -export const apply5Struct = new beet.FixableBeetArgsStruct< - Apply5InstructionArgs & { - instructionDiscriminator: number[] /* size: 8 */; - } ->( - [ - ["instructionDiscriminator", beet.uniformFixedSizeArray(beet.u8, 8)], - ["args", beet.bytes], - ], - "Apply5InstructionArgs", -); -/** - * Accounts required by the _apply5_ instruction - * - * @property [] boltSystem - * @property [] componentProgram1 - * @property [_writable_] boltComponent1 - * @property [] componentProgram2 - * @property [_writable_] boltComponent2 - * @property [] componentProgram3 - * @property [_writable_] boltComponent3 - * @property [] componentProgram4 - * @property [_writable_] boltComponent4 - * @property [] componentProgram5 - * @property [_writable_] boltComponent5 - * @property [] authority - * @property [] instructionSysvarAccount - * @category Instructions - * @category Apply5 - * @category generated - */ -export interface Apply5InstructionAccounts { - boltSystem: web3.PublicKey; - componentProgram1: web3.PublicKey; - boltComponent1: web3.PublicKey; - componentProgram2: web3.PublicKey; - boltComponent2: web3.PublicKey; - componentProgram3: web3.PublicKey; - boltComponent3: web3.PublicKey; - componentProgram4: web3.PublicKey; - boltComponent4: web3.PublicKey; - componentProgram5: web3.PublicKey; - boltComponent5: web3.PublicKey; - authority: web3.PublicKey; - instructionSysvarAccount: web3.PublicKey; - anchorRemainingAccounts?: web3.AccountMeta[]; -} - -export const apply5InstructionDiscriminator = [ - 70, 164, 214, 28, 136, 116, 84, 153, -]; - -/** - * Creates a _Apply5_ instruction. - * - * @param accounts that will be accessed while the instruction is processed - * @param args to provide as instruction data to the program - * - * @category Instructions - * @category Apply5 - * @category generated - */ -export function createApply5Instruction( - accounts: Apply5InstructionAccounts, - args: Apply5InstructionArgs, - programId = new web3.PublicKey("WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n"), -) { - const [data] = apply5Struct.serialize({ - instructionDiscriminator: apply5InstructionDiscriminator, - ...args, - }); - const keys: web3.AccountMeta[] = [ - { - pubkey: accounts.boltSystem, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.componentProgram1, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.boltComponent1, - isWritable: true, - isSigner: false, - }, - { - pubkey: accounts.componentProgram2, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.boltComponent2, - isWritable: true, - isSigner: false, - }, - { - pubkey: accounts.componentProgram3, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.boltComponent3, - isWritable: true, - isSigner: false, - }, - { - pubkey: accounts.componentProgram4, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.boltComponent4, - isWritable: true, - isSigner: false, - }, - { - pubkey: accounts.componentProgram5, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.boltComponent5, - isWritable: true, - isSigner: false, - }, - { - pubkey: accounts.authority, - isWritable: false, - isSigner: false, - }, - { - pubkey: accounts.instructionSysvarAccount, - isWritable: false, - isSigner: false, - }, - ]; - - if (accounts.anchorRemainingAccounts != null) { - for (const acc of accounts.anchorRemainingAccounts) { - keys.push(acc); - } - } - - const ix = new web3.TransactionInstruction({ - programId, - keys, - data, - }); - return ix; -} diff --git a/clients/typescript/src/generated/instructions/index.ts b/clients/typescript/src/generated/instructions/index.ts index abde1fd..1db8f0f 100644 --- a/clients/typescript/src/generated/instructions/index.ts +++ b/clients/typescript/src/generated/instructions/index.ts @@ -7,10 +7,6 @@ export * from "./addEntity"; export * from "./apply"; -export * from "./apply2"; -export * from "./apply3"; -export * from "./apply4"; -export * from "./apply5"; export * from "./initializeComponent"; export * from "./initializeNewWorld"; export * from "./initializeRegistry"; diff --git a/clients/typescript/src/generated/types/world.ts b/clients/typescript/src/generated/types/world.ts index ac6ef36..90a127e 100644 --- a/clients/typescript/src/generated/types/world.ts +++ b/clients/typescript/src/generated/types/world.ts @@ -89,6 +89,10 @@ export type World = { name: "apply"; discriminator: [248, 243, 145, 24, 105, 50, 162, 225]; accounts: [ + { + name: "buffer"; + writable: true; + }, { name: "boltSystem"; }, @@ -102,6 +106,10 @@ export type World = { { name: "world"; }, + { + name: "systemProgram"; + address: "11111111111111111111111111111111"; + }, ]; args: [ { @@ -114,6 +122,10 @@ export type World = { name: "applyWithSession"; discriminator: [213, 69, 29, 230, 142, 107, 134, 103]; accounts: [ + { + name: "buffer"; + writable: true; + }, { name: "boltSystem"; }, @@ -130,6 +142,10 @@ export type World = { { name: "sessionToken"; }, + { + name: "systemProgram"; + address: "11111111111111111111111111111111"; + }, ]; args: [ { @@ -403,6 +419,11 @@ export type World = { name: "systemNotApproved"; msg: "The system is not approved in this world instance"; }, + { + code: 6006; + name: "invalidComponentOwner"; + msg: "The component owner does not match the program"; + }, ]; types: [ { diff --git a/clients/typescript/src/index.ts b/clients/typescript/src/index.ts index 7eb3377..815d76f 100644 --- a/clients/typescript/src/index.ts +++ b/clients/typescript/src/index.ts @@ -1,4 +1,4 @@ -import { PublicKey } from "@solana/web3.js"; +import { Keypair, PublicKey } from "@solana/web3.js"; import BN from "bn.js"; import { PROGRAM_ID as WORLD_PROGRAM_ID } from "./generated"; import { World as WORLD_PROGRAM_IDL } from "./generated/types"; @@ -99,6 +99,13 @@ export function FindComponentProgramDataPda({ )[0]; } +export function FindBufferPda() { + return PublicKey.findProgramAddressSync( + [Buffer.from("buffer")], // TODO: Everyone will share the same buffer. We need to optimize this to derive a different buffer for each user or transaction. + WORLD_PROGRAM_ID, + )[0]; +} + // TODO: seed must be Uint8Array like the other FindPda functions export function FindComponentPda({ componentId, diff --git a/clients/typescript/src/world/transactions.ts b/clients/typescript/src/world/transactions.ts index f52c5a0..c828e43 100644 --- a/clients/typescript/src/world/transactions.ts +++ b/clients/typescript/src/world/transactions.ts @@ -17,6 +17,7 @@ import { WORLD_PROGRAM_ID, BN, FindComponentProgramDataPda, + FindBufferPda, } from "../index"; import web3 from "@solana/web3.js"; import { @@ -503,6 +504,7 @@ async function createApplySystemInstruction({ return program.methods .applyWithSession(SerializeArgs(args)) .accounts({ + buffer: FindBufferPda(), authority: authority ?? PROGRAM_ID, boltSystem: systemId, sessionToken: session.token, @@ -515,6 +517,7 @@ async function createApplySystemInstruction({ return program.methods .apply(SerializeArgs(args)) .accounts({ + buffer: FindBufferPda(), authority: authority ?? PROGRAM_ID, boltSystem: systemId, world, diff --git a/clients/typescript/test/low-level/ecs.ts b/clients/typescript/test/low-level/ecs.ts index 43f7b8c..e7128c9 100644 --- a/clients/typescript/test/low-level/ecs.ts +++ b/clients/typescript/test/low-level/ecs.ts @@ -9,6 +9,7 @@ import { CPI_AUTH_ADDRESS, } from "../../lib"; import { Direction } from "../framework"; +import { FindBufferPda } from "../../src"; export function ecs(framework) { describe("ECS", () => { @@ -189,6 +190,7 @@ export function ecs(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs({ direction: Direction.Up })) .accounts({ + buffer: FindBufferPda(), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemSimpleMovement.programId, world: framework.worldPda, @@ -220,10 +222,14 @@ 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 })) .accounts({ + buffer: FindBufferPda(), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemSimpleMovement.programId, world: framework.worldPda, @@ -258,6 +264,7 @@ export function ecs(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs()) .accounts({ + buffer: FindBufferPda(), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemFly.programId, world: framework.worldPda, @@ -292,6 +299,7 @@ export function ecs(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs()) .accounts({ + buffer: FindBufferPda(), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemApplyVelocity.programId, world: framework.worldPda, @@ -345,6 +353,7 @@ export function ecs(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs()) .accounts({ + buffer: FindBufferPda(), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemApplyVelocity.programId, world: framework.worldPda, @@ -401,6 +410,7 @@ export function ecs(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs()) .accounts({ + buffer: FindBufferPda(), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemFly.programId, world: framework.worldPda, diff --git a/clients/typescript/test/low-level/index.ts b/clients/typescript/test/low-level/index.ts index 9d8d98f..0b39100 100644 --- a/clients/typescript/test/low-level/index.ts +++ b/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); }); diff --git a/clients/typescript/test/main.ts b/clients/typescript/test/main.ts index f119121..247672e 100644 --- a/clients/typescript/test/main.ts +++ b/clients/typescript/test/main.ts @@ -1,2 +1,2 @@ import "./low-level"; -import "./intermediate-level"; +// import "./intermediate-level"; diff --git a/crates/bolt-lang/attribute/bolt-program/Cargo.toml b/crates/bolt-lang/attribute/bolt-program/Cargo.toml index 86e2a89..88c441e 100644 --- a/crates/bolt-lang/attribute/bolt-program/Cargo.toml +++ b/crates/bolt-lang/attribute/bolt-program/Cargo.toml @@ -15,3 +15,4 @@ proc-macro = true syn = { workspace = true } quote = { workspace = true } proc-macro2 = { workspace = true } +bolt-utils = { workspace = true } \ No newline at end of file diff --git a/crates/bolt-lang/attribute/bolt-program/src/lib.rs b/crates/bolt-lang/attribute/bolt-program/src/lib.rs index bd81804..977ba04 100644 --- a/crates/bolt-lang/attribute/bolt-program/src/lib.rs +++ b/crates/bolt-lang/attribute/bolt-program/src/lib.rs @@ -34,9 +34,7 @@ pub fn bolt_program(args: TokenStream, input: TokenStream) -> TokenStream { extract_type_name(&args).expect("Expected a component type in macro arguments"); let modified = modify_component_module(ast, &component_type); let additional_macro: Attribute = parse_quote! { #[program] }; - let cpi_checker = generate_cpi_checker(); TokenStream::from(quote! { - #cpi_checker #additional_macro #modified }) @@ -48,7 +46,8 @@ fn modify_component_module(mut module: ItemMod, component_type: &Type) -> ItemMo let (destroy_fn, destroy_struct) = generate_destroy(component_type); let (update_fn, update_with_session_fn, update_struct, update_with_session_struct) = generate_update(component_type); - + let set_owner = bolt_utils::instructions::generate_set_owner(); + let set_data = bolt_utils::instructions::generate_set_data(); module.content = module.content.map(|(brace, mut items)| { items.extend( vec![ @@ -60,6 +59,10 @@ fn modify_component_module(mut module: ItemMod, component_type: &Type) -> ItemMo update_with_session_struct, destroy_fn, destroy_struct, + set_owner.function, + set_owner.accounts, + set_data.function, + set_data.accounts, ] .into_iter() .map(|item| syn::parse2(item).unwrap()) @@ -119,18 +122,6 @@ fn create_check_attribute() -> Attribute { } } -/// Generates the CPI checker function. -fn generate_cpi_checker() -> TokenStream2 { - quote! { - fn cpi_checker<'info>(cpi_auth: &AccountInfo<'info>) -> Result<()> { - if !cpi_auth.is_signer || cpi_auth.key != &bolt_lang::world::World::cpi_auth_address() { - return Err(BoltError::InvalidCaller.into()); - } - Ok(()) - } - } -} - /// Generates the destroy function and struct. fn generate_destroy(component_type: &Type) -> (TokenStream2, TokenStream2) { ( @@ -160,7 +151,7 @@ fn generate_destroy(component_type: &Type) -> (TokenStream2, TokenStream2) { return Err(BoltError::InvalidAuthority.into()); } - cpi_checker(&ctx.accounts.cpi_auth.to_account_info())?; + bolt_lang::cpi::checker(&ctx.accounts.cpi_auth.to_account_info())?; Ok(()) } @@ -193,7 +184,7 @@ fn generate_initialize(component_type: &Type) -> (TokenStream2, TokenStream2) { quote! { #[automatically_derived] pub fn initialize(ctx: Context) -> Result<()> { - cpi_checker(&ctx.accounts.cpi_auth.to_account_info())?; + bolt_lang::cpi::checker(&ctx.accounts.cpi_auth.to_account_info())?; ctx.accounts.data.set_inner(<#component_type>::default()); ctx.accounts.data.bolt_metadata.authority = *ctx.accounts.authority.key; Ok(()) @@ -219,6 +210,7 @@ fn generate_initialize(component_type: &Type) -> (TokenStream2, TokenStream2) { ) } +// TODO: Remove this. We are directly writing to the account. /// Generates the instructions and related structs to inject in the component. fn generate_update( component_type: &Type, @@ -229,7 +221,7 @@ fn generate_update( pub fn update(ctx: Context, data: Vec) -> Result<()> { 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); - cpi_checker(&ctx.accounts.cpi_auth.to_account_info())?; + bolt_lang::cpi::checker(&ctx.accounts.cpi_auth.to_account_info())?; ctx.accounts.bolt_component.set_inner(<#component_type>::try_from_slice(&data)?); Ok(()) @@ -251,7 +243,7 @@ fn generate_update( require_eq!(ctx.accounts.bolt_component.bolt_metadata.authority, ctx.accounts.session_token.authority, bolt_lang::session_keys::SessionError::InvalidToken); } - cpi_checker(&ctx.accounts.cpi_auth.to_account_info())?; + bolt_lang::cpi::checker(&ctx.accounts.cpi_auth.to_account_info())?; ctx.accounts.bolt_component.set_inner(<#component_type>::try_from_slice(&data)?); Ok(()) diff --git a/crates/bolt-lang/attribute/component-deserialize/src/lib.rs b/crates/bolt-lang/attribute/component-deserialize/src/lib.rs index 6ec8d23..e2f7803 100644 --- a/crates/bolt-lang/attribute/component-deserialize/src/lib.rs +++ b/crates/bolt-lang/attribute/component-deserialize/src/lib.rs @@ -1,4 +1,4 @@ -use bolt_utils::add_bolt_metadata; +use bolt_utils::metadata::add_bolt_metadata; use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, Attribute, DeriveInput}; diff --git a/crates/bolt-lang/attribute/component/src/lib.rs b/crates/bolt-lang/attribute/component/src/lib.rs index 2f4fa64..88d87c9 100644 --- a/crates/bolt-lang/attribute/component/src/lib.rs +++ b/crates/bolt-lang/attribute/component/src/lib.rs @@ -6,7 +6,7 @@ use syn::{ NestedMeta, }; -use bolt_utils::add_bolt_metadata; +use bolt_utils::metadata::add_bolt_metadata; use heck::ToSnakeCase; /// This Component attribute is used to automatically generate the seed and size functions diff --git a/crates/bolt-lang/attribute/system-input/src/lib.rs b/crates/bolt-lang/attribute/system-input/src/lib.rs index 2701aa8..5b9fc18 100644 --- a/crates/bolt-lang/attribute/system-input/src/lib.rs +++ b/crates/bolt-lang/attribute/system-input/src/lib.rs @@ -61,13 +61,20 @@ pub fn system_input(_attr: TokenStream, item: TokenStream) -> TokenStream { }) .collect(); + let bolt_accounts = fields.iter().map(|f| { + let field_type = &f.ty; + quote! { + pub type #field_type = bolt_lang::account::BoltAccount; + } + }); + // Transform fields for the struct definition let transformed_fields = fields.iter().map(|f| { let field_name = &f.ident; let field_type = &f.ty; quote! { - #[account()] - pub #field_name: Account<'info, #field_type>, + #[account(mut)] + pub #field_name: Account<'info, bolt_accounts::#field_type>, } }); @@ -82,18 +89,12 @@ pub fn system_input(_attr: TokenStream, item: TokenStream) -> TokenStream { } }; - // Generate the try_to_vec method - let try_to_vec_fields = fields.iter().map(|f| { - let field_name = &f.ident; - quote! { - self.#field_name.try_to_vec()? - } - }); - let try_from_fields = fields.iter().enumerate().map(|(i, f)| { let field_name = &f.ident; quote! { - #field_name: Account::try_from(context.remaining_accounts.as_ref().get(#i).ok_or_else(|| ErrorCode::ConstraintAccountIsNone)?)?, + #field_name: { + Account::try_from(context.remaining_accounts.as_ref().get(#i).ok_or_else(|| ErrorCode::ConstraintAccountIsNone)?)? + }, } }); @@ -111,13 +112,9 @@ pub fn system_input(_attr: TokenStream, item: TokenStream) -> TokenStream { } }; - // Generate the implementation of try_to_vec for the struct + // Generate the implementation of try_from for the struct let output_impl = quote! { impl<'info> #name<'info> { - pub fn try_to_vec(&self) -> Result>> { - Ok(vec![#(#try_to_vec_fields,)*]) - } - fn try_from<'a, 'b>(context: &Context<'a, 'b, 'info, 'info, VariadicBoltComponents<'info>>) -> Result { Ok(Self { authority: context.accounts.authority.clone(), @@ -129,6 +126,10 @@ pub fn system_input(_attr: TokenStream, item: TokenStream) -> TokenStream { // Combine the struct definition and its implementation into the final TokenStream let output = quote! { + mod bolt_accounts { + #(#bolt_accounts)* + } + #output_struct #output_impl #output_trait diff --git a/crates/bolt-lang/attribute/system/Cargo.toml b/crates/bolt-lang/attribute/system/Cargo.toml index 0475629..83142cc 100644 --- a/crates/bolt-lang/attribute/system/Cargo.toml +++ b/crates/bolt-lang/attribute/system/Cargo.toml @@ -14,4 +14,5 @@ proc-macro = true [dependencies] syn = { workspace = true, features = ["visit-mut"] } quote = { workspace = true } -proc-macro2 = { workspace = true } \ No newline at end of file +proc-macro2 = { workspace = true } +bolt-utils = { workspace = true } \ No newline at end of file diff --git a/crates/bolt-lang/attribute/system/src/lib.rs b/crates/bolt-lang/attribute/system/src/lib.rs index 9fc4705..b22bb3b 100644 --- a/crates/bolt-lang/attribute/system/src/lib.rs +++ b/crates/bolt-lang/attribute/system/src/lib.rs @@ -3,7 +3,7 @@ use proc_macro2::Ident; use quote::{quote, ToTokens, TokenStreamExt}; use syn::{ parse_macro_input, parse_quote, visit_mut::VisitMut, Expr, FnArg, GenericArgument, ItemFn, - ItemMod, ItemStruct, PathArguments, ReturnType, Stmt, Type, TypePath, + ItemMod, ItemStruct, PathArguments, Stmt, Type, }; #[derive(Default)] @@ -46,6 +46,8 @@ pub fn system(_attr: TokenStream, item: TokenStream) -> TokenStream { if let Some((_, ref mut items)) = ast.content { items.insert(0, syn::Item::Use(use_super)); SystemTransform::add_variadic_execute_function(items); + SystemTransform::add_set_data_function(items); + SystemTransform::add_set_owner_function(items); } let mut transform = SystemTransform; @@ -123,17 +125,6 @@ impl VisitMut for SystemTransform { // Modify the return type of the system function to Result,*> fn visit_item_fn_mut(&mut self, item_fn: &mut ItemFn) { if item_fn.sig.ident == "execute" { - // Modify the return type to Result> if necessary - if let ReturnType::Type(_, type_box) = &item_fn.sig.output { - if let Type::Path(type_path) = &**type_box { - if !Self::check_is_result_vec_u8(type_path) { - item_fn.sig.output = parse_quote! { -> Result>> }; - // Modify the return statement inside the function body - let block = &mut item_fn.block; - self.visit_stmts_mut(&mut block.stmts); - } - } - } // If second argument is not Vec, modify it to be so and use parse_args Self::modify_args(item_fn); } @@ -188,40 +179,28 @@ impl VisitMut for SystemTransform { impl SystemTransform { fn add_variadic_execute_function(content: &mut Vec) { content.push(syn::parse2(quote! { - pub fn bolt_execute<'info>(ctx: Context<'_, '_, 'info, 'info, VariadicBoltComponents<'info>>, args: Vec) -> Result>> { + pub fn bolt_execute<'info>(ctx: Context<'_, '_, 'info, 'info, VariadicBoltComponents<'info>>, args: Vec) -> Result<()> { let mut components = Components::try_from(&ctx)?; let bumps = ComponentsBumps {}; let context = Context::new(ctx.program_id, &mut components, ctx.remaining_accounts, bumps); - execute(context, args) + execute(context, args)?; + components.exit(&crate::id())?; + Ok(()) } }).unwrap()); } - // Helper function to check if a type is `Vec` or `(Vec, Vec, ...)` - fn check_is_result_vec_u8(ty: &TypePath) -> bool { - if let Some(segment) = ty.path.segments.last() { - if segment.ident == "Result" { - if let PathArguments::AngleBracketed(args) = &segment.arguments { - if let Some(GenericArgument::Type(Type::Tuple(tuple))) = args.args.first() { - return tuple.elems.iter().all(|elem| { - if let Type::Path(type_path) = elem { - if let Some(segment) = type_path.path.segments.first() { - return segment.ident == "Vec" && Self::is_u8_vec(segment); - } - } - false - }); - } else if let Some(GenericArgument::Type(Type::Path(type_path))) = - args.args.first() - { - if let Some(segment) = type_path.path.segments.first() { - return segment.ident == "Vec" && Self::is_u8_vec(segment); - } - } - } - } - } - false + + fn add_set_data_function(content: &mut Vec) { + let set_data = bolt_utils::instructions::generate_set_data(); + content.push(syn::parse2(set_data.function).unwrap()); + content.push(syn::parse2(set_data.accounts).unwrap()); + } + + fn add_set_owner_function(content: &mut Vec) { + let set_owner = bolt_utils::instructions::generate_set_owner(); + content.push(syn::parse2(set_owner.function).unwrap()); + content.push(syn::parse2(set_owner.accounts).unwrap()); } // Helper function to check if a type is Vec diff --git a/crates/bolt-lang/src/account.rs b/crates/bolt-lang/src/account.rs new file mode 100644 index 0000000..9513b39 --- /dev/null +++ b/crates/bolt-lang/src/account.rs @@ -0,0 +1,110 @@ +use anchor_lang::prelude::*; + +/// BoltAccount used as a workaround for altering the account ownership check during deserialization. +/// P0 and P1 are the two parts of the pubkey and it's used on the Owner trait implementation. +#[derive(Clone)] +pub struct BoltAccount(T); + +impl Discriminator for BoltAccount { + const DISCRIMINATOR: &'static [u8] = T::DISCRIMINATOR; +} + +impl anchor_lang::AccountDeserialize for BoltAccount { + fn try_deserialize(data: &mut &[u8]) -> Result { + Ok(BoltAccount(T::try_deserialize(data)?)) + } + + fn try_deserialize_unchecked(data: &mut &[u8]) -> Result { + Ok(BoltAccount(T::try_deserialize_unchecked(data)?)) + } +} + +impl anchor_lang::AccountSerialize for BoltAccount { + fn try_serialize(&self, writer: &mut W) -> Result<()> { + self.0.try_serialize(writer) + } +} + +impl anchor_lang::Owner for BoltAccount { + fn owner() -> Pubkey { + pubkey_from_array([P0, P1]) + } +} + +impl<'info, T: anchor_lang::ToAccountInfos<'info>, const P0: u128, const P1: u128> anchor_lang::ToAccountInfos<'info> for BoltAccount { + fn to_account_infos(&self) -> Vec> { + self.0.to_account_infos() + } +} + +impl<'info, T: anchor_lang::ToAccountMetas, const P0: u128, const P1: u128> anchor_lang::ToAccountMetas for BoltAccount { + fn to_account_metas(&self, is_signer: Option) -> Vec { + self.0.to_account_metas(is_signer) + } +} + +impl<'info, T: anchor_lang::AccountsExit<'info>, const P0: u128, const P1: u128> anchor_lang::AccountsExit<'info> + for BoltAccount +{ + fn exit(&self, program_id: &Pubkey) -> Result<()> { + self.0.exit(program_id) + } +} + + +impl std::ops::Deref for BoltAccount { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for BoltAccount { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(feature = "idl-build")] +impl anchor_lang::IdlBuild for BoltAccount { + fn create_type() -> Option { + T::create_type() + } + fn insert_types(types: &mut std::collections::BTreeMap) { + T::insert_types(types); + } + fn get_full_path() -> String { + T::get_full_path() + } +} + +pub const fn pubkey_p0(key: Pubkey) -> u128 { + let bytes = key.to_bytes(); + u128::from_le_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], + bytes[4], bytes[5], bytes[6], bytes[7], + bytes[8], bytes[9], bytes[10], bytes[11], + bytes[12], bytes[13], bytes[14], bytes[15], + ]) +} + +pub const fn pubkey_p1(key: Pubkey) -> u128 { + let bytes = key.to_bytes(); + u128::from_le_bytes([ + bytes[16], bytes[17], bytes[18], bytes[19], + bytes[20], bytes[21], bytes[22], bytes[23], + bytes[24], bytes[25], bytes[26], bytes[27], + bytes[28], bytes[29], bytes[30], bytes[31], + ]) +} + +pub const fn pubkey_from_u128s(p0: u128, p1: u128) -> Pubkey { + pubkey_from_array([p0, p1]) +} + +pub const fn pubkey_from_array(array: [u128; 2]) -> Pubkey { + let p0 = array[0].to_le_bytes(); + let p1 = array[1].to_le_bytes(); + Pubkey::new_from_array([p0[0], p0[1], p0[2], p0[3], p0[4], p0[5], p0[6], p0[7], p0[8], p0[9], p0[10], p0[11], p0[12], p0[13], p0[14], p0[15], p1[0], p1[1], p1[2], p1[3], p1[4], p1[5], p1[6], p1[7], p1[8], p1[9], p1[10], p1[11], p1[12], p1[13], p1[14], p1[15]]) +} diff --git a/crates/bolt-lang/src/cpi.rs b/crates/bolt-lang/src/cpi.rs new file mode 100644 index 0000000..d224f80 --- /dev/null +++ b/crates/bolt-lang/src/cpi.rs @@ -0,0 +1,9 @@ +use crate::prelude::*; +use crate::BoltError; + +pub fn checker<'info>(cpi_auth: &AccountInfo<'info>) -> Result<()> { + if !cpi_auth.is_signer || cpi_auth.key != &crate::world::World::cpi_auth_address() { + return Err(BoltError::InvalidCaller.into()); + } + Ok(()) +} diff --git a/crates/bolt-lang/src/instructions/mod.rs b/crates/bolt-lang/src/instructions/mod.rs new file mode 100644 index 0000000..610ac70 --- /dev/null +++ b/crates/bolt-lang/src/instructions/mod.rs @@ -0,0 +1,5 @@ +mod set_owner; +pub use set_owner::*; + +mod set_data; +pub use set_data::*; \ No newline at end of file diff --git a/crates/bolt-lang/src/instructions/set_data.rs b/crates/bolt-lang/src/instructions/set_data.rs new file mode 100644 index 0000000..d1bfc16 --- /dev/null +++ b/crates/bolt-lang/src/instructions/set_data.rs @@ -0,0 +1,11 @@ +use crate::prelude::*; + +pub fn set_data<'info>(cpi_auth: AccountInfo<'info>, buffer: AccountInfo<'info>, component: AccountInfo<'info>) -> Result<()> { + crate::cpi::checker(&cpi_auth)?; + let buffer_data = buffer.try_borrow_data()?; + component.realloc(buffer_data.len(), false)?; + let mut component_data = component.try_borrow_mut_data()?; + component_data.copy_from_slice(&buffer_data); + Ok(()) +} + \ No newline at end of file diff --git a/crates/bolt-lang/src/instructions/set_owner.rs b/crates/bolt-lang/src/instructions/set_owner.rs new file mode 100644 index 0000000..d28fef6 --- /dev/null +++ b/crates/bolt-lang/src/instructions/set_owner.rs @@ -0,0 +1,8 @@ +use crate::prelude::*; + +pub fn set_owner<'info>(cpi_auth: AccountInfo<'info>, component: AccountInfo<'info>, owner: Pubkey) -> Result<()> { + crate::cpi::checker(&cpi_auth)?; + component.realloc(0, false)?; + component.assign(&owner); + Ok(()) +} diff --git a/crates/bolt-lang/src/lib.rs b/crates/bolt-lang/src/lib.rs index 714d1c9..a2d8373 100644 --- a/crates/bolt-lang/src/lib.rs +++ b/crates/bolt-lang/src/lib.rs @@ -7,7 +7,13 @@ pub use anchor_lang::{ AccountDeserialize, AccountSerialize, AnchorDeserialize, AnchorSerialize, Bumps, Result, }; +pub mod instructions; +pub use instructions::*; + +pub mod cpi; +pub mod account; pub use session_keys; +pub use account::BoltAccount; pub use bolt_attribute_bolt_arguments::arguments; pub use bolt_attribute_bolt_component::component; diff --git a/crates/bolt-lang/utils/src/instructions/mod.rs b/crates/bolt-lang/utils/src/instructions/mod.rs new file mode 100644 index 0000000..9108948 --- /dev/null +++ b/crates/bolt-lang/utils/src/instructions/mod.rs @@ -0,0 +1,12 @@ +mod set_owner; +pub use set_owner::*; + +mod set_data; +pub use set_data::*; + +use proc_macro2::TokenStream; + +pub struct InstructionGeneration { + pub function: TokenStream, + pub accounts: TokenStream +} \ No newline at end of file diff --git a/crates/bolt-lang/utils/src/instructions/set_data.rs b/crates/bolt-lang/utils/src/instructions/set_data.rs new file mode 100644 index 0000000..6199497 --- /dev/null +++ b/crates/bolt-lang/utils/src/instructions/set_data.rs @@ -0,0 +1,27 @@ +use quote::quote; +use crate::instructions::InstructionGeneration; + +pub fn generate_set_data() -> InstructionGeneration { + InstructionGeneration { + function: quote! { + #[automatically_derived] + pub fn set_data(ctx: Context) -> Result<()> { + bolt_lang::instructions::set_data(ctx.accounts.cpi_auth.to_account_info(), ctx.accounts.buffer.to_account_info(), ctx.accounts.component.to_account_info()) + } + }, + accounts: quote! { + #[automatically_derived] + #[derive(Accounts)] + pub struct SetData<'info> { + #[account()] + pub cpi_auth: Signer<'info>, + /// CHECK: buffer data check + #[account()] + pub buffer: UncheckedAccount<'info>, + /// CHECK: component data check + #[account(mut)] + pub component: UncheckedAccount<'info>, + } + } + } +} \ No newline at end of file diff --git a/crates/bolt-lang/utils/src/instructions/set_owner.rs b/crates/bolt-lang/utils/src/instructions/set_owner.rs new file mode 100644 index 0000000..dec6a16 --- /dev/null +++ b/crates/bolt-lang/utils/src/instructions/set_owner.rs @@ -0,0 +1,25 @@ +use quote::quote; +use crate::instructions::InstructionGeneration; + +/// Generate the set owner function and struct. +pub fn generate_set_owner() -> InstructionGeneration { + InstructionGeneration { + function: quote! { + #[automatically_derived] + pub fn set_owner(ctx: Context, owner: Pubkey) -> Result<()> { + bolt_lang::instructions::set_owner(ctx.accounts.cpi_auth.to_account_info(), ctx.accounts.component.to_account_info(), owner) + } + }, + accounts: quote! { + #[automatically_derived] + #[derive(Accounts)] + pub struct SetOwner<'info> { + #[account()] + pub cpi_auth: Signer<'info>, + /// CHECK: This is a component account. + #[account(mut)] + pub component: UncheckedAccount<'info>, + } + }, + } +} diff --git a/crates/bolt-lang/utils/src/lib.rs b/crates/bolt-lang/utils/src/lib.rs index 1d44e75..a817af3 100644 --- a/crates/bolt-lang/utils/src/lib.rs +++ b/crates/bolt-lang/utils/src/lib.rs @@ -1,22 +1,2 @@ -use proc_macro2::Ident; -use syn::{DeriveInput, Field, Type, Visibility}; - -pub fn add_bolt_metadata(input: &mut DeriveInput) { - let authority_field: Field = Field { - attrs: vec![], - vis: Visibility::Public(syn::VisPublic { - pub_token: Default::default(), - }), - ident: Some(Ident::new("bolt_metadata", proc_macro2::Span::call_site())), - colon_token: Some(Default::default()), - ty: Type::Path(syn::TypePath { - qself: None, - path: syn::Path::from(Ident::new("BoltMetadata", proc_macro2::Span::call_site())), - }), - }; - 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); - } - } -} +pub mod metadata; +pub mod instructions; \ No newline at end of file diff --git a/crates/bolt-lang/utils/src/metadata.rs b/crates/bolt-lang/utils/src/metadata.rs new file mode 100644 index 0000000..1d44e75 --- /dev/null +++ b/crates/bolt-lang/utils/src/metadata.rs @@ -0,0 +1,22 @@ +use proc_macro2::Ident; +use syn::{DeriveInput, Field, Type, Visibility}; + +pub fn add_bolt_metadata(input: &mut DeriveInput) { + let authority_field: Field = Field { + attrs: vec![], + vis: Visibility::Public(syn::VisPublic { + pub_token: Default::default(), + }), + ident: Some(Ident::new("bolt_metadata", proc_macro2::Span::call_site())), + colon_token: Some(Default::default()), + ty: Type::Path(syn::TypePath { + qself: None, + path: syn::Path::from(Ident::new("BoltMetadata", proc_macro2::Span::call_site())), + }), + }; + 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); + } + } +} diff --git a/crates/programs/bolt-component/src/lib.rs b/crates/programs/bolt-component/src/lib.rs index 8f826b4..8b0021e 100644 --- a/crates/programs/bolt-component/src/lib.rs +++ b/crates/programs/bolt-component/src/lib.rs @@ -14,40 +14,13 @@ pub mod bolt_component { Ok(()) } - pub fn update(_ctx: Context, _data: Vec) -> Result<()> { + pub fn set_owner(_ctx: Context, _owner: Pubkey) -> Result<()> { Ok(()) } - pub fn update_with_session(_ctx: Context, _data: Vec) -> Result<()> { + pub fn set_data(_ctx: Context) -> Result<()> { Ok(()) } - - #[derive(Accounts)] - pub struct Update<'info> { - #[account()] - pub cpi_auth: Signer<'info>, - #[account(mut)] - /// CHECK: The component to update - pub bolt_component: UncheckedAccount<'info>, - #[account()] - /// CHECK: The authority of the component - pub authority: Signer<'info>, - } - - #[derive(Accounts)] - pub struct UpdateWithSession<'info> { - #[account()] - pub cpi_auth: Signer<'info>, - #[account(mut)] - /// CHECK: The component to update - pub bolt_component: UncheckedAccount<'info>, - #[account()] - /// CHECK: The authority of the component - pub authority: Signer<'info>, - #[account()] - /// CHECK: The session token - pub session_token: UncheckedAccount<'info>, - } } #[derive(Accounts)] @@ -89,6 +62,27 @@ pub struct Destroy<'info> { pub system_program: Program<'info, System>, } +#[derive(Accounts)] +pub struct SetOwner<'info> { + #[account()] + pub cpi_auth: Signer<'info>, + #[account(mut)] + /// CHECK: The component to set the owner on + pub component: UncheckedAccount<'info>, +} + +#[derive(Accounts, Clone)] +pub struct SetData<'info> { + #[account()] + pub cpi_auth: Signer<'info>, + /// CHECK: buffer data check + #[account()] + pub buffer: UncheckedAccount<'info>, + /// CHECK: component data check + #[account(mut)] + pub component: UncheckedAccount<'info>, +} + #[derive(InitSpace, AnchorSerialize, AnchorDeserialize, Default, Copy, Clone)] pub struct BoltMetadata { pub authority: Pubkey, @@ -105,28 +99,28 @@ pub trait CpiContextBuilder<'a, 'b, 'c, 'info>: ) -> 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::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) - } -} +// #[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) +// } +// } diff --git a/crates/programs/bolt-system/src/lib.rs b/crates/programs/bolt-system/src/lib.rs index 1cc8c70..5fda094 100644 --- a/crates/programs/bolt-system/src/lib.rs +++ b/crates/programs/bolt-system/src/lib.rs @@ -5,8 +5,16 @@ declare_id!("7X4EFsDJ5aYTcEjKzJ94rD8FRKgQeXC89fkpeTS4KaqP"); #[program] pub mod bolt_system { use super::*; - pub fn bolt_execute(_ctx: Context, _args: Vec) -> Result>> { - Ok(Vec::new()) + pub fn bolt_execute(_ctx: Context, _args: Vec) -> Result<()> { + Ok(()) + } + + pub fn set_data(_ctx: Context) -> Result<()> { + Ok(()) + } + + pub fn set_owner(_ctx: Context, _owner: Pubkey) -> Result<()> { + Ok(()) } } @@ -16,3 +24,24 @@ pub struct BoltExecute<'info> { #[account()] pub authority: AccountInfo<'info>, } + +#[derive(Accounts, Clone)] +pub struct SetData<'info> { + #[account()] + pub cpi_auth: Signer<'info>, + /// CHECK: buffer data check + #[account()] + pub buffer: UncheckedAccount<'info>, + /// CHECK: component data check + #[account(mut)] + pub component: UncheckedAccount<'info>, +} + +#[derive(Accounts, Clone)] +pub struct SetOwner<'info> { + #[account()] + pub cpi_auth: Signer<'info>, + /// CHECK: This is a component account. + #[account(mut)] + pub component: UncheckedAccount<'info>, +} \ No newline at end of file diff --git a/crates/programs/world/src/error.rs b/crates/programs/world/src/error.rs index e36ff3e..751929f 100644 --- a/crates/programs/world/src/error.rs +++ b/crates/programs/world/src/error.rs @@ -14,4 +14,6 @@ pub enum WorldError { AuthorityNotFound, #[msg("The system is not approved in this world instance")] SystemNotApproved, + #[msg("The component owner does not match the program")] + InvalidComponentOwner, } diff --git a/crates/programs/world/src/lib.rs b/crates/programs/world/src/lib.rs index da53257..e941a42 100644 --- a/crates/programs/world/src/lib.rs +++ b/crates/programs/world/src/lib.rs @@ -1,6 +1,5 @@ #![allow(clippy::manual_unwrap_or_default)] -use anchor_lang::prelude::*; -use bolt_component::CpiContextBuilder; +use anchor_lang::{prelude::*, system_program}; use error::WorldError; use std::collections::BTreeSet; @@ -274,31 +273,25 @@ pub mod world { ctx: Context<'_, '_, '_, 'info, Apply<'info>>, args: Vec, ) -> Result<()> { - let (pairs, results) = apply_impl( + apply_impl( + &ctx.accounts.buffer, &ctx.accounts.authority, &ctx.accounts.world, &ctx.accounts.bolt_system, + &ctx.accounts.cpi_auth, ctx.accounts.build(), args, + &ctx.accounts.system_program, ctx.remaining_accounts.to_vec(), )?; - for ((program, component), result) in pairs.into_iter().zip(results.into_iter()) { - bolt_component::cpi::update( - build_update_context( - program, - component, - ctx.accounts.authority.clone(), - ctx.accounts.cpi_auth.clone(), - &[World::cpi_auth_seeds().as_slice()], - ), - result, - )?; - } Ok(()) } #[derive(Accounts)] pub struct Apply<'info> { + /// CHECK: buffer data check + #[account(mut)] + pub buffer: AccountInfo<'info>, /// CHECK: bolt system program check #[account()] pub bolt_system: UncheckedAccount<'info>, @@ -310,6 +303,7 @@ pub mod world { pub cpi_auth: UncheckedAccount<'info>, #[account()] pub world: Account<'info, World>, + pub system_program: Program<'info, System>, } impl<'info> Apply<'info> { @@ -328,32 +322,25 @@ pub mod world { ctx: Context<'_, '_, '_, 'info, ApplyWithSession<'info>>, args: Vec, ) -> Result<()> { - let (pairs, results) = apply_impl( + apply_impl( + &ctx.accounts.buffer, &ctx.accounts.authority, &ctx.accounts.world, &ctx.accounts.bolt_system, + &ctx.accounts.cpi_auth, ctx.accounts.build(), args, + &ctx.accounts.system_program, ctx.remaining_accounts.to_vec(), )?; - for ((program, component), result) in pairs.into_iter().zip(results.into_iter()) { - bolt_component::cpi::update_with_session( - build_update_context_with_session( - program, - component, - ctx.accounts.authority.clone(), - ctx.accounts.cpi_auth.clone(), - ctx.accounts.session_token.clone(), - &[World::cpi_auth_seeds().as_slice()], - ), - result, - )?; - } Ok(()) } #[derive(Accounts)] pub struct ApplyWithSession<'info> { + /// CHECK: buffer data check + #[account(mut)] + pub buffer: AccountInfo<'info>, /// CHECK: bolt system program check #[account()] pub bolt_system: UncheckedAccount<'info>, @@ -368,6 +355,7 @@ pub mod world { #[account()] /// CHECK: The session token pub session_token: UncheckedAccount<'info>, + pub system_program: Program<'info, System>, } impl<'info> ApplyWithSession<'info> { @@ -385,13 +373,16 @@ pub mod world { #[allow(clippy::type_complexity)] fn apply_impl<'info>( + buffer: &AccountInfo<'info>, authority: &Signer<'info>, world: &Account<'info, World>, bolt_system: &UncheckedAccount<'info>, + cpi_auth: &UncheckedAccount<'info>, cpi_context: CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::BoltExecute<'info>>, args: Vec, + system_program: &Program<'info, System>, mut remaining_accounts: Vec>, -) -> Result<(Vec<(AccountInfo<'info>, AccountInfo<'info>)>, Vec>)> { +) -> Result<()> { if !authority.is_signer && authority.key != &ID { return Err(WorldError::InvalidAuthority.into()); } @@ -422,16 +413,102 @@ fn apply_impl<'info>( components_accounts.append(&mut remaining_accounts); let remaining_accounts = components_accounts; - let results = bolt_system::cpi::bolt_execute( + 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)?; + { + let mut data = buffer.try_borrow_mut_data()?; + data.copy_from_slice(component.try_borrow_data()?.as_ref()); + } + + bolt_component::cpi::set_owner( + CpiContext::new_with_signer( + program.to_account_info(), + bolt_component::cpi::accounts::SetOwner { + cpi_auth: cpi_auth.to_account_info(), + component: component.to_account_info(), + }, + &[World::cpi_auth_seeds().as_slice()], + ), + *bolt_system.key, + )?; + + bolt_system::cpi::set_data( + CpiContext::new_with_signer( + bolt_system.to_account_info(), + bolt_system::cpi::accounts::SetData { + cpi_auth: cpi_auth.to_account_info(), + buffer: buffer.to_account_info(), + component: component.to_account_info(), + }, + &[World::cpi_auth_seeds().as_slice()], + ), + )?; + } + + msg!("Executing bolt system"); + + bolt_system::cpi::bolt_execute( cpi_context.with_remaining_accounts(remaining_accounts), args, - )? - .get(); + )?; - if results.len() != pairs.len() { - return Err(WorldError::InvalidSystemOutput.into()); + msg!("Executed bolt system"); + + for (program, component) in &pairs { + buffer.realloc(component.data_len(), false)?; + { + let mut data = buffer.try_borrow_mut_data()?; + data.copy_from_slice(component.try_borrow_data()?.as_ref()); + } + + bolt_system::cpi::set_owner( + CpiContext::new_with_signer( + bolt_system.to_account_info(), + bolt_system::cpi::accounts::SetOwner { + cpi_auth: cpi_auth.to_account_info(), + component: component.to_account_info(), + }, + &[World::cpi_auth_seeds().as_slice()], + ), + program.key(), + )?; + + if *component.owner != program.key() { + return Err(WorldError::InvalidComponentOwner.into()); + } + + bolt_component::cpi::set_data( + CpiContext::new_with_signer( + program.to_account_info(), + bolt_component::cpi::accounts::SetData { + cpi_auth: cpi_auth.to_account_info(), + buffer: buffer.to_account_info(), + component: component.to_account_info(), + }, + &[World::cpi_auth_seeds().as_slice()], + ), + )?; } - Ok((pairs, results)) + + buffer.realloc(0, false)?; + + Ok(()) } #[derive(Accounts)] @@ -680,6 +757,10 @@ impl World { Pubkey::find_program_address(&[World::seed(), &self.id.to_be_bytes()], &crate::ID) } + pub fn buffer_seeds() -> [&'static [u8]; 2] { + [b"buffer", &[255]] // 251 is the pre-computed bump for buffer. + } + pub fn cpi_auth_seeds() -> [&'static [u8]; 2] { [b"cpi_auth", &[251]] // 251 is the pre-computed bump for cpi_auth. } @@ -715,43 +796,43 @@ impl SystemWhitelist { } } -/// Builds the context for updating a component. -pub fn build_update_context<'a, 'b, 'c, 'info>( - component_program: AccountInfo<'info>, - bolt_component: AccountInfo<'info>, - authority: Signer<'info>, - cpi_auth: UncheckedAccount<'info>, - signer_seeds: &'a [&'b [&'c [u8]]], -) -> CpiContext<'a, 'b, 'c, 'info, bolt_component::cpi::accounts::Update<'info>> { - let authority = authority.to_account_info(); - let cpi_auth = cpi_auth.to_account_info(); - let cpi_program = component_program; - bolt_component::cpi::accounts::Update { - bolt_component, - authority, - cpi_auth, - } - .build_cpi_context(cpi_program, signer_seeds) -} - -/// Builds the context for updating a component. -pub fn build_update_context_with_session<'a, 'b, 'c, 'info>( - component_program: AccountInfo<'info>, - bolt_component: AccountInfo<'info>, - authority: Signer<'info>, - cpi_auth: UncheckedAccount<'info>, - session_token: UncheckedAccount<'info>, - signer_seeds: &'a [&'b [&'c [u8]]], -) -> CpiContext<'a, 'b, 'c, 'info, bolt_component::cpi::accounts::UpdateWithSession<'info>> { - let authority = authority.to_account_info(); - let cpi_auth = cpi_auth.to_account_info(); - let cpi_program = component_program; - let session_token = session_token.to_account_info(); - bolt_component::cpi::accounts::UpdateWithSession { - bolt_component, - authority, - cpi_auth, - session_token, - } - .build_cpi_context(cpi_program, signer_seeds) -} +// /// Builds the context for updating a component. +// pub fn build_update_context<'a, 'b, 'c, 'info>( +// component_program: AccountInfo<'info>, +// bolt_component: AccountInfo<'info>, +// authority: Signer<'info>, +// cpi_auth: UncheckedAccount<'info>, +// signer_seeds: &'a [&'b [&'c [u8]]], +// ) -> CpiContext<'a, 'b, 'c, 'info, bolt_component::cpi::accounts::Update<'info>> { +// let authority = authority.to_account_info(); +// let cpi_auth = cpi_auth.to_account_info(); +// let cpi_program = component_program; +// bolt_component::cpi::accounts::Update { +// bolt_component, +// authority, +// cpi_auth, +// } +// .build_cpi_context(cpi_program, signer_seeds) +// } + +// /// Builds the context for updating a component. +// pub fn build_update_context_with_session<'a, 'b, 'c, 'info>( +// component_program: AccountInfo<'info>, +// bolt_component: AccountInfo<'info>, +// authority: Signer<'info>, +// cpi_auth: UncheckedAccount<'info>, +// session_token: UncheckedAccount<'info>, +// signer_seeds: &'a [&'b [&'c [u8]]], +// ) -> CpiContext<'a, 'b, 'c, 'info, bolt_component::cpi::accounts::UpdateWithSession<'info>> { +// let authority = authority.to_account_info(); +// let cpi_auth = cpi_auth.to_account_info(); +// let cpi_program = component_program; +// let session_token = session_token.to_account_info(); +// bolt_component::cpi::accounts::UpdateWithSession { +// bolt_component, +// authority, +// cpi_auth, +// session_token, +// } +// .build_cpi_context(cpi_program, signer_seeds) +// } diff --git a/examples/system-apply-velocity/src/lib.rs b/examples/system-apply-velocity/src/lib.rs index ee1db4e..637c7aa 100644 --- a/examples/system-apply-velocity/src/lib.rs +++ b/examples/system-apply-velocity/src/lib.rs @@ -7,7 +7,7 @@ declare_id!("6LHhFVwif6N9Po3jHtSmMVtPjF6zRfL3xMosSzcrQAS8"); #[system] pub mod system_apply_velocity { - pub fn execute(ctx: Context, _args: Vec) -> Result { + pub fn execute(ctx: Context, _args: Vec) -> Result<()> { ctx.accounts.velocity.x = 10; let mut clock = Clock::get()?; if let Ok(clock_account_info) = ctx.sysvar_clock() { @@ -16,7 +16,7 @@ pub mod system_apply_velocity { } ctx.accounts.velocity.last_applied = clock.unix_timestamp; ctx.accounts.position.x += 10 * (ctx.accounts.velocity.x + 2) + 3; - Ok(ctx.accounts) + Ok(()) } #[system_input] diff --git a/examples/system-fly/src/lib.rs b/examples/system-fly/src/lib.rs index c605a28..830fe05 100644 --- a/examples/system-fly/src/lib.rs +++ b/examples/system-fly/src/lib.rs @@ -6,10 +6,10 @@ declare_id!("HT2YawJjkNmqWcLNfPAMvNsLdWwPvvvbKA5bpMw4eUpq"); #[system] pub mod system_fly { - pub fn execute(ctx: Context, _args: Vec) -> Result { + pub fn execute(ctx: Context, _args: Vec) -> Result<()> { let pos = &mut ctx.accounts.position; pos.z += 1; - Ok(ctx.accounts) + Ok(()) } #[system_input] diff --git a/examples/system-simple-movement/src/lib.rs b/examples/system-simple-movement/src/lib.rs index 311996d..0ac0ed6 100644 --- a/examples/system-simple-movement/src/lib.rs +++ b/examples/system-simple-movement/src/lib.rs @@ -4,7 +4,7 @@ declare_id!("FSa6qoJXFBR3a7ThQkTAMrC15p6NkchPEjBdd4n6dXxA"); #[system] pub mod system_simple_movement { - pub fn execute(ctx: Context, args: Args) -> Result { + pub fn execute(ctx: Context, args: Args) -> Result<()> { // Compute the new position based on the direction let (dx, dy) = match args.direction { Direction::Left => (-1, 0), @@ -15,7 +15,7 @@ pub mod system_simple_movement { ctx.accounts.position.x += dx; ctx.accounts.position.y += dy; - Ok(ctx.accounts) + Ok(()) } #[system_input] From 08ae6580e7c269e05d72f9f83168232e0c81fc0c Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Thu, 7 Aug 2025 17:42:28 -0300 Subject: [PATCH 05/21] :construction: on-chain component serialization --- Cargo.lock | 1 + clients/typescript/src/index.ts | 2 +- .../bolt-cli/src/templates/component/mod.rs | 4 +- .../component-deserialize/src/lib.rs | 8 +++- crates/bolt-lang/src/account.rs | 11 +++-- crates/bolt-lang/src/bpf_writer.rs | 48 +++++++++++++++++++ crates/bolt-lang/src/lib.rs | 2 + crates/types/Cargo.toml | 5 +- ...zEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs | 2 - examples/system-simple-movement/src/lib.rs | 6 ++- 10 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 crates/bolt-lang/src/bpf_writer.rs diff --git a/Cargo.lock b/Cargo.lock index 6542dfc..6605162 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -924,6 +924,7 @@ dependencies = [ name = "bolt-types" version = "0.2.4" dependencies = [ + "anchor-lang", "bolt-lang", ] diff --git a/clients/typescript/src/index.ts b/clients/typescript/src/index.ts index 815d76f..3643ecc 100644 --- a/clients/typescript/src/index.ts +++ b/clients/typescript/src/index.ts @@ -1,4 +1,4 @@ -import { Keypair, PublicKey } from "@solana/web3.js"; +import { PublicKey } from "@solana/web3.js"; import BN from "bn.js"; import { PROGRAM_ID as WORLD_PROGRAM_ID } from "./generated"; import { World as WORLD_PROGRAM_IDL } from "./generated/types"; diff --git a/crates/bolt-cli/src/templates/component/mod.rs b/crates/bolt-cli/src/templates/component/mod.rs index 0cb9dee..2dfc263 100644 --- a/crates/bolt-cli/src/templates/component/mod.rs +++ b/crates/bolt-cli/src/templates/component/mod.rs @@ -38,13 +38,15 @@ pub fn component_type(idl: &Idl, component_id: &str) -> Result { Some(ty) => ty, None => return Err(anyhow::anyhow!("Component type not found in IDL")), }; + + let component_name = component_account.name.to_upper_camel_case(); + println!("Component name: {}", component_name); let component_code = component_to_rust_code(type_def, component_id); let types_code = component_types_to_rust_code(&idl.types, &component_account.name); Ok(format!( r#"use bolt_lang::*; #[component_deserialize] -#[derive(Clone, Copy)] {} {} diff --git a/crates/bolt-lang/attribute/component-deserialize/src/lib.rs b/crates/bolt-lang/attribute/component-deserialize/src/lib.rs index e2f7803..4728a31 100644 --- a/crates/bolt-lang/attribute/component-deserialize/src/lib.rs +++ b/crates/bolt-lang/attribute/component-deserialize/src/lib.rs @@ -63,7 +63,13 @@ pub fn component_deserialize(_attr: TokenStream, item: TokenStream) -> TokenStre #[automatically_derived] impl bolt_lang::AccountSerialize for #name { - fn try_serialize(&self, _writer: &mut W) -> Result<()> { + fn try_serialize(&self, writer: &mut W) -> Result<()> { + if writer.write_all(Self::DISCRIMINATOR).is_err() { + return Err(bolt_lang::AccountDidNotSerializeErrorCode.into()); + } + if bolt_lang::AnchorSerialize::serialize(self, writer).is_err() { + return Err(bolt_lang::AccountDidNotSerializeErrorCode.into()); + } Ok(()) } } diff --git a/crates/bolt-lang/src/account.rs b/crates/bolt-lang/src/account.rs index 9513b39..fe00535 100644 --- a/crates/bolt-lang/src/account.rs +++ b/crates/bolt-lang/src/account.rs @@ -43,11 +43,16 @@ impl<'info, T: anchor_lang::ToAccountMetas, const P0: u128, const P1: u128> anch } } -impl<'info, T: anchor_lang::AccountsExit<'info>, const P0: u128, const P1: u128> anchor_lang::AccountsExit<'info> +impl<'info, T: anchor_lang::ToAccountInfos<'info> + anchor_lang::ToAccountInfo<'info> + anchor_lang::AccountSerialize + anchor_lang::AccountsExit<'info>, const P0: u128, const P1: u128> anchor_lang::AccountsExit<'info> for BoltAccount { - fn exit(&self, program_id: &Pubkey) -> Result<()> { - self.0.exit(program_id) + fn exit(&self, _program_id: &Pubkey) -> Result<()> { + let info = self.0.to_account_info(); + let mut data = info.try_borrow_mut_data()?; + let dst: &mut [u8] = &mut data; + let mut writer = crate::bpf_writer::BpfWriter::new(dst); + self.0.try_serialize(&mut writer)?; + Ok(()) } } diff --git a/crates/bolt-lang/src/bpf_writer.rs b/crates/bolt-lang/src/bpf_writer.rs new file mode 100644 index 0000000..b679aa2 --- /dev/null +++ b/crates/bolt-lang/src/bpf_writer.rs @@ -0,0 +1,48 @@ +/// Implementation from Anchor. + +use solana_program::program_memory::sol_memcpy; +use std::cmp; +use std::io::{self, Write}; + +#[derive(Debug, Default)] +pub struct BpfWriter { + inner: T, + pub pos: u64, +} + +impl BpfWriter { + pub fn new(inner: T) -> Self { + Self { inner, pos: 0 } + } +} + +impl Write for BpfWriter<&mut [u8]> { + fn write(&mut self, buf: &[u8]) -> io::Result { + if self.pos >= self.inner.len() as u64 { + return Ok(0); + } + + let amt = cmp::min( + self.inner.len().saturating_sub(self.pos as usize), + buf.len(), + ); + sol_memcpy(&mut self.inner[(self.pos as usize)..], buf, amt); + self.pos += amt as u64; + Ok(amt) + } + + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + if self.write(buf)? == buf.len() { + Ok(()) + } else { + Err(io::Error::new( + io::ErrorKind::WriteZero, + "failed to write whole buffer", + )) + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/crates/bolt-lang/src/lib.rs b/crates/bolt-lang/src/lib.rs index a2d8373..5c9c1b9 100644 --- a/crates/bolt-lang/src/lib.rs +++ b/crates/bolt-lang/src/lib.rs @@ -2,12 +2,14 @@ pub mod prelude; pub use anchor_lang; pub use anchor_lang::error::ErrorCode::AccountDidNotDeserialize as AccountDidNotDeserializeErrorCode; +pub use anchor_lang::error::ErrorCode::AccountDidNotSerialize as AccountDidNotSerializeErrorCode; pub use anchor_lang::prelude::*; pub use anchor_lang::{ AccountDeserialize, AccountSerialize, AnchorDeserialize, AnchorSerialize, Bumps, Result, }; pub mod instructions; +pub mod bpf_writer; pub use instructions::*; pub mod cpi; diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index c33ef51..fc2a0e1 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bolt-types" -version.workspace = true +version = "0.2.4" description = "Autogenerate types for the bolt language" edition = "2021" @@ -9,4 +9,5 @@ crate-type = ["cdylib", "lib"] name = "bolt_types" [dependencies] -bolt-lang.workspace = true \ No newline at end of file +bolt-lang.workspace = true +anchor-lang.workspace = true diff --git a/crates/types/src/component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs b/crates/types/src/component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs index 2643011..033277e 100644 --- a/crates/types/src/component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs +++ b/crates/types/src/component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs @@ -6,5 +6,3 @@ pub struct ComponentFn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ { pub y: i64, pub z: i64, } - -pub use ComponentFn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ as Position; diff --git a/examples/system-simple-movement/src/lib.rs b/examples/system-simple-movement/src/lib.rs index 0ac0ed6..7c1c572 100644 --- a/examples/system-simple-movement/src/lib.rs +++ b/examples/system-simple-movement/src/lib.rs @@ -14,7 +14,11 @@ 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(()) } From 82a06bbe483574c79666acff60f3b31770f5c510 Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Wed, 13 Aug 2025 00:27:47 -0300 Subject: [PATCH 06/21] :recycle: Authority & Session checks --- Cargo.lock | 2 + Cargo.toml | 1 + clients/typescript/src/generated/index.ts | 3 +- clients/typescript/src/world/transactions.ts | 26 ++----- clients/typescript/test/framework.ts | 35 --------- clients/typescript/test/low-level/ecs.ts | 3 - clients/typescript/test/low-level/index.ts | 4 +- .../test/low-level/permissioning/component.ts | 3 + .../test/low-level/permissioning/world.ts | 9 ++- clients/typescript/test/low-level/session.ts | 3 + clients/typescript/test/main.ts | 2 +- clients/typescript/yarn.lock | 32 ++++---- .../bolt-cli/src/templates/component/mod.rs | 15 ++-- .../component-deserialize/Cargo.toml | 1 + .../component-deserialize/src/lib.rs | 41 +++++++++- crates/bolt-lang/utils/src/metadata.rs | 2 +- crates/programs/bolt-component/src/lib.rs | 26 ------- crates/programs/world/Cargo.toml | 1 + crates/programs/world/src/lib.rs | 77 +++++++++++++------ ...zEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs | 2 +- examples/system-simple-movement/src/lib.rs | 5 -- scripts/test.sh | 6 -- 22 files changed, 152 insertions(+), 147 deletions(-) delete mode 100755 scripts/test.sh diff --git a/Cargo.lock b/Cargo.lock index 6605162..e356e6f 100644 --- a/Cargo.lock +++ b/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", ] diff --git a/Cargo.toml b/Cargo.toml index 6bf9029..e138c86 100644 --- a/Cargo.toml +++ b/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 diff --git a/clients/typescript/src/generated/index.ts b/clients/typescript/src/generated/index.ts index 52d8819..851ec3e 100644 --- a/clients/typescript/src/generated/index.ts +++ b/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 diff --git a/clients/typescript/src/world/transactions.ts b/clients/typescript/src/world/transactions.ts index c828e43..a05fcac 100644 --- a/clients/typescript/src/world/transactions.ts +++ b/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; + 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; + 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; + 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; + 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; + 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 { - const program = new Program( - worldIdl as Idl, - ) as unknown as Program; + const program = new Program(worldIdl as Idl) as unknown as Program; let componentCount = 0; entities.forEach(function (entity) { componentCount += entity.components.length; diff --git a/clients/typescript/test/framework.ts b/clients/typescript/test/framework.ts index 628dd62..f2e8f2f 100644 --- a/clients/typescript/test/framework.ts +++ b/clients/typescript/test/framework.ts @@ -70,7 +70,6 @@ export class Framework { ); } - consume(line: string): number { let consumed = line.split(" consumed ")[1].split(" of ")[0]; return parseInt(consumed); @@ -94,38 +93,4 @@ export class Framework { console.log(`Total CPI Consumed: ${total}`); console.log(`Number of Instructions: ${numberOfInstructions}`); console.log(`World Report: ${worldReport}`); - } } - -// example input -// [ -// 'Program WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n invoke [1]', -// 'Program log: Instruction: Apply', -// 'Program A3kNNSgmkTNA5V1qtnrbtNeqKrYHNxUMCTkqTDaQzE97 invoke [2]', -// 'Program log: Instruction: BoltExecute', -// 'Program A3kNNSgmkTNA5V1qtnrbtNeqKrYHNxUMCTkqTDaQzE97 consumed 3679 of 191671 compute units', -// 'Program return: A3kNNSgmkTNA5V1qtnrbtNeqKrYHNxUMCTkqTDaQzE97 BQAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAei0UnvLCu8bsxLLo92w4UVq1z4LrLIGvtTpkos13GLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6LRSe8sK7xuzEsuj3bDhRWrXPgussga+1OmSizXcYtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHotFJ7ywrvG7MSy6PdsOFFatc+C6yyBr7U6ZKLNdxi0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAei0UnvLCu8bsxLLo92w4UVq1z4LrLIGvtTpkos13GLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6LRSe8sK7xuzEsuj3bDhRWrXPgussga+1OmSizXcYs=', -// 'Program A3kNNSgmkTNA5V1qtnrbtNeqKrYHNxUMCTkqTDaQzE97 success', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX invoke [2]', -// 'Program log: Instruction: Update', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX consumed 4732 of 183865 compute units', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX success', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX invoke [2]', -// 'Program log: Instruction: Update', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX consumed 4732 of 176143 compute units', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX success', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX invoke [2]', -// 'Program log: Instruction: Update', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX consumed 4732 of 168421 compute units', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX success', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX invoke [2]', -// 'Program log: Instruction: Update', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX consumed 4732 of 160699 compute units', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX success', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX invoke [2]', -// 'Program log: Instruction: Update', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX consumed 4732 of 152977 compute units', -// 'Program FJjiJoz799Q6NqYffXbsFFj1pBmwsQZgcoizCfWvM5HX success', -// 'Program WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n consumed 52251 of 200000 compute units', -// 'Program WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n success' -// ] \ No newline at end of file diff --git a/clients/typescript/test/low-level/ecs.ts b/clients/typescript/test/low-level/ecs.ts index e7128c9..843bb5f 100644 --- a/clients/typescript/test/low-level/ecs.ts +++ b/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 })) diff --git a/clients/typescript/test/low-level/index.ts b/clients/typescript/test/low-level/index.ts index 0b39100..9d8d98f 100644 --- a/clients/typescript/test/low-level/index.ts +++ b/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); }); diff --git a/clients/typescript/test/low-level/permissioning/component.ts b/clients/typescript/test/low-level/permissioning/component.ts index 610cf03..cfe469e 100644 --- a/clients/typescript/test/low-level/permissioning/component.ts +++ b/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, diff --git a/clients/typescript/test/low-level/permissioning/world.ts b/clients/typescript/test/low-level/permissioning/world.ts index 5ff88b1..ab75998 100644 --- a/clients/typescript/test/low-level/permissioning/world.ts +++ b/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, diff --git a/clients/typescript/test/low-level/session.ts b/clients/typescript/test/low-level/session.ts index f613ec2..b924232 100644 --- a/clients/typescript/test/low-level/session.ts +++ b/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, diff --git a/clients/typescript/test/main.ts b/clients/typescript/test/main.ts index 247672e..f119121 100644 --- a/clients/typescript/test/main.ts +++ b/clients/typescript/test/main.ts @@ -1,2 +1,2 @@ import "./low-level"; -// import "./intermediate-level"; +import "./intermediate-level"; diff --git a/clients/typescript/yarn.lock b/clients/typescript/yarn.lock index d13a8be..d12e8e6 100644 --- a/clients/typescript/yarn.lock +++ b/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== diff --git a/crates/bolt-cli/src/templates/component/mod.rs b/crates/bolt-cli/src/templates/component/mod.rs index 2dfc263..000cea4 100644 --- a/crates/bolt-cli/src/templates/component/mod.rs +++ b/crates/bolt-cli/src/templates/component/mod.rs @@ -46,12 +46,14 @@ pub fn component_type(idl: &Idl, component_id: &str) -> Result { 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"; diff --git a/crates/bolt-lang/attribute/component-deserialize/Cargo.toml b/crates/bolt-lang/attribute/component-deserialize/Cargo.toml index 89ff8eb..268ca6a 100644 --- a/crates/bolt-lang/attribute/component-deserialize/Cargo.toml +++ b/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 } diff --git a/crates/bolt-lang/attribute/component-deserialize/src/lib.rs b/crates/bolt-lang/attribute/component-deserialize/src/lib.rs index 4728a31..6c42e34 100644 --- a/crates/bolt-lang/attribute/component-deserialize/src/lib.rs +++ b/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 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::(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::>(); + 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 diff --git a/crates/bolt-lang/utils/src/metadata.rs b/crates/bolt-lang/utils/src/metadata.rs index 1d44e75..99ea4d3 100644 --- a/crates/bolt-lang/utils/src/metadata.rs +++ b/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); } } } diff --git a/crates/programs/bolt-component/src/lib.rs b/crates/programs/bolt-component/src/lib.rs index 8b0021e..0b18677 100644 --- a/crates/programs/bolt-component/src/lib.rs +++ b/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) -// } -// } diff --git a/crates/programs/world/Cargo.toml b/crates/programs/world/Cargo.toml index 4292d2a..e1be12e 100644 --- a/crates/programs/world/Cargo.toml +++ b/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 \ No newline at end of file diff --git a/crates/programs/world/src/lib.rs b/crates/programs/world/src/lib.rs index e941a42..e6abf52 100644 --- a/crates/programs/world/src/lib.rs +++ b/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, system_program: &Program<'info, System>, + session_token: Option<&Account<'info, session_keys::SessionToken>>, mut remaining_accounts: Vec>, ) -> 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)?; { diff --git a/crates/types/src/component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs b/crates/types/src/component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs index 033277e..7b911a6 100644 --- a/crates/types/src/component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs +++ b/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, diff --git a/examples/system-simple-movement/src/lib.rs b/examples/system-simple-movement/src/lib.rs index 7c1c572..881c827 100644 --- a/examples/system-simple-movement/src/lib.rs +++ b/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(()) } diff --git a/scripts/test.sh b/scripts/test.sh deleted file mode 100755 index ed68311..0000000 --- a/scripts/test.sh +++ /dev/null @@ -1,6 +0,0 @@ -SCRIPT_DIR=$(dirname "$0") -PROJECT_DIR="$SCRIPT_DIR/.." -pushd "$PROJECT_DIR" -cp tests/keys/* target/deploy/ -bolt test -popd \ No newline at end of file From 6d51b71f86d0631ab97d9ae3ae177b0303822bae Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Wed, 13 Aug 2025 02:17:54 -0300 Subject: [PATCH 07/21] :recycle: Updating C# client --- .../csharp/Solana.Unity.Bolt.Test/ECSTest.cs | 2 + .../Solana.Unity.Bolt.Test/Framework.cs | 3 +- .../WorldProgram/Bolt/DestroyComponent.cs | 1 + .../WorldProgram/Bolt/InitializeComponent.cs | 1 + .../WorldProgram/Generated.cs | 1215 +++++++++-------- .../Solana.Unity.Bolt/WorldProgram/World.cs | 14 + .../attribute/bolt-program/src/lib.rs | 74 - 7 files changed, 668 insertions(+), 642 deletions(-) diff --git a/clients/csharp/Solana.Unity.Bolt.Test/ECSTest.cs b/clients/csharp/Solana.Unity.Bolt.Test/ECSTest.cs index 6682eb6..a599efc 100644 --- a/clients/csharp/Solana.Unity.Bolt.Test/ECSTest.cs +++ b/clients/csharp/Solana.Unity.Bolt.Test/ECSTest.cs @@ -108,9 +108,11 @@ public static async Task CheckPositionOnEntity1IsDefault(Framework framework) { public static async Task ApplySimpleMovementSystemUpOnEntity1(Framework framework) { var apply = new ApplyAccounts() { + CpiAuth = WorldProgram.CPI_AUTH_ADDRESS, Authority = framework.Wallet.Account.PublicKey, BoltSystem = framework.SystemSimpleMovement, World = framework.WorldPda, + Buffer = WorldProgram.FindBufferPda(), }; var instruction = WorldProgram.Apply(apply, Bolt.World.SerializeArgs(new { direction = "Up" })); instruction.Keys.Add(AccountMeta.ReadOnly(framework.ExampleComponentPosition, false)); diff --git a/clients/csharp/Solana.Unity.Bolt.Test/Framework.cs b/clients/csharp/Solana.Unity.Bolt.Test/Framework.cs index f8311b7..430d770 100644 --- a/clients/csharp/Solana.Unity.Bolt.Test/Framework.cs +++ b/clients/csharp/Solana.Unity.Bolt.Test/Framework.cs @@ -96,7 +96,7 @@ public async Task SendAndConfirmInstruction(IRpcClient client, Transacti .AddInstruction(instruction) .Build(signers); - var signature = await client.SendTransactionAsync(transaction, true, Commitment.Processed); + var signature = await client.SendTransactionAsync(transaction, false, Commitment.Processed); var confirmed = await client.ConfirmTransaction(signature.Result, Commitment.Processed); if (signature.WasSuccessful && confirmed) { @@ -107,6 +107,7 @@ public async Task SendAndConfirmInstruction(IRpcClient client, Transacti if (signature.ErrorData != null) { errorMessage += "\n" + string.Join("\n", signature.ErrorData.Logs); } + Console.WriteLine(errorMessage); throw new Exception(errorMessage); } diff --git a/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/DestroyComponent.cs b/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/DestroyComponent.cs index 8437c6c..c4715a8 100644 --- a/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/DestroyComponent.cs +++ b/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/DestroyComponent.cs @@ -30,6 +30,7 @@ public static async Task DestroyComponent(PublicKey public static async Task DestroyComponent(PublicKey authority, PublicKey receiver, PublicKey entity, PublicKey componentProgram, PublicKey componentPda) { var componentProgramData = WorldProgram.FindComponentProgramDataPda(componentProgram); var destroyComponent = new DestroyComponentAccounts() { + CpiAuth = WorldProgram.CPI_AUTH_ADDRESS, Authority = authority, Receiver = receiver, Entity = entity, diff --git a/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/InitializeComponent.cs b/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/InitializeComponent.cs index 6d29745..ff9c8c6 100644 --- a/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/InitializeComponent.cs +++ b/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/InitializeComponent.cs @@ -30,6 +30,7 @@ public static async Task InitializeComponent(Pub public static async Task InitializeComponent(PublicKey payer, PublicKey entity, PublicKey componentId, PublicKey componentPda, PublicKey authority = null) { var initializeComponent = new InitializeComponentAccounts() { + CpiAuth = WorldProgram.CPI_AUTH_ADDRESS, Payer = payer, Entity = entity, Data = componentPda, diff --git a/clients/csharp/Solana.Unity.Bolt/WorldProgram/Generated.cs b/clients/csharp/Solana.Unity.Bolt/WorldProgram/Generated.cs index 9d5cf63..41e8b42 100644 --- a/clients/csharp/Solana.Unity.Bolt/WorldProgram/Generated.cs +++ b/clients/csharp/Solana.Unity.Bolt/WorldProgram/Generated.cs @@ -1,568 +1,649 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; -using Solana.Unity; -using Solana.Unity.Programs.Abstract; -using Solana.Unity.Programs.Utilities; -using Solana.Unity.Rpc; -using Solana.Unity.Rpc.Builders; -using Solana.Unity.Rpc.Core.Http; -using Solana.Unity.Rpc.Core.Sockets; -using Solana.Unity.Rpc.Types; -using Solana.Unity.Wallet; -using World; -using World.Program; -using World.Errors; -using World.Accounts; -using World.Types; - -namespace World -{ - namespace Accounts - { - public partial class Entity - { - public static ulong ACCOUNT_DISCRIMINATOR => 1751670451238706478UL; - public static ReadOnlySpan ACCOUNT_DISCRIMINATOR_BYTES => new byte[]{46, 157, 161, 161, 254, 46, 79, 24}; - public static string ACCOUNT_DISCRIMINATOR_B58 => "8oEQa6zH67R"; - public ulong Id { get; set; } - - public static Entity Deserialize(ReadOnlySpan _data) - { - int offset = 0; - ulong accountHashValue = _data.GetU64(offset); - offset += 8; - if (accountHashValue != ACCOUNT_DISCRIMINATOR) - { - return null; - } - - Entity result = new Entity(); - result.Id = _data.GetU64(offset); - offset += 8; - return result; - } - } - - public partial class Registry - { - public static ulong ACCOUNT_DISCRIMINATOR => 15779688099924061743UL; - public static ReadOnlySpan ACCOUNT_DISCRIMINATOR_BYTES => new byte[]{47, 174, 110, 246, 184, 182, 252, 218}; - public static string ACCOUNT_DISCRIMINATOR_B58 => "8ya1XGY4XBP"; - public ulong Worlds { get; set; } - - public static Registry Deserialize(ReadOnlySpan _data) - { - int offset = 0; - ulong accountHashValue = _data.GetU64(offset); - offset += 8; - if (accountHashValue != ACCOUNT_DISCRIMINATOR) - { - return null; - } - - Registry result = new Registry(); - result.Worlds = _data.GetU64(offset); - offset += 8; - return result; - } - } - - public partial class World - { - public static ulong ACCOUNT_DISCRIMINATOR => 8978805993381703057UL; - public static ReadOnlySpan ACCOUNT_DISCRIMINATOR_BYTES => new byte[]{145, 45, 170, 174, 122, 32, 155, 124}; - public static string ACCOUNT_DISCRIMINATOR_B58 => "RHQudtaQtu1"; - public ulong Id { get; set; } - - public ulong Entities { get; set; } - - public PublicKey[] Authorities { get; set; } - - public bool Permissionless { get; set; } - - public byte[] Systems { get; set; } - - public static World Deserialize(ReadOnlySpan _data) - { - int offset = 0; - ulong accountHashValue = _data.GetU64(offset); - offset += 8; - if (accountHashValue != ACCOUNT_DISCRIMINATOR) - { - return null; - } - - World result = new World(); - result.Id = _data.GetU64(offset); - offset += 8; - result.Entities = _data.GetU64(offset); - offset += 8; - int resultAuthoritiesLength = (int)_data.GetU32(offset); - offset += 4; - result.Authorities = new PublicKey[resultAuthoritiesLength]; - for (uint resultAuthoritiesIdx = 0; resultAuthoritiesIdx < resultAuthoritiesLength; resultAuthoritiesIdx++) - { - result.Authorities[resultAuthoritiesIdx] = _data.GetPubKey(offset); - offset += 32; - } - - result.Permissionless = _data.GetBool(offset); - offset += 1; - int resultSystemsLength = (int)_data.GetU32(offset); - offset += 4; - result.Systems = _data.GetBytes(offset, resultSystemsLength); - offset += resultSystemsLength; - return result; - } - } - } - - namespace Errors - { - public enum WorldErrorKind : uint - { - InvalidAuthority = 6000U, - InvalidSystemOutput = 6001U, - WorldAccountMismatch = 6002U, - TooManyAuthorities = 6003U, - AuthorityNotFound = 6004U, - SystemNotApproved = 6005U - } - } - - namespace Types - { - } - - public partial class WorldClient : TransactionalBaseClient - { - public WorldClient(IRpcClient rpcClient, IStreamingRpcClient streamingRpcClient, PublicKey programId = null) : base(rpcClient, streamingRpcClient, programId ?? new PublicKey(WorldProgram.ID)) - { - } - - public async Task>> GetEntitysAsync(string programAddress = WorldProgram.ID, Commitment commitment = Commitment.Confirmed) - { - var list = new List{new Solana.Unity.Rpc.Models.MemCmp{Bytes = Entity.ACCOUNT_DISCRIMINATOR_B58, Offset = 0}}; - var res = await RpcClient.GetProgramAccountsAsync(programAddress, commitment, memCmpList: list); - if (!res.WasSuccessful || !(res.Result?.Count > 0)) - return new Solana.Unity.Programs.Models.ProgramAccountsResultWrapper>(res); - List resultingAccounts = new List(res.Result.Count); - resultingAccounts.AddRange(res.Result.Select(result => Entity.Deserialize(Convert.FromBase64String(result.Account.Data[0])))); - return new Solana.Unity.Programs.Models.ProgramAccountsResultWrapper>(res, resultingAccounts); - } - - public async Task>> GetRegistrysAsync(string programAddress = WorldProgram.ID, Commitment commitment = Commitment.Confirmed) - { - var list = new List{new Solana.Unity.Rpc.Models.MemCmp{Bytes = Registry.ACCOUNT_DISCRIMINATOR_B58, Offset = 0}}; - var res = await RpcClient.GetProgramAccountsAsync(programAddress, commitment, memCmpList: list); - if (!res.WasSuccessful || !(res.Result?.Count > 0)) - return new Solana.Unity.Programs.Models.ProgramAccountsResultWrapper>(res); - List resultingAccounts = new List(res.Result.Count); - resultingAccounts.AddRange(res.Result.Select(result => Registry.Deserialize(Convert.FromBase64String(result.Account.Data[0])))); - return new Solana.Unity.Programs.Models.ProgramAccountsResultWrapper>(res, resultingAccounts); - } - - public async Task>> GetWorldsAsync(string programAddress = WorldProgram.ID, Commitment commitment = Commitment.Confirmed) - { - var list = new List{new Solana.Unity.Rpc.Models.MemCmp{Bytes = World.Accounts.World.ACCOUNT_DISCRIMINATOR_B58, Offset = 0}}; - var res = await RpcClient.GetProgramAccountsAsync(programAddress, commitment, memCmpList: list); - if (!res.WasSuccessful || !(res.Result?.Count > 0)) - return new Solana.Unity.Programs.Models.ProgramAccountsResultWrapper>(res); - List resultingAccounts = new List(res.Result.Count); - resultingAccounts.AddRange(res.Result.Select(result => World.Accounts.World.Deserialize(Convert.FromBase64String(result.Account.Data[0])))); - return new Solana.Unity.Programs.Models.ProgramAccountsResultWrapper>(res, resultingAccounts); - } - - public async Task> GetEntityAsync(string accountAddress, Commitment commitment = Commitment.Finalized) - { - var res = await RpcClient.GetAccountInfoAsync(accountAddress, commitment); - if (!res.WasSuccessful) - return new Solana.Unity.Programs.Models.AccountResultWrapper(res); - var resultingAccount = Entity.Deserialize(Convert.FromBase64String(res.Result.Value.Data[0])); - return new Solana.Unity.Programs.Models.AccountResultWrapper(res, resultingAccount); - } - - public async Task> GetRegistryAsync(string accountAddress, Commitment commitment = Commitment.Finalized) - { - var res = await RpcClient.GetAccountInfoAsync(accountAddress, commitment); - if (!res.WasSuccessful) - return new Solana.Unity.Programs.Models.AccountResultWrapper(res); - var resultingAccount = Registry.Deserialize(Convert.FromBase64String(res.Result.Value.Data[0])); - return new Solana.Unity.Programs.Models.AccountResultWrapper(res, resultingAccount); - } - - public async Task> GetWorldAsync(string accountAddress, Commitment commitment = Commitment.Finalized) - { - var res = await RpcClient.GetAccountInfoAsync(accountAddress, commitment); - if (!res.WasSuccessful) - return new Solana.Unity.Programs.Models.AccountResultWrapper(res); - var resultingAccount = World.Accounts.World.Deserialize(Convert.FromBase64String(res.Result.Value.Data[0])); - return new Solana.Unity.Programs.Models.AccountResultWrapper(res, resultingAccount); - } - - public async Task SubscribeEntityAsync(string accountAddress, Action, Entity> callback, Commitment commitment = Commitment.Finalized) - { - SubscriptionState res = await StreamingRpcClient.SubscribeAccountInfoAsync(accountAddress, (s, e) => - { - Entity parsingResult = null; - if (e.Value?.Data?.Count > 0) - parsingResult = Entity.Deserialize(Convert.FromBase64String(e.Value.Data[0])); - callback(s, e, parsingResult); - }, commitment); - return res; - } - - public async Task SubscribeRegistryAsync(string accountAddress, Action, Registry> callback, Commitment commitment = Commitment.Finalized) - { - SubscriptionState res = await StreamingRpcClient.SubscribeAccountInfoAsync(accountAddress, (s, e) => - { - Registry parsingResult = null; - if (e.Value?.Data?.Count > 0) - parsingResult = Registry.Deserialize(Convert.FromBase64String(e.Value.Data[0])); - callback(s, e, parsingResult); - }, commitment); - return res; - } - - public async Task SubscribeWorldAsync(string accountAddress, Action, World.Accounts.World> callback, Commitment commitment = Commitment.Finalized) - { - SubscriptionState res = await StreamingRpcClient.SubscribeAccountInfoAsync(accountAddress, (s, e) => - { - World.Accounts.World parsingResult = null; - if (e.Value?.Data?.Count > 0) - parsingResult = World.Accounts.World.Deserialize(Convert.FromBase64String(e.Value.Data[0])); - callback(s, e, parsingResult); - }, commitment); - return res; - } - - protected override Dictionary> BuildErrorsDictionary() - { - return new Dictionary>{{6000U, new ProgramError(WorldErrorKind.InvalidAuthority, "Invalid authority for instruction")}, {6001U, new ProgramError(WorldErrorKind.InvalidSystemOutput, "Invalid system output")}, {6002U, new ProgramError(WorldErrorKind.WorldAccountMismatch, "The provided world account does not match the expected PDA.")}, {6003U, new ProgramError(WorldErrorKind.TooManyAuthorities, "Exceed the maximum number of authorities.")}, {6004U, new ProgramError(WorldErrorKind.AuthorityNotFound, "The provided authority not found")}, {6005U, new ProgramError(WorldErrorKind.SystemNotApproved, "The system is not approved in this world instance")}, }; - } - } - - namespace Program - { - public class AddAuthorityAccounts - { - public PublicKey Authority { get; set; } - - public PublicKey NewAuthority { get; set; } - - public PublicKey World { get; set; } - - public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); - } - - public class AddEntityAccounts - { - public PublicKey Payer { get; set; } - - public PublicKey Entity { get; set; } - - public PublicKey World { get; set; } - - public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); - } - - public class ApplyAccounts - { - public PublicKey BoltSystem { get; set; } - - public PublicKey Authority { get; set; } - - public PublicKey CpiAuth { get; set; } = new PublicKey("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi"); - public PublicKey World { get; set; } - } - - public class ApplyWithSessionAccounts - { - public PublicKey BoltSystem { get; set; } - - public PublicKey Authority { get; set; } - - public PublicKey CpiAuth { get; set; } = new PublicKey("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi"); - public PublicKey World { get; set; } - - public PublicKey SessionToken { get; set; } - } - - public class ApproveSystemAccounts - { - public PublicKey Authority { get; set; } - - public PublicKey World { get; set; } - - public PublicKey System { get; set; } - - public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); - } - - public class DestroyComponentAccounts - { - public PublicKey Authority { get; set; } - - public PublicKey Receiver { get; set; } - - public PublicKey ComponentProgram { get; set; } - - public PublicKey ComponentProgramData { get; set; } - - public PublicKey Entity { get; set; } - - public PublicKey Component { get; set; } - - public PublicKey CpiAuth { get; set; } = new PublicKey("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi"); - public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); - } - - public class InitializeComponentAccounts - { - public PublicKey Payer { get; set; } - - public PublicKey Data { get; set; } - - public PublicKey Entity { get; set; } - - public PublicKey ComponentProgram { get; set; } - - public PublicKey Authority { get; set; } - - public PublicKey CpiAuth { get; set; } = new PublicKey("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi"); - public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); - } - - public class InitializeNewWorldAccounts - { - public PublicKey Payer { get; set; } - - public PublicKey World { get; set; } - - public PublicKey Registry { get; set; } - - public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); - } - - public class InitializeRegistryAccounts - { - public PublicKey Registry { get; set; } - - public PublicKey Payer { get; set; } - - public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); - } - - public class RemoveAuthorityAccounts - { - public PublicKey Authority { get; set; } - - public PublicKey AuthorityToDelete { get; set; } - - public PublicKey World { get; set; } - - public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); - } - - public class RemoveSystemAccounts - { - public PublicKey Authority { get; set; } - - public PublicKey World { get; set; } - - public PublicKey System { get; set; } - - public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); - } - - public partial class WorldProgram - { - public const string ID = "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n"; - public static Solana.Unity.Rpc.Models.TransactionInstruction AddAuthority(AddAuthorityAccounts accounts, ulong world_id, PublicKey programId = null) - { - programId ??= new(ID); - List keys = new() - {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.NewAuthority, false), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; - byte[] _data = new byte[1200]; - int offset = 0; - _data.WriteU64(13217455069452700133UL, offset); - offset += 8; - _data.WriteU64(world_id, offset); - offset += 8; - byte[] resultData = new byte[offset]; - Array.Copy(_data, resultData, offset); - return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; - } - - public static Solana.Unity.Rpc.Models.TransactionInstruction AddEntity(AddEntityAccounts accounts, byte[] extra_seed, PublicKey programId = null) - { - programId ??= new(ID); - List keys = new() - {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Payer, true), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Entity, false), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; - byte[] _data = new byte[1200]; - int offset = 0; - _data.WriteU64(4121062988444201379UL, offset); - offset += 8; - if (extra_seed != null) - { - _data.WriteU8(1, offset); - offset += 1; - _data.WriteS32(extra_seed.Length, offset); - offset += 4; - _data.WriteSpan(extra_seed, offset); - offset += extra_seed.Length; - } - else - { - _data.WriteU8(0, offset); - offset += 1; - } - - byte[] resultData = new byte[offset]; - Array.Copy(_data, resultData, offset); - return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; - } - - public static Solana.Unity.Rpc.Models.TransactionInstruction Apply(ApplyAccounts accounts, byte[] args, PublicKey programId = null) - { - programId ??= new(ID); - List keys = new() - {Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.BoltSystem, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.World, false)}; - byte[] _data = new byte[1200]; - int offset = 0; - _data.WriteU64(16258613031726085112UL, offset); - offset += 8; - _data.WriteS32(args.Length, offset); - offset += 4; - _data.WriteSpan(args, offset); - offset += args.Length; - byte[] resultData = new byte[offset]; - Array.Copy(_data, resultData, offset); - return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; - } - - public static Solana.Unity.Rpc.Models.TransactionInstruction ApplyWithSession(ApplyWithSessionAccounts accounts, byte[] args, PublicKey programId = null) - { - programId ??= new(ID); - List keys = new() - {Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.BoltSystem, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SessionToken, false)}; - byte[] _data = new byte[1200]; - int offset = 0; - _data.WriteU64(7459768094276011477UL, offset); - offset += 8; - _data.WriteS32(args.Length, offset); - offset += 4; - _data.WriteSpan(args, offset); - offset += args.Length; - byte[] resultData = new byte[offset]; - Array.Copy(_data, resultData, offset); - return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; - } - - public static Solana.Unity.Rpc.Models.TransactionInstruction ApproveSystem(ApproveSystemAccounts accounts, PublicKey programId = null) - { - programId ??= new(ID); - List keys = new() - {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.System, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; - byte[] _data = new byte[1200]; - int offset = 0; - _data.WriteU64(8777308090533520754UL, offset); - offset += 8; - byte[] resultData = new byte[offset]; - Array.Copy(_data, resultData, offset); - return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; - } - - public static Solana.Unity.Rpc.Models.TransactionInstruction DestroyComponent(DestroyComponentAccounts accounts, PublicKey programId = null) - { - programId ??= new(ID); - List keys = new() - {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Receiver, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.ComponentProgram, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.ComponentProgramData, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Entity, false), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Component, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; - byte[] _data = new byte[1200]; - int offset = 0; - _data.WriteU64(5321952129328727336UL, offset); - offset += 8; - byte[] resultData = new byte[offset]; - Array.Copy(_data, resultData, offset); - return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; - } - - public static Solana.Unity.Rpc.Models.TransactionInstruction InitializeComponent(InitializeComponentAccounts accounts, PublicKey programId = null) - { - programId ??= new(ID); - List keys = new() - {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Payer, true), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Data, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Entity, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.ComponentProgram, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Authority, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; - byte[] _data = new byte[1200]; - int offset = 0; - _data.WriteU64(2179155133888827172UL, offset); - offset += 8; - byte[] resultData = new byte[offset]; - Array.Copy(_data, resultData, offset); - return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; - } - - public static Solana.Unity.Rpc.Models.TransactionInstruction InitializeNewWorld(InitializeNewWorldAccounts accounts, PublicKey programId = null) - { - programId ??= new(ID); - List keys = new() - {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Payer, true), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Registry, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; - byte[] _data = new byte[1200]; - int offset = 0; - _data.WriteU64(7118163274173538327UL, offset); - offset += 8; - byte[] resultData = new byte[offset]; - Array.Copy(_data, resultData, offset); - return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; - } - - public static Solana.Unity.Rpc.Models.TransactionInstruction InitializeRegistry(InitializeRegistryAccounts accounts, PublicKey programId = null) - { - programId ??= new(ID); - List keys = new() - {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Registry, false), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Payer, true), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; - byte[] _data = new byte[1200]; - int offset = 0; - _data.WriteU64(4321548737212364221UL, offset); - offset += 8; - byte[] resultData = new byte[offset]; - Array.Copy(_data, resultData, offset); - return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; - } - - public static Solana.Unity.Rpc.Models.TransactionInstruction RemoveAuthority(RemoveAuthorityAccounts accounts, ulong world_id, PublicKey programId = null) - { - programId ??= new(ID); - List keys = new() - {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.AuthorityToDelete, false), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; - byte[] _data = new byte[1200]; - int offset = 0; - _data.WriteU64(15585545156648003826UL, offset); - offset += 8; - _data.WriteU64(world_id, offset); - offset += 8; - byte[] resultData = new byte[offset]; - Array.Copy(_data, resultData, offset); - return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; - } - - public static Solana.Unity.Rpc.Models.TransactionInstruction RemoveSystem(RemoveSystemAccounts accounts, PublicKey programId = null) - { - programId ??= new(ID); - List keys = new() - {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.System, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; - byte[] _data = new byte[1200]; - int offset = 0; - _data.WriteU64(8688994685429436634UL, offset); - offset += 8; - byte[] resultData = new byte[offset]; - Array.Copy(_data, resultData, offset); - return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; - } - } - } +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; +using Solana.Unity; +using Solana.Unity.Programs.Abstract; +using Solana.Unity.Programs.Utilities; +using Solana.Unity.Rpc; +using Solana.Unity.Rpc.Builders; +using Solana.Unity.Rpc.Core.Http; +using Solana.Unity.Rpc.Core.Sockets; +using Solana.Unity.Rpc.Types; +using Solana.Unity.Wallet; +using World; +using World.Program; +using World.Errors; +using World.Accounts; +using World.Types; + +namespace World +{ + namespace Accounts + { + public partial class Entity + { + public static ulong ACCOUNT_DISCRIMINATOR => 1751670451238706478UL; + public static ReadOnlySpan ACCOUNT_DISCRIMINATOR_BYTES => new byte[]{46, 157, 161, 161, 254, 46, 79, 24}; + public static string ACCOUNT_DISCRIMINATOR_B58 => "8oEQa6zH67R"; + public ulong Id { get; set; } + + public static Entity Deserialize(ReadOnlySpan _data) + { + int offset = 0; + ulong accountHashValue = _data.GetU64(offset); + offset += 8; + if (accountHashValue != ACCOUNT_DISCRIMINATOR) + { + return null; + } + + Entity result = new Entity(); + result.Id = _data.GetU64(offset); + offset += 8; + return result; + } + } + + public partial class Registry + { + public static ulong ACCOUNT_DISCRIMINATOR => 15779688099924061743UL; + public static ReadOnlySpan ACCOUNT_DISCRIMINATOR_BYTES => new byte[]{47, 174, 110, 246, 184, 182, 252, 218}; + public static string ACCOUNT_DISCRIMINATOR_B58 => "8ya1XGY4XBP"; + public ulong Worlds { get; set; } + + public static Registry Deserialize(ReadOnlySpan _data) + { + int offset = 0; + ulong accountHashValue = _data.GetU64(offset); + offset += 8; + if (accountHashValue != ACCOUNT_DISCRIMINATOR) + { + return null; + } + + Registry result = new Registry(); + result.Worlds = _data.GetU64(offset); + offset += 8; + return result; + } + } + + public partial class SessionToken + { + public static ulong ACCOUNT_DISCRIMINATOR => 1081168673100727529UL; + public static ReadOnlySpan ACCOUNT_DISCRIMINATOR_BYTES => new byte[]{233, 4, 115, 14, 46, 21, 1, 15}; + public static string ACCOUNT_DISCRIMINATOR_B58 => "fyZWTdUu1pS"; + public PublicKey Authority { get; set; } + + public PublicKey TargetProgram { get; set; } + + public PublicKey SessionSigner { get; set; } + + public long ValidUntil { get; set; } + + public static SessionToken Deserialize(ReadOnlySpan _data) + { + int offset = 0; + ulong accountHashValue = _data.GetU64(offset); + offset += 8; + if (accountHashValue != ACCOUNT_DISCRIMINATOR) + { + return null; + } + + SessionToken result = new SessionToken(); + result.Authority = _data.GetPubKey(offset); + offset += 32; + result.TargetProgram = _data.GetPubKey(offset); + offset += 32; + result.SessionSigner = _data.GetPubKey(offset); + offset += 32; + result.ValidUntil = _data.GetS64(offset); + offset += 8; + return result; + } + } + + public partial class World + { + public static ulong ACCOUNT_DISCRIMINATOR => 8978805993381703057UL; + public static ReadOnlySpan ACCOUNT_DISCRIMINATOR_BYTES => new byte[]{145, 45, 170, 174, 122, 32, 155, 124}; + public static string ACCOUNT_DISCRIMINATOR_B58 => "RHQudtaQtu1"; + public ulong Id { get; set; } + + public ulong Entities { get; set; } + + public PublicKey[] Authorities { get; set; } + + public bool Permissionless { get; set; } + + public byte[] Systems { get; set; } + + public static World Deserialize(ReadOnlySpan _data) + { + int offset = 0; + ulong accountHashValue = _data.GetU64(offset); + offset += 8; + if (accountHashValue != ACCOUNT_DISCRIMINATOR) + { + return null; + } + + World result = new World(); + result.Id = _data.GetU64(offset); + offset += 8; + result.Entities = _data.GetU64(offset); + offset += 8; + int resultAuthoritiesLength = (int)_data.GetU32(offset); + offset += 4; + result.Authorities = new PublicKey[resultAuthoritiesLength]; + for (uint resultAuthoritiesIdx = 0; resultAuthoritiesIdx < resultAuthoritiesLength; resultAuthoritiesIdx++) + { + result.Authorities[resultAuthoritiesIdx] = _data.GetPubKey(offset); + offset += 32; + } + + result.Permissionless = _data.GetBool(offset); + offset += 1; + int resultSystemsLength = (int)_data.GetU32(offset); + offset += 4; + result.Systems = _data.GetBytes(offset, resultSystemsLength); + offset += resultSystemsLength; + return result; + } + } + } + + namespace Errors + { + public enum WorldErrorKind : uint + { + InvalidAuthority = 6000U, + InvalidSystemOutput = 6001U, + WorldAccountMismatch = 6002U, + TooManyAuthorities = 6003U, + AuthorityNotFound = 6004U, + SystemNotApproved = 6005U, + InvalidComponentOwner = 6006U + } + } + + namespace Types + { + } + + public partial class WorldClient : TransactionalBaseClient + { + public WorldClient(IRpcClient rpcClient, IStreamingRpcClient streamingRpcClient, PublicKey programId = null) : base(rpcClient, streamingRpcClient, programId ?? new PublicKey(WorldProgram.ID)) + { + } + + public async Task>> GetEntitysAsync(string programAddress = WorldProgram.ID, Commitment commitment = Commitment.Confirmed) + { + var list = new List{new Solana.Unity.Rpc.Models.MemCmp{Bytes = Entity.ACCOUNT_DISCRIMINATOR_B58, Offset = 0}}; + var res = await RpcClient.GetProgramAccountsAsync(programAddress, commitment, memCmpList: list); + if (!res.WasSuccessful || !(res.Result?.Count > 0)) + return new Solana.Unity.Programs.Models.ProgramAccountsResultWrapper>(res); + List resultingAccounts = new List(res.Result.Count); + resultingAccounts.AddRange(res.Result.Select(result => Entity.Deserialize(Convert.FromBase64String(result.Account.Data[0])))); + return new Solana.Unity.Programs.Models.ProgramAccountsResultWrapper>(res, resultingAccounts); + } + + public async Task>> GetRegistrysAsync(string programAddress = WorldProgram.ID, Commitment commitment = Commitment.Confirmed) + { + var list = new List{new Solana.Unity.Rpc.Models.MemCmp{Bytes = Registry.ACCOUNT_DISCRIMINATOR_B58, Offset = 0}}; + var res = await RpcClient.GetProgramAccountsAsync(programAddress, commitment, memCmpList: list); + if (!res.WasSuccessful || !(res.Result?.Count > 0)) + return new Solana.Unity.Programs.Models.ProgramAccountsResultWrapper>(res); + List resultingAccounts = new List(res.Result.Count); + resultingAccounts.AddRange(res.Result.Select(result => Registry.Deserialize(Convert.FromBase64String(result.Account.Data[0])))); + return new Solana.Unity.Programs.Models.ProgramAccountsResultWrapper>(res, resultingAccounts); + } + + public async Task>> GetSessionTokensAsync(string programAddress = WorldProgram.ID, Commitment commitment = Commitment.Confirmed) + { + var list = new List{new Solana.Unity.Rpc.Models.MemCmp{Bytes = SessionToken.ACCOUNT_DISCRIMINATOR_B58, Offset = 0}}; + var res = await RpcClient.GetProgramAccountsAsync(programAddress, commitment, memCmpList: list); + if (!res.WasSuccessful || !(res.Result?.Count > 0)) + return new Solana.Unity.Programs.Models.ProgramAccountsResultWrapper>(res); + List resultingAccounts = new List(res.Result.Count); + resultingAccounts.AddRange(res.Result.Select(result => SessionToken.Deserialize(Convert.FromBase64String(result.Account.Data[0])))); + return new Solana.Unity.Programs.Models.ProgramAccountsResultWrapper>(res, resultingAccounts); + } + + public async Task>> GetWorldsAsync(string programAddress = WorldProgram.ID, Commitment commitment = Commitment.Confirmed) + { + var list = new List{new Solana.Unity.Rpc.Models.MemCmp{Bytes = World.Accounts.World.ACCOUNT_DISCRIMINATOR_B58, Offset = 0}}; + var res = await RpcClient.GetProgramAccountsAsync(programAddress, commitment, memCmpList: list); + if (!res.WasSuccessful || !(res.Result?.Count > 0)) + return new Solana.Unity.Programs.Models.ProgramAccountsResultWrapper>(res); + List resultingAccounts = new List(res.Result.Count); + resultingAccounts.AddRange(res.Result.Select(result => World.Accounts.World.Deserialize(Convert.FromBase64String(result.Account.Data[0])))); + return new Solana.Unity.Programs.Models.ProgramAccountsResultWrapper>(res, resultingAccounts); + } + + public async Task> GetEntityAsync(string accountAddress, Commitment commitment = Commitment.Finalized) + { + var res = await RpcClient.GetAccountInfoAsync(accountAddress, commitment); + if (!res.WasSuccessful) + return new Solana.Unity.Programs.Models.AccountResultWrapper(res); + var resultingAccount = Entity.Deserialize(Convert.FromBase64String(res.Result.Value.Data[0])); + return new Solana.Unity.Programs.Models.AccountResultWrapper(res, resultingAccount); + } + + public async Task> GetRegistryAsync(string accountAddress, Commitment commitment = Commitment.Finalized) + { + var res = await RpcClient.GetAccountInfoAsync(accountAddress, commitment); + if (!res.WasSuccessful) + return new Solana.Unity.Programs.Models.AccountResultWrapper(res); + var resultingAccount = Registry.Deserialize(Convert.FromBase64String(res.Result.Value.Data[0])); + return new Solana.Unity.Programs.Models.AccountResultWrapper(res, resultingAccount); + } + + public async Task> GetSessionTokenAsync(string accountAddress, Commitment commitment = Commitment.Finalized) + { + var res = await RpcClient.GetAccountInfoAsync(accountAddress, commitment); + if (!res.WasSuccessful) + return new Solana.Unity.Programs.Models.AccountResultWrapper(res); + var resultingAccount = SessionToken.Deserialize(Convert.FromBase64String(res.Result.Value.Data[0])); + return new Solana.Unity.Programs.Models.AccountResultWrapper(res, resultingAccount); + } + + public async Task> GetWorldAsync(string accountAddress, Commitment commitment = Commitment.Finalized) + { + var res = await RpcClient.GetAccountInfoAsync(accountAddress, commitment); + if (!res.WasSuccessful) + return new Solana.Unity.Programs.Models.AccountResultWrapper(res); + var resultingAccount = World.Accounts.World.Deserialize(Convert.FromBase64String(res.Result.Value.Data[0])); + return new Solana.Unity.Programs.Models.AccountResultWrapper(res, resultingAccount); + } + + public async Task SubscribeEntityAsync(string accountAddress, Action, Entity> callback, Commitment commitment = Commitment.Finalized) + { + SubscriptionState res = await StreamingRpcClient.SubscribeAccountInfoAsync(accountAddress, (s, e) => + { + Entity parsingResult = null; + if (e.Value?.Data?.Count > 0) + parsingResult = Entity.Deserialize(Convert.FromBase64String(e.Value.Data[0])); + callback(s, e, parsingResult); + }, commitment); + return res; + } + + public async Task SubscribeRegistryAsync(string accountAddress, Action, Registry> callback, Commitment commitment = Commitment.Finalized) + { + SubscriptionState res = await StreamingRpcClient.SubscribeAccountInfoAsync(accountAddress, (s, e) => + { + Registry parsingResult = null; + if (e.Value?.Data?.Count > 0) + parsingResult = Registry.Deserialize(Convert.FromBase64String(e.Value.Data[0])); + callback(s, e, parsingResult); + }, commitment); + return res; + } + + public async Task SubscribeSessionTokenAsync(string accountAddress, Action, SessionToken> callback, Commitment commitment = Commitment.Finalized) + { + SubscriptionState res = await StreamingRpcClient.SubscribeAccountInfoAsync(accountAddress, (s, e) => + { + SessionToken parsingResult = null; + if (e.Value?.Data?.Count > 0) + parsingResult = SessionToken.Deserialize(Convert.FromBase64String(e.Value.Data[0])); + callback(s, e, parsingResult); + }, commitment); + return res; + } + + public async Task SubscribeWorldAsync(string accountAddress, Action, World.Accounts.World> callback, Commitment commitment = Commitment.Finalized) + { + SubscriptionState res = await StreamingRpcClient.SubscribeAccountInfoAsync(accountAddress, (s, e) => + { + World.Accounts.World parsingResult = null; + if (e.Value?.Data?.Count > 0) + parsingResult = World.Accounts.World.Deserialize(Convert.FromBase64String(e.Value.Data[0])); + callback(s, e, parsingResult); + }, commitment); + return res; + } + + protected override Dictionary> BuildErrorsDictionary() + { + return new Dictionary>{{6000U, new ProgramError(WorldErrorKind.InvalidAuthority, "Invalid authority for instruction")}, {6001U, new ProgramError(WorldErrorKind.InvalidSystemOutput, "Invalid system output")}, {6002U, new ProgramError(WorldErrorKind.WorldAccountMismatch, "The provided world account does not match the expected PDA.")}, {6003U, new ProgramError(WorldErrorKind.TooManyAuthorities, "Exceed the maximum number of authorities.")}, {6004U, new ProgramError(WorldErrorKind.AuthorityNotFound, "The provided authority not found")}, {6005U, new ProgramError(WorldErrorKind.SystemNotApproved, "The system is not approved in this world instance")}, {6006U, new ProgramError(WorldErrorKind.InvalidComponentOwner, "The component owner does not match the program")}, }; + } + } + + namespace Program + { + public class AddAuthorityAccounts + { + public PublicKey Authority { get; set; } + + public PublicKey NewAuthority { get; set; } + + public PublicKey World { get; set; } + + public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); + } + + public class AddEntityAccounts + { + public PublicKey Payer { get; set; } + + public PublicKey Entity { get; set; } + + public PublicKey World { get; set; } + + public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); + } + + public class ApplyAccounts + { + public PublicKey Buffer { get; set; } + + public PublicKey BoltSystem { get; set; } + + public PublicKey Authority { get; set; } + + public PublicKey CpiAuth { get; set; } + + public PublicKey World { get; set; } + + public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); + } + + public class ApplyWithSessionAccounts + { + public PublicKey Buffer { get; set; } + + public PublicKey BoltSystem { get; set; } + + public PublicKey Authority { get; set; } + + public PublicKey CpiAuth { get; set; } + + public PublicKey World { get; set; } + + public PublicKey SessionToken { get; set; } + + public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); + } + + public class ApproveSystemAccounts + { + public PublicKey Authority { get; set; } + + public PublicKey World { get; set; } + + public PublicKey System { get; set; } + + public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); + } + + public class DestroyComponentAccounts + { + public PublicKey Authority { get; set; } + + public PublicKey Receiver { get; set; } + + public PublicKey ComponentProgram { get; set; } + + public PublicKey ComponentProgramData { get; set; } + + public PublicKey Entity { get; set; } + + public PublicKey Component { get; set; } + + public PublicKey CpiAuth { get; set; } + + public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); + } + + public class InitializeComponentAccounts + { + public PublicKey Payer { get; set; } + + public PublicKey Data { get; set; } + + public PublicKey Entity { get; set; } + + public PublicKey ComponentProgram { get; set; } + + public PublicKey Authority { get; set; } + + public PublicKey CpiAuth { get; set; } + + public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); + } + + public class InitializeNewWorldAccounts + { + public PublicKey Payer { get; set; } + + public PublicKey World { get; set; } + + public PublicKey Registry { get; set; } + + public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); + } + + public class InitializeRegistryAccounts + { + public PublicKey Registry { get; set; } + + public PublicKey Payer { get; set; } + + public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); + } + + public class RemoveAuthorityAccounts + { + public PublicKey Authority { get; set; } + + public PublicKey AuthorityToDelete { get; set; } + + public PublicKey World { get; set; } + + public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); + } + + public class RemoveSystemAccounts + { + public PublicKey Authority { get; set; } + + public PublicKey World { get; set; } + + public PublicKey System { get; set; } + + public PublicKey SystemProgram { get; set; } = new PublicKey("11111111111111111111111111111111"); + } + + public partial class WorldProgram + { + public const string ID = "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n"; + public static Solana.Unity.Rpc.Models.TransactionInstruction AddAuthority(AddAuthorityAccounts accounts, ulong world_id, PublicKey programId = null) + { + programId ??= new(ID); + List keys = new() + {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.NewAuthority, false), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; + byte[] _data = new byte[1200]; + int offset = 0; + _data.WriteU64(13217455069452700133UL, offset); + offset += 8; + _data.WriteU64(world_id, offset); + offset += 8; + byte[] resultData = new byte[offset]; + Array.Copy(_data, resultData, offset); + return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; + } + + public static Solana.Unity.Rpc.Models.TransactionInstruction AddEntity(AddEntityAccounts accounts, byte[] extra_seed, PublicKey programId = null) + { + programId ??= new(ID); + List keys = new() + {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Payer, true), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Entity, false), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; + byte[] _data = new byte[1200]; + int offset = 0; + _data.WriteU64(4121062988444201379UL, offset); + offset += 8; + if (extra_seed != null) + { + _data.WriteU8(1, offset); + offset += 1; + _data.WriteS32(extra_seed.Length, offset); + offset += 4; + _data.WriteSpan(extra_seed, offset); + offset += extra_seed.Length; + } + else + { + _data.WriteU8(0, offset); + offset += 1; + } + + byte[] resultData = new byte[offset]; + Array.Copy(_data, resultData, offset); + return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; + } + + public static Solana.Unity.Rpc.Models.TransactionInstruction Apply(ApplyAccounts accounts, byte[] args, PublicKey programId = null) + { + programId ??= new(ID); + List keys = new() + {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Buffer, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.BoltSystem, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; + byte[] _data = new byte[1200]; + int offset = 0; + _data.WriteU64(16258613031726085112UL, offset); + offset += 8; + _data.WriteS32(args.Length, offset); + offset += 4; + _data.WriteSpan(args, offset); + offset += args.Length; + byte[] resultData = new byte[offset]; + Array.Copy(_data, resultData, offset); + return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; + } + + public static Solana.Unity.Rpc.Models.TransactionInstruction ApplyWithSession(ApplyWithSessionAccounts accounts, byte[] args, PublicKey programId = null) + { + programId ??= new(ID); + List keys = new() + {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Buffer, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.BoltSystem, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SessionToken, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; + byte[] _data = new byte[1200]; + int offset = 0; + _data.WriteU64(7459768094276011477UL, offset); + offset += 8; + _data.WriteS32(args.Length, offset); + offset += 4; + _data.WriteSpan(args, offset); + offset += args.Length; + byte[] resultData = new byte[offset]; + Array.Copy(_data, resultData, offset); + return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; + } + + public static Solana.Unity.Rpc.Models.TransactionInstruction ApproveSystem(ApproveSystemAccounts accounts, PublicKey programId = null) + { + programId ??= new(ID); + List keys = new() + {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.System, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; + byte[] _data = new byte[1200]; + int offset = 0; + _data.WriteU64(8777308090533520754UL, offset); + offset += 8; + byte[] resultData = new byte[offset]; + Array.Copy(_data, resultData, offset); + return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; + } + + public static Solana.Unity.Rpc.Models.TransactionInstruction DestroyComponent(DestroyComponentAccounts accounts, PublicKey programId = null) + { + programId ??= new(ID); + List keys = new() + {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Receiver, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.ComponentProgram, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.ComponentProgramData, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Entity, false), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Component, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; + byte[] _data = new byte[1200]; + int offset = 0; + _data.WriteU64(5321952129328727336UL, offset); + offset += 8; + byte[] resultData = new byte[offset]; + Array.Copy(_data, resultData, offset); + return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; + } + + public static Solana.Unity.Rpc.Models.TransactionInstruction InitializeComponent(InitializeComponentAccounts accounts, PublicKey programId = null) + { + programId ??= new(ID); + List keys = new() + {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Payer, true), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Data, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Entity, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.ComponentProgram, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Authority, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; + byte[] _data = new byte[1200]; + int offset = 0; + _data.WriteU64(2179155133888827172UL, offset); + offset += 8; + byte[] resultData = new byte[offset]; + Array.Copy(_data, resultData, offset); + return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; + } + + public static Solana.Unity.Rpc.Models.TransactionInstruction InitializeNewWorld(InitializeNewWorldAccounts accounts, PublicKey programId = null) + { + programId ??= new(ID); + List keys = new() + {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Payer, true), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Registry, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; + byte[] _data = new byte[1200]; + int offset = 0; + _data.WriteU64(7118163274173538327UL, offset); + offset += 8; + byte[] resultData = new byte[offset]; + Array.Copy(_data, resultData, offset); + return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; + } + + public static Solana.Unity.Rpc.Models.TransactionInstruction InitializeRegistry(InitializeRegistryAccounts accounts, PublicKey programId = null) + { + programId ??= new(ID); + List keys = new() + {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Registry, false), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Payer, true), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; + byte[] _data = new byte[1200]; + int offset = 0; + _data.WriteU64(4321548737212364221UL, offset); + offset += 8; + byte[] resultData = new byte[offset]; + Array.Copy(_data, resultData, offset); + return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; + } + + public static Solana.Unity.Rpc.Models.TransactionInstruction RemoveAuthority(RemoveAuthorityAccounts accounts, ulong world_id, PublicKey programId = null) + { + programId ??= new(ID); + List keys = new() + {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.AuthorityToDelete, false), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; + byte[] _data = new byte[1200]; + int offset = 0; + _data.WriteU64(15585545156648003826UL, offset); + offset += 8; + _data.WriteU64(world_id, offset); + offset += 8; + byte[] resultData = new byte[offset]; + Array.Copy(_data, resultData, offset); + return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; + } + + public static Solana.Unity.Rpc.Models.TransactionInstruction RemoveSystem(RemoveSystemAccounts accounts, PublicKey programId = null) + { + programId ??= new(ID); + List keys = new() + {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.System, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; + byte[] _data = new byte[1200]; + int offset = 0; + _data.WriteU64(8688994685429436634UL, offset); + offset += 8; + byte[] resultData = new byte[offset]; + Array.Copy(_data, resultData, offset); + return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData}; + } + } + } } \ No newline at end of file diff --git a/clients/csharp/Solana.Unity.Bolt/WorldProgram/World.cs b/clients/csharp/Solana.Unity.Bolt/WorldProgram/World.cs index 8334073..17b55f3 100644 --- a/clients/csharp/Solana.Unity.Bolt/WorldProgram/World.cs +++ b/clients/csharp/Solana.Unity.Bolt/WorldProgram/World.cs @@ -15,6 +15,8 @@ namespace Program { public partial class WorldProgram { + public static readonly PublicKey CPI_AUTH_ADDRESS = new("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi"); + public static Solana.Unity.Rpc.Models.TransactionInstruction AddEntity(AddEntityAccounts accounts, PublicKey programId = null) { programId ??= new(ID); @@ -27,6 +29,14 @@ public static Solana.Unity.Rpc.Models.TransactionInstruction AddEntity(AddEntity return AddEntity(accounts, System.Text.Encoding.UTF8.GetBytes(extraSeed), programId); } + public static PublicKey FindBufferPda() { + PublicKey.TryFindProgramAddress(new[] + { + Encoding.UTF8.GetBytes("buffer"), + }, new PublicKey(ID), out var pda, out _); + return pda; + } + public static PublicKey FindSessionTokenPda(PublicKey sessionSigner, PublicKey authority) { PublicKey.TryFindProgramAddress(new[] @@ -223,6 +233,8 @@ public static Solana.Unity.Rpc.Models.TransactionInstruction ApplySystem( Solana.Unity.Rpc.Models.TransactionInstruction instruction; if (sessionToken != null) { var apply = new ApplyWithSessionAccounts() { + CpiAuth = CPI_AUTH_ADDRESS, + Buffer = FindBufferPda(), BoltSystem = system, Authority = authority, World = world, @@ -231,6 +243,8 @@ public static Solana.Unity.Rpc.Models.TransactionInstruction ApplySystem( instruction = ApplyWithSession(apply, args, programId); } else { var apply = new ApplyAccounts() { + CpiAuth = CPI_AUTH_ADDRESS, + Buffer = FindBufferPda(), BoltSystem = system, Authority = authority, World = world, diff --git a/crates/bolt-lang/attribute/bolt-program/src/lib.rs b/crates/bolt-lang/attribute/bolt-program/src/lib.rs index 977ba04..54a02e7 100644 --- a/crates/bolt-lang/attribute/bolt-program/src/lib.rs +++ b/crates/bolt-lang/attribute/bolt-program/src/lib.rs @@ -44,8 +44,6 @@ pub fn bolt_program(args: TokenStream, input: TokenStream) -> TokenStream { fn modify_component_module(mut module: ItemMod, component_type: &Type) -> ItemMod { let (initialize_fn, initialize_struct) = generate_initialize(component_type); let (destroy_fn, destroy_struct) = generate_destroy(component_type); - let (update_fn, update_with_session_fn, update_struct, update_with_session_struct) = - generate_update(component_type); let set_owner = bolt_utils::instructions::generate_set_owner(); let set_data = bolt_utils::instructions::generate_set_data(); module.content = module.content.map(|(brace, mut items)| { @@ -53,10 +51,6 @@ fn modify_component_module(mut module: ItemMod, component_type: &Type) -> ItemMo vec![ initialize_fn, initialize_struct, - update_fn, - update_struct, - update_with_session_fn, - update_with_session_struct, destroy_fn, destroy_struct, set_owner.function, @@ -210,74 +204,6 @@ fn generate_initialize(component_type: &Type) -> (TokenStream2, TokenStream2) { ) } -// TODO: Remove this. We are directly writing to the account. -/// Generates the instructions and related structs to inject in the component. -fn generate_update( - component_type: &Type, -) -> (TokenStream2, TokenStream2, TokenStream2, TokenStream2) { - ( - quote! { - #[automatically_derived] - pub fn update(ctx: Context, data: Vec) -> Result<()> { - 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); - - bolt_lang::cpi::checker(&ctx.accounts.cpi_auth.to_account_info())?; - - ctx.accounts.bolt_component.set_inner(<#component_type>::try_from_slice(&data)?); - Ok(()) - } - }, - quote! { - #[automatically_derived] - pub fn update_with_session(ctx: Context, data: Vec) -> Result<()> { - if ctx.accounts.bolt_component.bolt_metadata.authority == World::id() { - require!(Clock::get()?.unix_timestamp < ctx.accounts.session_token.valid_until, bolt_lang::session_keys::SessionError::InvalidToken); - } else { - let validity_ctx = bolt_lang::session_keys::ValidityChecker { - session_token: ctx.accounts.session_token.clone(), - session_signer: ctx.accounts.authority.clone(), - authority: ctx.accounts.bolt_component.bolt_metadata.authority.clone(), - target_program: World::id(), - }; - require!(ctx.accounts.session_token.validate(validity_ctx)?, bolt_lang::session_keys::SessionError::InvalidToken); - require_eq!(ctx.accounts.bolt_component.bolt_metadata.authority, ctx.accounts.session_token.authority, bolt_lang::session_keys::SessionError::InvalidToken); - } - - bolt_lang::cpi::checker(&ctx.accounts.cpi_auth.to_account_info())?; - - ctx.accounts.bolt_component.set_inner(<#component_type>::try_from_slice(&data)?); - Ok(()) - } - }, - quote! { - #[automatically_derived] - #[derive(Accounts)] - pub struct Update<'info> { - #[account()] - pub cpi_auth: Signer<'info>, - #[account(mut)] - pub bolt_component: Account<'info, #component_type>, - #[account()] - pub authority: Signer<'info>, - } - }, - quote! { - #[automatically_derived] - #[derive(Accounts)] - pub struct UpdateWithSession<'info> { - #[account()] - pub cpi_auth: Signer<'info>, - #[account(mut)] - pub bolt_component: Account<'info, #component_type>, - #[account()] - pub authority: Signer<'info>, - #[account(constraint = session_token.to_account_info().owner == &bolt_lang::session_keys::ID)] - pub session_token: Account<'info, bolt_lang::session_keys::SessionToken>, - } - }, - ) -} - /// Checks if the field is expecting a program. fn is_expecting_program(field: &Field) -> bool { field.ty.to_token_stream().to_string().contains("Program") From d61ca022c7dd217d91712c75f71dd202443159fd Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Wed, 13 Aug 2025 02:20:08 -0300 Subject: [PATCH 08/21] :fire: Removing unnecessary test --- .../test/low-level/permissioning/world.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/clients/typescript/test/low-level/permissioning/world.ts b/clients/typescript/test/low-level/permissioning/world.ts index ab75998..3517365 100644 --- a/clients/typescript/test/low-level/permissioning/world.ts +++ b/clients/typescript/test/low-level/permissioning/world.ts @@ -228,22 +228,5 @@ export function world(framework) { } 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, - }) - .rpc(); - } catch (error) { - expect(error.message).to.contain("Error Code: InvalidCaller"); - invalid = true; - } - expect(invalid).to.equal(true); - }); }); } From 36373a11183205611a3fcc99cadf61ffdb67c4ea Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Wed, 13 Aug 2025 16:17:42 -0300 Subject: [PATCH 09/21] :bug: Fixing authority account mutability --- .../csharp/Solana.Unity.Bolt.Test/ECSTest.cs | 4 +- .../WorldProgram/Bolt/DestroyComponent.cs | 2 +- .../WorldProgram/Bolt/InitializeComponent.cs | 2 +- .../WorldProgram/Generated.cs | 4 +- .../Solana.Unity.Bolt/WorldProgram/World.cs | 18 +-- .../typescript/src/generated/idl/world.json | 39 +++++++ .../src/generated/instructions/apply.ts | 4 +- .../instructions/initializeComponent.ts | 4 +- .../typescript/src/generated/types/world.ts | 30 +++++ clients/typescript/src/index.ts | 11 +- clients/typescript/src/world/transactions.ts | 15 +-- clients/typescript/test/low-level/ecs.ts | 37 +++---- .../test/low-level/permissioning/component.ts | 12 +- .../test/low-level/permissioning/world.ts | 10 +- clients/typescript/test/low-level/session.ts | 20 ++-- crates/programs/world/src/lib.rs | 104 ++++++++---------- 16 files changed, 191 insertions(+), 125 deletions(-) diff --git a/clients/csharp/Solana.Unity.Bolt.Test/ECSTest.cs b/clients/csharp/Solana.Unity.Bolt.Test/ECSTest.cs index a599efc..dd1bb8f 100644 --- a/clients/csharp/Solana.Unity.Bolt.Test/ECSTest.cs +++ b/clients/csharp/Solana.Unity.Bolt.Test/ECSTest.cs @@ -108,11 +108,11 @@ public static async Task CheckPositionOnEntity1IsDefault(Framework framework) { public static async Task ApplySimpleMovementSystemUpOnEntity1(Framework framework) { var apply = new ApplyAccounts() { - CpiAuth = WorldProgram.CPI_AUTH_ADDRESS, + CpiAuth = WorldProgram.FindCpiAuthPda(), Authority = framework.Wallet.Account.PublicKey, BoltSystem = framework.SystemSimpleMovement, World = framework.WorldPda, - Buffer = WorldProgram.FindBufferPda(), + Buffer = WorldProgram.FindBufferPda(framework.Wallet.Account.PublicKey), }; var instruction = WorldProgram.Apply(apply, Bolt.World.SerializeArgs(new { direction = "Up" })); instruction.Keys.Add(AccountMeta.ReadOnly(framework.ExampleComponentPosition, false)); diff --git a/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/DestroyComponent.cs b/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/DestroyComponent.cs index c4715a8..c793cee 100644 --- a/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/DestroyComponent.cs +++ b/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/DestroyComponent.cs @@ -30,7 +30,7 @@ public static async Task DestroyComponent(PublicKey public static async Task DestroyComponent(PublicKey authority, PublicKey receiver, PublicKey entity, PublicKey componentProgram, PublicKey componentPda) { var componentProgramData = WorldProgram.FindComponentProgramDataPda(componentProgram); var destroyComponent = new DestroyComponentAccounts() { - CpiAuth = WorldProgram.CPI_AUTH_ADDRESS, + CpiAuth = WorldProgram.FindCpiAuthPda(), Authority = authority, Receiver = receiver, Entity = entity, diff --git a/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/InitializeComponent.cs b/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/InitializeComponent.cs index ff9c8c6..42b3154 100644 --- a/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/InitializeComponent.cs +++ b/clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/InitializeComponent.cs @@ -30,7 +30,7 @@ public static async Task InitializeComponent(Pub public static async Task InitializeComponent(PublicKey payer, PublicKey entity, PublicKey componentId, PublicKey componentPda, PublicKey authority = null) { var initializeComponent = new InitializeComponentAccounts() { - CpiAuth = WorldProgram.CPI_AUTH_ADDRESS, + CpiAuth = WorldProgram.FindCpiAuthPda(), Payer = payer, Entity = entity, Data = componentPda, diff --git a/clients/csharp/Solana.Unity.Bolt/WorldProgram/Generated.cs b/clients/csharp/Solana.Unity.Bolt/WorldProgram/Generated.cs index 41e8b42..5c297ab 100644 --- a/clients/csharp/Solana.Unity.Bolt/WorldProgram/Generated.cs +++ b/clients/csharp/Solana.Unity.Bolt/WorldProgram/Generated.cs @@ -513,7 +513,7 @@ public static Solana.Unity.Rpc.Models.TransactionInstruction Apply(ApplyAccounts { programId ??= new(ID); List keys = new() - {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Buffer, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.BoltSystem, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; + {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Buffer, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.BoltSystem, false), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; byte[] _data = new byte[1200]; int offset = 0; _data.WriteU64(16258613031726085112UL, offset); @@ -531,7 +531,7 @@ public static Solana.Unity.Rpc.Models.TransactionInstruction ApplyWithSession(Ap { programId ??= new(ID); List keys = new() - {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Buffer, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.BoltSystem, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SessionToken, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; + {Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Buffer, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.BoltSystem, false), Solana.Unity.Rpc.Models.AccountMeta.Writable(accounts.Authority, true), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.World, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SessionToken, false), Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SystemProgram, false)}; byte[] _data = new byte[1200]; int offset = 0; _data.WriteU64(7459768094276011477UL, offset); diff --git a/clients/csharp/Solana.Unity.Bolt/WorldProgram/World.cs b/clients/csharp/Solana.Unity.Bolt/WorldProgram/World.cs index 17b55f3..07a73f4 100644 --- a/clients/csharp/Solana.Unity.Bolt/WorldProgram/World.cs +++ b/clients/csharp/Solana.Unity.Bolt/WorldProgram/World.cs @@ -15,8 +15,6 @@ namespace Program { public partial class WorldProgram { - public static readonly PublicKey CPI_AUTH_ADDRESS = new("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi"); - public static Solana.Unity.Rpc.Models.TransactionInstruction AddEntity(AddEntityAccounts accounts, PublicKey programId = null) { programId ??= new(ID); @@ -29,14 +27,18 @@ public static Solana.Unity.Rpc.Models.TransactionInstruction AddEntity(AddEntity return AddEntity(accounts, System.Text.Encoding.UTF8.GetBytes(extraSeed), programId); } - public static PublicKey FindBufferPda() { + public static PublicKey FindCpiAuthPda() { PublicKey.TryFindProgramAddress(new[] { - Encoding.UTF8.GetBytes("buffer"), + Encoding.UTF8.GetBytes("cpi_auth"), }, new PublicKey(ID), out var pda, out _); return pda; } + public static PublicKey FindBufferPda(PublicKey account) { + return FindBufferPda(account, new PublicKey(ID)); + } + public static PublicKey FindSessionTokenPda(PublicKey sessionSigner, PublicKey authority) { PublicKey.TryFindProgramAddress(new[] @@ -233,8 +235,8 @@ public static Solana.Unity.Rpc.Models.TransactionInstruction ApplySystem( Solana.Unity.Rpc.Models.TransactionInstruction instruction; if (sessionToken != null) { var apply = new ApplyWithSessionAccounts() { - CpiAuth = CPI_AUTH_ADDRESS, - Buffer = FindBufferPda(), + CpiAuth = WorldProgram.FindCpiAuthPda(), + Buffer = FindBufferPda(authority), BoltSystem = system, Authority = authority, World = world, @@ -243,8 +245,8 @@ public static Solana.Unity.Rpc.Models.TransactionInstruction ApplySystem( instruction = ApplyWithSession(apply, args, programId); } else { var apply = new ApplyAccounts() { - CpiAuth = CPI_AUTH_ADDRESS, - Buffer = FindBufferPda(), + CpiAuth = WorldProgram.FindCpiAuthPda(), + Buffer = FindBufferPda(authority), BoltSystem = system, Authority = authority, World = world, diff --git a/clients/typescript/src/generated/idl/world.json b/clients/typescript/src/generated/idl/world.json index a9521b4..e8a2097 100644 --- a/clients/typescript/src/generated/idl/world.json +++ b/clients/typescript/src/generated/idl/world.json @@ -125,6 +125,7 @@ }, { "name": "authority", + "writable": true, "signer": true }, { @@ -167,6 +168,7 @@ }, { "name": "authority", + "writable": true, "signer": true }, { @@ -521,6 +523,19 @@ 218 ] }, + { + "name": "SessionToken", + "discriminator": [ + 233, + 4, + 115, + 14, + 46, + 21, + 1, + 15 + ] + }, { "name": "World", "discriminator": [ @@ -597,6 +612,30 @@ ] } }, + { + "name": "SessionToken", + "type": { + "kind": "struct", + "fields": [ + { + "name": "authority", + "type": "pubkey" + }, + { + "name": "target_program", + "type": "pubkey" + }, + { + "name": "session_signer", + "type": "pubkey" + }, + { + "name": "valid_until", + "type": "i64" + } + ] + } + }, { "name": "World", "type": { diff --git a/clients/typescript/src/generated/instructions/apply.ts b/clients/typescript/src/generated/instructions/apply.ts index 3210443..fd63ad8 100644 --- a/clients/typescript/src/generated/instructions/apply.ts +++ b/clients/typescript/src/generated/instructions/apply.ts @@ -7,7 +7,7 @@ import * as beet from "@metaplex-foundation/beet"; import * as web3 from "@solana/web3.js"; -import { CPI_AUTH_ADDRESS } from "../../world/transactions"; +import { FindCpiAuthPda } from "../../index"; /** * @category Instructions @@ -98,7 +98,7 @@ export function createApplyInstruction( isSigner: false, }, { - pubkey: CPI_AUTH_ADDRESS, + pubkey: FindCpiAuthPda(), isWritable: false, isSigner: false, }, diff --git a/clients/typescript/src/generated/instructions/initializeComponent.ts b/clients/typescript/src/generated/instructions/initializeComponent.ts index 0d14c21..520ca00 100644 --- a/clients/typescript/src/generated/instructions/initializeComponent.ts +++ b/clients/typescript/src/generated/instructions/initializeComponent.ts @@ -7,7 +7,7 @@ import * as beet from "@metaplex-foundation/beet"; import * as web3 from "@solana/web3.js"; -import { CPI_AUTH_ADDRESS } from "../../world/transactions"; +import { FindCpiAuthPda } from "../../index"; /** * @category Instructions @@ -89,7 +89,7 @@ export function createInitializeComponentInstruction( isSigner: false, }, { - pubkey: CPI_AUTH_ADDRESS, + pubkey: FindCpiAuthPda(), isWritable: false, isSigner: false, }, diff --git a/clients/typescript/src/generated/types/world.ts b/clients/typescript/src/generated/types/world.ts index 90a127e..d87c671 100644 --- a/clients/typescript/src/generated/types/world.ts +++ b/clients/typescript/src/generated/types/world.ts @@ -98,6 +98,7 @@ export type World = { }, { name: "authority"; + writable: true; signer: true; }, { @@ -131,6 +132,7 @@ export type World = { }, { name: "authority"; + writable: true; signer: true; }, { @@ -383,6 +385,10 @@ export type World = { name: "registry"; discriminator: [47, 174, 110, 246, 184, 182, 252, 218]; }, + { + name: "sessionToken"; + discriminator: [233, 4, 115, 14, 46, 21, 1, 15]; + }, { name: "world"; discriminator: [145, 45, 170, 174, 122, 32, 155, 124]; @@ -450,6 +456,30 @@ export type World = { ]; }; }, + { + name: "sessionToken"; + type: { + kind: "struct"; + fields: [ + { + name: "authority"; + type: "pubkey"; + }, + { + name: "targetProgram"; + type: "pubkey"; + }, + { + name: "sessionSigner"; + type: "pubkey"; + }, + { + name: "validUntil"; + type: "i64"; + }, + ]; + }; + }, { name: "world"; type: { diff --git a/clients/typescript/src/index.ts b/clients/typescript/src/index.ts index 3643ecc..ca313c2 100644 --- a/clients/typescript/src/index.ts +++ b/clients/typescript/src/index.ts @@ -99,9 +99,16 @@ export function FindComponentProgramDataPda({ )[0]; } -export function FindBufferPda() { +export function FindBufferPda(owner: PublicKey) { return PublicKey.findProgramAddressSync( - [Buffer.from("buffer")], // TODO: Everyone will share the same buffer. We need to optimize this to derive a different buffer for each user or transaction. + [Buffer.from("buffer"), owner.toBuffer()], + WORLD_PROGRAM_ID, + )[0]; +} + +export function FindCpiAuthPda() { + return PublicKey.findProgramAddressSync( + [Buffer.from("cpi_auth")], WORLD_PROGRAM_ID, )[0]; } diff --git a/clients/typescript/src/world/transactions.ts b/clients/typescript/src/world/transactions.ts index a05fcac..4d12a4f 100644 --- a/clients/typescript/src/world/transactions.ts +++ b/clients/typescript/src/world/transactions.ts @@ -18,6 +18,7 @@ import { BN, FindComponentProgramDataPda, FindBufferPda, + FindCpiAuthPda, } from "../index"; import web3 from "@solana/web3.js"; import { @@ -35,10 +36,6 @@ import { } from "../generated"; import { type Idl, Program } from "@coral-xyz/anchor"; -export const CPI_AUTH_ADDRESS = new web3.PublicKey( - "B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi", -); - export async function InitializeRegistry({ payer, connection, @@ -366,7 +363,7 @@ export async function DestroyComponent({ componentProgram, componentProgramData, receiver, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .instruction(); const transaction = new Transaction().add(instruction); @@ -492,12 +489,12 @@ async function createApplySystemInstruction({ return program.methods .applyWithSession(SerializeArgs(args)) .accounts({ - buffer: FindBufferPda(), + buffer: FindBufferPda(authority ?? PROGRAM_ID), authority: authority ?? PROGRAM_ID, boltSystem: systemId, sessionToken: session.token, world, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .remainingAccounts(remainingAccounts) .instruction(); @@ -505,11 +502,11 @@ async function createApplySystemInstruction({ return program.methods .apply(SerializeArgs(args)) .accounts({ - buffer: FindBufferPda(), + buffer: FindBufferPda(authority ?? PROGRAM_ID), authority: authority ?? PROGRAM_ID, boltSystem: systemId, world, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .remainingAccounts(remainingAccounts) .instruction(); diff --git a/clients/typescript/test/low-level/ecs.ts b/clients/typescript/test/low-level/ecs.ts index 843bb5f..7607a7d 100644 --- a/clients/typescript/test/low-level/ecs.ts +++ b/clients/typescript/test/low-level/ecs.ts @@ -6,10 +6,9 @@ import { FindComponentProgramDataPda, FindEntityPda, SerializeArgs, - CPI_AUTH_ADDRESS, } from "../../lib"; import { Direction } from "../framework"; -import { FindBufferPda } from "../../src"; +import { FindBufferPda, FindCpiAuthPda } from "../../src"; export function ecs(framework) { describe("ECS", () => { @@ -106,7 +105,7 @@ export function ecs(framework) { data: framework.componentVelocityEntity1Pda, componentProgram: componentId, authority: framework.provider.wallet.publicKey, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .instruction(); const transaction = new anchor.web3.Transaction().add(instruction); @@ -127,7 +126,7 @@ export function ecs(framework) { data: framework.componentPositionEntity1Pda, componentProgram: componentId, authority: framework.worldProgram.programId, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .instruction(); const transaction = new anchor.web3.Transaction().add(instruction); @@ -148,7 +147,7 @@ export function ecs(framework) { data: componentPda, componentProgram: componentId, authority: framework.worldProgram.programId, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .instruction(); const transaction = new anchor.web3.Transaction().add(instruction); @@ -169,7 +168,7 @@ export function ecs(framework) { data: framework.componentPositionEntity4Pda, componentProgram: componentId, authority: framework.worldProgram.programId, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .instruction(); const transaction = new anchor.web3.Transaction().add(instruction); @@ -190,11 +189,11 @@ export function ecs(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs({ direction: Direction.Up })) .accounts({ - buffer: FindBufferPda(), + buffer: FindBufferPda(framework.provider.wallet.publicKey), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemSimpleMovement.programId, world: framework.worldPda, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .remainingAccounts([ { @@ -226,11 +225,11 @@ export function ecs(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs({ direction: Direction.Right })) .accounts({ - buffer: FindBufferPda(), + buffer: FindBufferPda(framework.provider.wallet.publicKey), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemSimpleMovement.programId, world: framework.worldPda, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .remainingAccounts([ { @@ -261,11 +260,11 @@ export function ecs(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs()) .accounts({ - buffer: FindBufferPda(), + buffer: FindBufferPda(framework.provider.wallet.publicKey), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemFly.programId, world: framework.worldPda, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .remainingAccounts([ { @@ -296,11 +295,11 @@ export function ecs(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs()) .accounts({ - buffer: FindBufferPda(), + buffer: FindBufferPda(framework.provider.wallet.publicKey), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemApplyVelocity.programId, world: framework.worldPda, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .remainingAccounts([ { @@ -350,11 +349,11 @@ export function ecs(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs()) .accounts({ - buffer: FindBufferPda(), + buffer: FindBufferPda(framework.provider.wallet.publicKey), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemApplyVelocity.programId, world: framework.worldPda, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .remainingAccounts([ { @@ -407,11 +406,11 @@ export function ecs(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs()) .accounts({ - buffer: FindBufferPda(), + buffer: FindBufferPda(framework.provider.wallet.publicKey), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemFly.programId, world: framework.worldPda, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .remainingAccounts([ { @@ -458,7 +457,7 @@ export function ecs(framework) { component: framework.componentVelocityEntity1Pda, componentProgramData: componentProgramData, receiver: keypair.publicKey, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .instruction(); const transaction = new anchor.web3.Transaction().add(instruction); diff --git a/clients/typescript/test/low-level/permissioning/component.ts b/clients/typescript/test/low-level/permissioning/component.ts index cfe469e..81bec96 100644 --- a/clients/typescript/test/low-level/permissioning/component.ts +++ b/clients/typescript/test/low-level/permissioning/component.ts @@ -4,8 +4,8 @@ import { FindEntityPda, FindComponentPda, SerializeArgs, - CPI_AUTH_ADDRESS, FindBufferPda, + FindCpiAuthPda, } from "../../../lib"; import { assert, expect } from "chai"; @@ -48,7 +48,7 @@ export function component(framework) { data: component, componentProgram: componentId, authority: framework.provider.wallet.publicKey, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .instruction(); const transaction = new anchor.web3.Transaction().add(instruction); @@ -66,11 +66,11 @@ export function component(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs()) .accounts({ - buffer: FindBufferPda(), + buffer: FindBufferPda(keypair.publicKey), authority: keypair.publicKey, boltSystem: framework.systemFly.programId, world: framework.worldPda, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .remainingAccounts([ { @@ -117,11 +117,11 @@ export function component(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs()) .accounts({ - buffer: FindBufferPda(), + buffer: FindBufferPda(framework.provider.wallet.publicKey), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemFly.programId, world: framework.worldPda, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .remainingAccounts([ { diff --git a/clients/typescript/test/low-level/permissioning/world.ts b/clients/typescript/test/low-level/permissioning/world.ts index 3517365..aceab62 100644 --- a/clients/typescript/test/low-level/permissioning/world.ts +++ b/clients/typescript/test/low-level/permissioning/world.ts @@ -1,8 +1,8 @@ import { expect } from "chai"; import { anchor, - CPI_AUTH_ADDRESS, FindBufferPda, + FindCpiAuthPda, SerializeArgs, } from "../../../lib"; @@ -129,11 +129,11 @@ export function world(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs()) .accounts({ - buffer: FindBufferPda(), + buffer: FindBufferPda(framework.provider.wallet.publicKey), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemFly.programId, world: framework.worldPda, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .remainingAccounts([ { @@ -178,11 +178,11 @@ export function world(framework) { const instruction = await framework.worldProgram.methods .apply(SerializeArgs()) .accounts({ - buffer: FindBufferPda(), + buffer: FindBufferPda(framework.provider.wallet.publicKey), authority: framework.provider.wallet.publicKey, boltSystem: framework.systemFly.programId, world: framework.worldPda, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .remainingAccounts([ { diff --git a/clients/typescript/test/low-level/session.ts b/clients/typescript/test/low-level/session.ts index b924232..4efd2b7 100644 --- a/clients/typescript/test/low-level/session.ts +++ b/clients/typescript/test/low-level/session.ts @@ -7,8 +7,8 @@ import { SessionProgram, FindSessionTokenPda, BN, - CPI_AUTH_ADDRESS, FindBufferPda, + FindCpiAuthPda, } from "../../lib"; import { Keypair } from "@solana/web3.js"; @@ -33,7 +33,7 @@ export function session(framework) { authority: framework.provider.wallet.publicKey, targetProgram: framework.worldProgram.programId, sessionToken, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .instruction(); const transaction = new anchor.web3.Transaction().add(instruction); @@ -54,7 +54,7 @@ export function session(framework) { payer: sessionSigner.publicKey, entity: entity, world: framework.worldPda, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .instruction(); const transaction = new anchor.web3.Transaction().add(instruction); @@ -75,7 +75,7 @@ export function session(framework) { data: component, componentProgram: componentId, authority: framework.worldProgram.programId, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .instruction(); const transaction = new anchor.web3.Transaction().add(instruction); @@ -91,12 +91,12 @@ export function session(framework) { const instruction = await framework.worldProgram.methods .applyWithSession(SerializeArgs()) .accounts({ - buffer: FindBufferPda(), + buffer: FindBufferPda(sessionSigner.publicKey), authority: sessionSigner.publicKey, boltSystem: framework.systemFly.programId, world: framework.worldPda, sessionToken, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .remainingAccounts([ { @@ -144,7 +144,7 @@ export function session(framework) { payer: sessionSigner.publicKey, world: framework.worldPda, entity: entityWithAuthority, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .instruction(); const transaction = new anchor.web3.Transaction().add(instruction); @@ -165,7 +165,7 @@ export function session(framework) { data: componentWithAuthority, componentProgram: componentId, authority: framework.provider.wallet.publicKey, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .instruction(); const transaction = new anchor.web3.Transaction().add(instruction); @@ -181,12 +181,12 @@ export function session(framework) { const instruction = await framework.worldProgram.methods .applyWithSession(SerializeArgs()) .accounts({ - buffer: FindBufferPda(), + buffer: FindBufferPda(sessionSigner.publicKey), authority: sessionSigner.publicKey, boltSystem: framework.systemFly.programId, world: framework.worldPda, sessionToken, - cpiAuth: CPI_AUTH_ADDRESS, + cpiAuth: FindCpiAuthPda(), }) .remainingAccounts([ { diff --git a/crates/programs/world/src/lib.rs b/crates/programs/world/src/lib.rs index e6abf52..b8ea069 100644 --- a/crates/programs/world/src/lib.rs +++ b/crates/programs/world/src/lib.rs @@ -260,12 +260,12 @@ pub mod world { if !ctx.accounts.authority.is_signer && ctx.accounts.authority.key != &ID { return Err(WorldError::InvalidAuthority.into()); } - bolt_component::cpi::initialize(ctx.accounts.build(&[World::cpi_auth_seeds().as_slice()]))?; + bolt_component::cpi::initialize(ctx.accounts.build(&[World::cpi_auth_seeds().as_array().as_slice()]))?; Ok(()) } pub fn destroy_component(ctx: Context) -> Result<()> { - bolt_component::cpi::destroy(ctx.accounts.build(&[World::cpi_auth_seeds().as_slice()]))?; + bolt_component::cpi::destroy(ctx.accounts.build(&[World::cpi_auth_seeds().as_array().as_slice()]))?; Ok(()) } @@ -297,7 +297,7 @@ pub mod world { #[account()] pub bolt_system: UncheckedAccount<'info>, /// CHECK: authority check - #[account()] + #[account(mut)] pub authority: Signer<'info>, #[account()] /// CHECK: cpi auth check @@ -347,7 +347,7 @@ pub mod world { #[account()] pub bolt_system: UncheckedAccount<'info>, /// CHECK: authority check - #[account()] + #[account(mut)] pub authority: Signer<'info>, #[account()] /// CHECK: cpi auth check @@ -458,7 +458,7 @@ fn apply_impl<'info>( from: authority.to_account_info(), to: buffer.to_account_info(), }, - &[World::buffer_seeds().as_slice()], + &[World::buffer_seeds(authority.key).as_array().as_slice()], ), lamports, size as u64, @@ -480,7 +480,7 @@ fn apply_impl<'info>( cpi_auth: cpi_auth.to_account_info(), component: component.to_account_info(), }, - &[World::cpi_auth_seeds().as_slice()], + &[World::cpi_auth_seeds().as_array().as_slice()], ), *bolt_system.key, )?; @@ -493,7 +493,7 @@ fn apply_impl<'info>( buffer: buffer.to_account_info(), component: component.to_account_info(), }, - &[World::cpi_auth_seeds().as_slice()], + &[World::cpi_auth_seeds().as_array().as_slice()], ), )?; } @@ -517,7 +517,7 @@ fn apply_impl<'info>( cpi_auth: cpi_auth.to_account_info(), component: component.to_account_info(), }, - &[World::cpi_auth_seeds().as_slice()], + &[World::cpi_auth_seeds().as_array().as_slice()], ), program.key(), )?; @@ -534,7 +534,7 @@ fn apply_impl<'info>( buffer: buffer.to_account_info(), component: component.to_account_info(), }, - &[World::cpi_auth_seeds().as_slice()], + &[World::cpi_auth_seeds().as_array().as_slice()], ), )?; } @@ -777,6 +777,37 @@ pub struct WorldSystems { pub approved_systems: BTreeSet, } +pub struct BufferSeeds<'buffer, 'owner> { + pub buffer: &'buffer [u8], + pub owner: &'owner [u8], + pub bump: [u8; 1], +} + +impl<'buffer, 'owner> BufferSeeds<'buffer, 'owner> { + pub fn new(buffer: &'buffer [u8], owner: &'owner [u8], bump: u8) -> Self { + Self { buffer, owner, bump: [bump] } + } + + pub fn as_array(&self) -> [&[u8]; 3] { + [&self.buffer, &self.owner, &self.bump] + } +} + +pub struct CpiAuthSeeds<'cpi_auth> { + pub cpi_auth: &'cpi_auth [u8], + pub bump: [u8; 1], +} + +impl<'cpi_auth> CpiAuthSeeds<'cpi_auth> { + pub fn new(cpi_auth: &'cpi_auth [u8], bump: u8) -> Self { + Self { cpi_auth, bump: [bump] } + } + + pub fn as_array(&self) -> [&[u8]; 2] { + [&self.cpi_auth, &self.bump] + } +} + impl World { pub fn seed() -> &'static [u8] { b"world" @@ -790,16 +821,18 @@ impl World { Pubkey::find_program_address(&[World::seed(), &self.id.to_be_bytes()], &crate::ID) } - pub fn buffer_seeds() -> [&'static [u8]; 2] { - [b"buffer", &[255]] // 251 is the pre-computed bump for buffer. + pub fn buffer_seeds<'buffer, 'owner>(owner: &'owner Pubkey) -> BufferSeeds<'buffer, 'owner> { + let (_, bump) = Pubkey::find_program_address(&[b"buffer", owner.as_ref()], &crate::ID); + BufferSeeds::new(b"buffer", owner.as_ref(), bump) } - pub fn cpi_auth_seeds() -> [&'static [u8]; 2] { - [b"cpi_auth", &[251]] // 251 is the pre-computed bump for cpi_auth. + pub fn cpi_auth_seeds<'cpi_auth>() -> CpiAuthSeeds<'cpi_auth> { + let (_, bump) = Pubkey::find_program_address(&[b"cpi_auth"], &crate::ID); + CpiAuthSeeds::new(b"cpi_auth", bump) } - pub const fn cpi_auth_address() -> Pubkey { - Pubkey::from_str_const("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi") // This is the pre-computed address for cpi_auth. + pub fn cpi_auth_address() -> Pubkey { + Pubkey::find_program_address(&[b"cpi_auth"], &crate::ID).0 } } @@ -828,44 +861,3 @@ impl SystemWhitelist { 8 + Registry::INIT_SPACE } } - -// /// Builds the context for updating a component. -// pub fn build_update_context<'a, 'b, 'c, 'info>( -// component_program: AccountInfo<'info>, -// bolt_component: AccountInfo<'info>, -// authority: Signer<'info>, -// cpi_auth: UncheckedAccount<'info>, -// signer_seeds: &'a [&'b [&'c [u8]]], -// ) -> CpiContext<'a, 'b, 'c, 'info, bolt_component::cpi::accounts::Update<'info>> { -// let authority = authority.to_account_info(); -// let cpi_auth = cpi_auth.to_account_info(); -// let cpi_program = component_program; -// bolt_component::cpi::accounts::Update { -// bolt_component, -// authority, -// cpi_auth, -// } -// .build_cpi_context(cpi_program, signer_seeds) -// } - -// /// Builds the context for updating a component. -// pub fn build_update_context_with_session<'a, 'b, 'c, 'info>( -// component_program: AccountInfo<'info>, -// bolt_component: AccountInfo<'info>, -// authority: Signer<'info>, -// cpi_auth: UncheckedAccount<'info>, -// session_token: UncheckedAccount<'info>, -// signer_seeds: &'a [&'b [&'c [u8]]], -// ) -> CpiContext<'a, 'b, 'c, 'info, bolt_component::cpi::accounts::UpdateWithSession<'info>> { -// let authority = authority.to_account_info(); -// let cpi_auth = cpi_auth.to_account_info(); -// let cpi_program = component_program; -// let session_token = session_token.to_account_info(); -// bolt_component::cpi::accounts::UpdateWithSession { -// bolt_component, -// authority, -// cpi_auth, -// session_token, -// } -// .build_cpi_context(cpi_program, signer_seeds) -// } From 10deb9521cefc296d1de0abebbd926130ceb48ee Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Wed, 13 Aug 2025 16:54:21 -0300 Subject: [PATCH 10/21] :fire: Removing CU test for whitelisting --- .../typescript/test/low-level/permissioning/world.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/clients/typescript/test/low-level/permissioning/world.ts b/clients/typescript/test/low-level/permissioning/world.ts index aceab62..b438ab8 100644 --- a/clients/typescript/test/low-level/permissioning/world.ts +++ b/clients/typescript/test/low-level/permissioning/world.ts @@ -106,17 +106,11 @@ export function world(framework) { world: framework.worldPda, }) .instruction(); - let transaction = new anchor.web3.Transaction().add(instruction); - let signature = await framework.provider.sendAndConfirm(transaction, [], { + const transaction = new anchor.web3.Transaction().add(instruction); + await framework.provider.sendAndConfirm(transaction, [], { skipPreflight: true, }); - let transactionResponse = - await framework.provider.connection.getTransaction(signature, { - commitment: "confirmed", - }); - console.log(transactionResponse.meta?.logMessages); // Reference CU is 29222 - // Get World and check permissionless and systems const worldAccount = await framework.worldProgram.account.world.fetch( framework.worldPda, From 9a86ff4bd6b27f086d4912c450bfcf89e478c7ad Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Thu, 14 Aug 2025 15:03:48 -0300 Subject: [PATCH 11/21] :recycle: Hardcoding known CPI Auth PDA --- .../typescript/src/generated/idl/world.json | 42 +++++++++++- .../typescript/src/generated/types/world.ts | 24 +++++++ crates/bolt-lang/src/cpi.rs | 3 +- crates/bolt-lang/src/instructions/set_data.rs | 2 +- crates/programs/world/src/lib.rs | 68 +++++-------------- 5 files changed, 85 insertions(+), 54 deletions(-) diff --git a/clients/typescript/src/generated/idl/world.json b/clients/typescript/src/generated/idl/world.json index e8a2097..730863a 100644 --- a/clients/typescript/src/generated/idl/world.json +++ b/clients/typescript/src/generated/idl/world.json @@ -118,7 +118,26 @@ "accounts": [ { "name": "buffer", - "writable": true + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 98, + 117, + 102, + 102, + 101, + 114 + ] + }, + { + "kind": "account", + "path": "authority" + } + ] + } }, { "name": "bolt_system" @@ -161,7 +180,26 @@ "accounts": [ { "name": "buffer", - "writable": true + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 98, + 117, + 102, + 102, + 101, + 114 + ] + }, + { + "kind": "account", + "path": "authority" + } + ] + } }, { "name": "bolt_system" diff --git a/clients/typescript/src/generated/types/world.ts b/clients/typescript/src/generated/types/world.ts index d87c671..7f4eb9a 100644 --- a/clients/typescript/src/generated/types/world.ts +++ b/clients/typescript/src/generated/types/world.ts @@ -92,6 +92,18 @@ export type World = { { name: "buffer"; writable: true; + pda: { + seeds: [ + { + kind: "const"; + value: [98, 117, 102, 102, 101, 114]; + }, + { + kind: "account"; + path: "authority"; + }, + ]; + }; }, { name: "boltSystem"; @@ -126,6 +138,18 @@ export type World = { { name: "buffer"; writable: true; + pda: { + seeds: [ + { + kind: "const"; + value: [98, 117, 102, 102, 101, 114]; + }, + { + kind: "account"; + path: "authority"; + }, + ]; + }; }, { name: "boltSystem"; diff --git a/crates/bolt-lang/src/cpi.rs b/crates/bolt-lang/src/cpi.rs index d224f80..323670a 100644 --- a/crates/bolt-lang/src/cpi.rs +++ b/crates/bolt-lang/src/cpi.rs @@ -1,8 +1,9 @@ use crate::prelude::*; use crate::BoltError; +#[inline(always)] pub fn checker<'info>(cpi_auth: &AccountInfo<'info>) -> Result<()> { - if !cpi_auth.is_signer || cpi_auth.key != &crate::world::World::cpi_auth_address() { + if !cpi_auth.is_signer || cpi_auth.key != crate::world::World::cpi_auth_address() { return Err(BoltError::InvalidCaller.into()); } Ok(()) diff --git a/crates/bolt-lang/src/instructions/set_data.rs b/crates/bolt-lang/src/instructions/set_data.rs index d1bfc16..8fb5f98 100644 --- a/crates/bolt-lang/src/instructions/set_data.rs +++ b/crates/bolt-lang/src/instructions/set_data.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +#[inline(always)] pub fn set_data<'info>(cpi_auth: AccountInfo<'info>, buffer: AccountInfo<'info>, component: AccountInfo<'info>) -> Result<()> { crate::cpi::checker(&cpi_auth)?; let buffer_data = buffer.try_borrow_data()?; @@ -8,4 +9,3 @@ pub fn set_data<'info>(cpi_auth: AccountInfo<'info>, buffer: AccountInfo<'info>, component_data.copy_from_slice(&buffer_data); Ok(()) } - \ No newline at end of file diff --git a/crates/programs/world/src/lib.rs b/crates/programs/world/src/lib.rs index b8ea069..16bba4f 100644 --- a/crates/programs/world/src/lib.rs +++ b/crates/programs/world/src/lib.rs @@ -3,6 +3,8 @@ use anchor_lang::{prelude::*, system_program}; use error::WorldError; use std::collections::BTreeSet; +static CPI_AUTH_ADDRESS: Pubkey = Pubkey::from_str_const("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi"); + #[cfg(not(feature = "no-entrypoint"))] use solana_security_txt::security_txt; @@ -260,12 +262,12 @@ pub mod world { if !ctx.accounts.authority.is_signer && ctx.accounts.authority.key != &ID { return Err(WorldError::InvalidAuthority.into()); } - bolt_component::cpi::initialize(ctx.accounts.build(&[World::cpi_auth_seeds().as_array().as_slice()]))?; + bolt_component::cpi::initialize(ctx.accounts.build(&[World::cpi_auth_seeds().as_slice()]))?; Ok(()) } pub fn destroy_component(ctx: Context) -> Result<()> { - bolt_component::cpi::destroy(ctx.accounts.build(&[World::cpi_auth_seeds().as_array().as_slice()]))?; + bolt_component::cpi::destroy(ctx.accounts.build(&[World::cpi_auth_seeds().as_slice()]))?; Ok(()) } @@ -275,6 +277,7 @@ pub mod world { ) -> Result<()> { apply_impl( &ctx.accounts.buffer, + ctx.bumps.buffer, &ctx.accounts.authority, &ctx.accounts.world, &ctx.accounts.bolt_system, @@ -291,7 +294,7 @@ pub mod world { #[derive(Accounts)] pub struct Apply<'info> { /// CHECK: buffer data check - #[account(mut)] + #[account(mut, seeds = [b"buffer", authority.key.as_ref()], bump)] pub buffer: AccountInfo<'info>, /// CHECK: bolt system program check #[account()] @@ -325,6 +328,7 @@ pub mod world { ) -> Result<()> { apply_impl( &ctx.accounts.buffer, + ctx.bumps.buffer, &ctx.accounts.authority, &ctx.accounts.world, &ctx.accounts.bolt_system, @@ -341,7 +345,7 @@ pub mod world { #[derive(Accounts)] pub struct ApplyWithSession<'info> { /// CHECK: buffer data check - #[account(mut)] + #[account(mut, seeds = [b"buffer", authority.key.as_ref()], bump)] pub buffer: AccountInfo<'info>, /// CHECK: bolt system program check #[account()] @@ -375,6 +379,7 @@ pub mod world { #[allow(clippy::type_complexity)] fn apply_impl<'info>( buffer: &AccountInfo<'info>, + buffer_bump: u8, authority: &Signer<'info>, world: &Account<'info, World>, bolt_system: &UncheckedAccount<'info>, @@ -458,7 +463,7 @@ fn apply_impl<'info>( from: authority.to_account_info(), to: buffer.to_account_info(), }, - &[World::buffer_seeds(authority.key).as_array().as_slice()], + &[&[b"buffer", authority.key.as_ref(), &[buffer_bump]]], ), lamports, size as u64, @@ -480,7 +485,7 @@ fn apply_impl<'info>( cpi_auth: cpi_auth.to_account_info(), component: component.to_account_info(), }, - &[World::cpi_auth_seeds().as_array().as_slice()], + &[World::cpi_auth_seeds().as_slice()], ), *bolt_system.key, )?; @@ -493,7 +498,7 @@ fn apply_impl<'info>( buffer: buffer.to_account_info(), component: component.to_account_info(), }, - &[World::cpi_auth_seeds().as_array().as_slice()], + &[World::cpi_auth_seeds().as_slice()], ), )?; } @@ -517,7 +522,7 @@ fn apply_impl<'info>( cpi_auth: cpi_auth.to_account_info(), component: component.to_account_info(), }, - &[World::cpi_auth_seeds().as_array().as_slice()], + &[World::cpi_auth_seeds().as_slice()], ), program.key(), )?; @@ -534,7 +539,7 @@ fn apply_impl<'info>( buffer: buffer.to_account_info(), component: component.to_account_info(), }, - &[World::cpi_auth_seeds().as_array().as_slice()], + &[World::cpi_auth_seeds().as_slice()], ), )?; } @@ -777,37 +782,6 @@ pub struct WorldSystems { pub approved_systems: BTreeSet, } -pub struct BufferSeeds<'buffer, 'owner> { - pub buffer: &'buffer [u8], - pub owner: &'owner [u8], - pub bump: [u8; 1], -} - -impl<'buffer, 'owner> BufferSeeds<'buffer, 'owner> { - pub fn new(buffer: &'buffer [u8], owner: &'owner [u8], bump: u8) -> Self { - Self { buffer, owner, bump: [bump] } - } - - pub fn as_array(&self) -> [&[u8]; 3] { - [&self.buffer, &self.owner, &self.bump] - } -} - -pub struct CpiAuthSeeds<'cpi_auth> { - pub cpi_auth: &'cpi_auth [u8], - pub bump: [u8; 1], -} - -impl<'cpi_auth> CpiAuthSeeds<'cpi_auth> { - pub fn new(cpi_auth: &'cpi_auth [u8], bump: u8) -> Self { - Self { cpi_auth, bump: [bump] } - } - - pub fn as_array(&self) -> [&[u8]; 2] { - [&self.cpi_auth, &self.bump] - } -} - impl World { pub fn seed() -> &'static [u8] { b"world" @@ -821,18 +795,12 @@ impl World { Pubkey::find_program_address(&[World::seed(), &self.id.to_be_bytes()], &crate::ID) } - pub fn buffer_seeds<'buffer, 'owner>(owner: &'owner Pubkey) -> BufferSeeds<'buffer, 'owner> { - let (_, bump) = Pubkey::find_program_address(&[b"buffer", owner.as_ref()], &crate::ID); - BufferSeeds::new(b"buffer", owner.as_ref(), bump) - } - - pub fn cpi_auth_seeds<'cpi_auth>() -> CpiAuthSeeds<'cpi_auth> { - let (_, bump) = Pubkey::find_program_address(&[b"cpi_auth"], &crate::ID); - CpiAuthSeeds::new(b"cpi_auth", bump) + pub const fn cpi_auth_seeds() -> [&'static [u8]; 2] { + [b"cpi_auth", &[251]] // 251 is the pre-computed bump for cpi_auth. } - pub fn cpi_auth_address() -> Pubkey { - Pubkey::find_program_address(&[b"cpi_auth"], &crate::ID).0 + pub const fn cpi_auth_address() -> &'static Pubkey { + &CPI_AUTH_ADDRESS // This is the pre-computed address for cpi_auth. } } From e777ffeedba848e4301dacabdf08a00415a96fca Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Thu, 14 Aug 2025 16:57:27 -0300 Subject: [PATCH 12/21] :recycle: Small optimizations --- crates/programs/world/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/programs/world/src/lib.rs b/crates/programs/world/src/lib.rs index 16bba4f..867ea0e 100644 --- a/crates/programs/world/src/lib.rs +++ b/crates/programs/world/src/lib.rs @@ -402,7 +402,7 @@ fn apply_impl<'info>( return Err(WorldError::SystemNotApproved.into()); } - let mut pairs = Vec::new(); + let mut pairs = Vec::with_capacity(remaining_accounts.len() / 2); while remaining_accounts.len() >= 2 { let program = remaining_accounts.remove(0); if program.key() == ID { @@ -425,9 +425,10 @@ fn apply_impl<'info>( key_bytes.copy_from_slice(&data_ref[start..start+32]); let component_authority = Pubkey::new_from_array(key_bytes); + let unix_timestamp = Clock::get()?.unix_timestamp; if let Some(session_token) = session_token { if component_authority == ID { - require!(Clock::get()?.unix_timestamp < session_token.valid_until, session_keys::SessionError::InvalidToken); + require!(unix_timestamp < session_token.valid_until, session_keys::SessionError::InvalidToken); } else { let validity_ctx = session_keys::ValidityChecker { session_token: session_token.clone(), From 78ac3ca641a82a5e255a985ff7d4f99f188684b2 Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Thu, 14 Aug 2025 16:58:34 -0300 Subject: [PATCH 13/21] :rotating_light: Fixing linter warnings --- crates/bolt-lang/attribute/system/src/lib.rs | 1 - crates/bolt-lang/src/account.rs | 58 +++++++++----- crates/bolt-lang/src/bpf_writer.rs | 1 - crates/bolt-lang/src/cpi.rs | 2 +- crates/bolt-lang/src/instructions/mod.rs | 2 +- crates/bolt-lang/src/instructions/set_data.rs | 6 +- .../bolt-lang/src/instructions/set_owner.rs | 6 +- crates/bolt-lang/src/lib.rs | 6 +- .../bolt-lang/utils/src/instructions/mod.rs | 4 +- .../utils/src/instructions/set_data.rs | 6 +- .../utils/src/instructions/set_owner.rs | 2 +- crates/bolt-lang/utils/src/lib.rs | 2 +- crates/programs/bolt-system/src/lib.rs | 2 +- crates/programs/world/src/lib.rs | 75 +++++++++++-------- 14 files changed, 105 insertions(+), 68 deletions(-) diff --git a/crates/bolt-lang/attribute/system/src/lib.rs b/crates/bolt-lang/attribute/system/src/lib.rs index b22bb3b..04d2db7 100644 --- a/crates/bolt-lang/attribute/system/src/lib.rs +++ b/crates/bolt-lang/attribute/system/src/lib.rs @@ -190,7 +190,6 @@ impl SystemTransform { }).unwrap()); } - fn add_set_data_function(content: &mut Vec) { let set_data = bolt_utils::instructions::generate_set_data(); content.push(syn::parse2(set_data.function).unwrap()); diff --git a/crates/bolt-lang/src/account.rs b/crates/bolt-lang/src/account.rs index fe00535..2a7d8be 100644 --- a/crates/bolt-lang/src/account.rs +++ b/crates/bolt-lang/src/account.rs @@ -9,7 +9,9 @@ impl Discriminator for BoltAcc const DISCRIMINATOR: &'static [u8] = T::DISCRIMINATOR; } -impl anchor_lang::AccountDeserialize for BoltAccount { +impl + anchor_lang::AccountDeserialize for BoltAccount +{ fn try_deserialize(data: &mut &[u8]) -> Result { Ok(BoltAccount(T::try_deserialize(data)?)) } @@ -19,32 +21,47 @@ impl anchor_ } } -impl anchor_lang::AccountSerialize for BoltAccount { +impl anchor_lang::AccountSerialize + for BoltAccount +{ fn try_serialize(&self, writer: &mut W) -> Result<()> { self.0.try_serialize(writer) } } -impl anchor_lang::Owner for BoltAccount { +impl anchor_lang::Owner + for BoltAccount +{ fn owner() -> Pubkey { pubkey_from_array([P0, P1]) } } -impl<'info, T: anchor_lang::ToAccountInfos<'info>, const P0: u128, const P1: u128> anchor_lang::ToAccountInfos<'info> for BoltAccount { +impl<'info, T: anchor_lang::ToAccountInfos<'info>, const P0: u128, const P1: u128> + anchor_lang::ToAccountInfos<'info> for BoltAccount +{ fn to_account_infos(&self) -> Vec> { self.0.to_account_infos() } } -impl<'info, T: anchor_lang::ToAccountMetas, const P0: u128, const P1: u128> anchor_lang::ToAccountMetas for BoltAccount { +impl anchor_lang::ToAccountMetas + for BoltAccount +{ fn to_account_metas(&self, is_signer: Option) -> Vec { self.0.to_account_metas(is_signer) } } -impl<'info, T: anchor_lang::ToAccountInfos<'info> + anchor_lang::ToAccountInfo<'info> + anchor_lang::AccountSerialize + anchor_lang::AccountsExit<'info>, const P0: u128, const P1: u128> anchor_lang::AccountsExit<'info> - for BoltAccount +impl< + 'info, + T: anchor_lang::ToAccountInfos<'info> + + anchor_lang::ToAccountInfo<'info> + + anchor_lang::AccountSerialize + + anchor_lang::AccountsExit<'info>, + const P0: u128, + const P1: u128, + > anchor_lang::AccountsExit<'info> for BoltAccount { fn exit(&self, _program_id: &Pubkey) -> Result<()> { let info = self.0.to_account_info(); @@ -56,7 +73,6 @@ impl<'info, T: anchor_lang::ToAccountInfos<'info> + anchor_lang::ToAccountInfo<' } } - impl std::ops::Deref for BoltAccount { type Target = T; @@ -72,11 +88,15 @@ impl std::ops::DerefMut for BoltAccount anchor_lang::IdlBuild for BoltAccount { +impl anchor_lang::IdlBuild + for BoltAccount +{ fn create_type() -> Option { T::create_type() } - fn insert_types(types: &mut std::collections::BTreeMap) { + fn insert_types( + types: &mut std::collections::BTreeMap, + ) { T::insert_types(types); } fn get_full_path() -> String { @@ -87,20 +107,16 @@ impl anchor_lang::IdlB pub const fn pubkey_p0(key: Pubkey) -> u128 { let bytes = key.to_bytes(); u128::from_le_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], - bytes[4], bytes[5], bytes[6], bytes[7], - bytes[8], bytes[9], bytes[10], bytes[11], - bytes[12], bytes[13], bytes[14], bytes[15], + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], + bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], ]) } pub const fn pubkey_p1(key: Pubkey) -> u128 { let bytes = key.to_bytes(); u128::from_le_bytes([ - bytes[16], bytes[17], bytes[18], bytes[19], - bytes[20], bytes[21], bytes[22], bytes[23], - bytes[24], bytes[25], bytes[26], bytes[27], - bytes[28], bytes[29], bytes[30], bytes[31], + bytes[16], bytes[17], bytes[18], bytes[19], bytes[20], bytes[21], bytes[22], bytes[23], + bytes[24], bytes[25], bytes[26], bytes[27], bytes[28], bytes[29], bytes[30], bytes[31], ]) } @@ -111,5 +127,9 @@ pub const fn pubkey_from_u128s(p0: u128, p1: u128) -> Pubkey { pub const fn pubkey_from_array(array: [u128; 2]) -> Pubkey { let p0 = array[0].to_le_bytes(); let p1 = array[1].to_le_bytes(); - Pubkey::new_from_array([p0[0], p0[1], p0[2], p0[3], p0[4], p0[5], p0[6], p0[7], p0[8], p0[9], p0[10], p0[11], p0[12], p0[13], p0[14], p0[15], p1[0], p1[1], p1[2], p1[3], p1[4], p1[5], p1[6], p1[7], p1[8], p1[9], p1[10], p1[11], p1[12], p1[13], p1[14], p1[15]]) + Pubkey::new_from_array([ + p0[0], p0[1], p0[2], p0[3], p0[4], p0[5], p0[6], p0[7], p0[8], p0[9], p0[10], p0[11], + p0[12], p0[13], p0[14], p0[15], p1[0], p1[1], p1[2], p1[3], p1[4], p1[5], p1[6], p1[7], + p1[8], p1[9], p1[10], p1[11], p1[12], p1[13], p1[14], p1[15], + ]) } diff --git a/crates/bolt-lang/src/bpf_writer.rs b/crates/bolt-lang/src/bpf_writer.rs index b679aa2..ee1a25d 100644 --- a/crates/bolt-lang/src/bpf_writer.rs +++ b/crates/bolt-lang/src/bpf_writer.rs @@ -1,5 +1,4 @@ /// Implementation from Anchor. - use solana_program::program_memory::sol_memcpy; use std::cmp; use std::io::{self, Write}; diff --git a/crates/bolt-lang/src/cpi.rs b/crates/bolt-lang/src/cpi.rs index 323670a..b3f08dd 100644 --- a/crates/bolt-lang/src/cpi.rs +++ b/crates/bolt-lang/src/cpi.rs @@ -2,7 +2,7 @@ use crate::prelude::*; use crate::BoltError; #[inline(always)] -pub fn checker<'info>(cpi_auth: &AccountInfo<'info>) -> Result<()> { +pub fn checker(cpi_auth: &AccountInfo<'_>) -> Result<()> { if !cpi_auth.is_signer || cpi_auth.key != crate::world::World::cpi_auth_address() { return Err(BoltError::InvalidCaller.into()); } diff --git a/crates/bolt-lang/src/instructions/mod.rs b/crates/bolt-lang/src/instructions/mod.rs index 610ac70..063447a 100644 --- a/crates/bolt-lang/src/instructions/mod.rs +++ b/crates/bolt-lang/src/instructions/mod.rs @@ -2,4 +2,4 @@ mod set_owner; pub use set_owner::*; mod set_data; -pub use set_data::*; \ No newline at end of file +pub use set_data::*; diff --git a/crates/bolt-lang/src/instructions/set_data.rs b/crates/bolt-lang/src/instructions/set_data.rs index 8fb5f98..729c8ed 100644 --- a/crates/bolt-lang/src/instructions/set_data.rs +++ b/crates/bolt-lang/src/instructions/set_data.rs @@ -1,7 +1,11 @@ use crate::prelude::*; #[inline(always)] -pub fn set_data<'info>(cpi_auth: AccountInfo<'info>, buffer: AccountInfo<'info>, component: AccountInfo<'info>) -> Result<()> { +pub fn set_data<'info>( + cpi_auth: AccountInfo<'info>, + buffer: AccountInfo<'info>, + component: AccountInfo<'info>, +) -> Result<()> { crate::cpi::checker(&cpi_auth)?; let buffer_data = buffer.try_borrow_data()?; component.realloc(buffer_data.len(), false)?; diff --git a/crates/bolt-lang/src/instructions/set_owner.rs b/crates/bolt-lang/src/instructions/set_owner.rs index d28fef6..7dba131 100644 --- a/crates/bolt-lang/src/instructions/set_owner.rs +++ b/crates/bolt-lang/src/instructions/set_owner.rs @@ -1,6 +1,10 @@ use crate::prelude::*; -pub fn set_owner<'info>(cpi_auth: AccountInfo<'info>, component: AccountInfo<'info>, owner: Pubkey) -> Result<()> { +pub fn set_owner<'info>( + cpi_auth: AccountInfo<'info>, + component: AccountInfo<'info>, + owner: Pubkey, +) -> Result<()> { crate::cpi::checker(&cpi_auth)?; component.realloc(0, false)?; component.assign(&owner); diff --git a/crates/bolt-lang/src/lib.rs b/crates/bolt-lang/src/lib.rs index 5c9c1b9..0c8a17a 100644 --- a/crates/bolt-lang/src/lib.rs +++ b/crates/bolt-lang/src/lib.rs @@ -8,14 +8,14 @@ pub use anchor_lang::{ AccountDeserialize, AccountSerialize, AnchorDeserialize, AnchorSerialize, Bumps, Result, }; -pub mod instructions; pub mod bpf_writer; +pub mod instructions; pub use instructions::*; -pub mod cpi; pub mod account; -pub use session_keys; +pub mod cpi; pub use account::BoltAccount; +pub use session_keys; pub use bolt_attribute_bolt_arguments::arguments; pub use bolt_attribute_bolt_component::component; diff --git a/crates/bolt-lang/utils/src/instructions/mod.rs b/crates/bolt-lang/utils/src/instructions/mod.rs index 9108948..acfbcf5 100644 --- a/crates/bolt-lang/utils/src/instructions/mod.rs +++ b/crates/bolt-lang/utils/src/instructions/mod.rs @@ -8,5 +8,5 @@ use proc_macro2::TokenStream; pub struct InstructionGeneration { pub function: TokenStream, - pub accounts: TokenStream -} \ No newline at end of file + pub accounts: TokenStream, +} diff --git a/crates/bolt-lang/utils/src/instructions/set_data.rs b/crates/bolt-lang/utils/src/instructions/set_data.rs index 6199497..b552c8c 100644 --- a/crates/bolt-lang/utils/src/instructions/set_data.rs +++ b/crates/bolt-lang/utils/src/instructions/set_data.rs @@ -1,5 +1,5 @@ -use quote::quote; use crate::instructions::InstructionGeneration; +use quote::quote; pub fn generate_set_data() -> InstructionGeneration { InstructionGeneration { @@ -22,6 +22,6 @@ pub fn generate_set_data() -> InstructionGeneration { #[account(mut)] pub component: UncheckedAccount<'info>, } - } + }, } -} \ No newline at end of file +} diff --git a/crates/bolt-lang/utils/src/instructions/set_owner.rs b/crates/bolt-lang/utils/src/instructions/set_owner.rs index dec6a16..c878391 100644 --- a/crates/bolt-lang/utils/src/instructions/set_owner.rs +++ b/crates/bolt-lang/utils/src/instructions/set_owner.rs @@ -1,5 +1,5 @@ -use quote::quote; use crate::instructions::InstructionGeneration; +use quote::quote; /// Generate the set owner function and struct. pub fn generate_set_owner() -> InstructionGeneration { diff --git a/crates/bolt-lang/utils/src/lib.rs b/crates/bolt-lang/utils/src/lib.rs index a817af3..52684bc 100644 --- a/crates/bolt-lang/utils/src/lib.rs +++ b/crates/bolt-lang/utils/src/lib.rs @@ -1,2 +1,2 @@ +pub mod instructions; pub mod metadata; -pub mod instructions; \ No newline at end of file diff --git a/crates/programs/bolt-system/src/lib.rs b/crates/programs/bolt-system/src/lib.rs index 5fda094..521ea8b 100644 --- a/crates/programs/bolt-system/src/lib.rs +++ b/crates/programs/bolt-system/src/lib.rs @@ -44,4 +44,4 @@ pub struct SetOwner<'info> { /// CHECK: This is a component account. #[account(mut)] pub component: UncheckedAccount<'info>, -} \ No newline at end of file +} diff --git a/crates/programs/world/src/lib.rs b/crates/programs/world/src/lib.rs index 867ea0e..546aa80 100644 --- a/crates/programs/world/src/lib.rs +++ b/crates/programs/world/src/lib.rs @@ -3,7 +3,8 @@ use anchor_lang::{prelude::*, system_program}; use error::WorldError; use std::collections::BTreeSet; -static CPI_AUTH_ADDRESS: Pubkey = Pubkey::from_str_const("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi"); +static CPI_AUTH_ADDRESS: Pubkey = + Pubkey::from_str_const("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi"); #[cfg(not(feature = "no-entrypoint"))] use solana_security_txt::security_txt; @@ -376,7 +377,7 @@ pub mod world { } } -#[allow(clippy::type_complexity)] +#[allow(clippy::type_complexity, clippy::too_many_arguments)] fn apply_impl<'info>( buffer: &AccountInfo<'info>, buffer_bump: u8, @@ -422,25 +423,39 @@ fn apply_impl<'info>( // 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]); + key_bytes.copy_from_slice(&data_ref[start..start + 32]); let component_authority = Pubkey::new_from_array(key_bytes); let unix_timestamp = Clock::get()?.unix_timestamp; if let Some(session_token) = session_token { if component_authority == ID { - require!(unix_timestamp < session_token.valid_until, session_keys::SessionError::InvalidToken); + require!( + 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(), + authority: component_authority, target_program: ID, }; - require!(session_token.validate(validity_ctx)?, session_keys::SessionError::InvalidToken); - require_eq!(component_authority, session_token.authority, session_keys::SessionError::InvalidToken); + 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); + require!( + component_authority == ID + || (component_authority == *authority.key && authority.is_signer), + WorldError::InvalidAuthority + ); } } @@ -491,17 +506,15 @@ fn apply_impl<'info>( *bolt_system.key, )?; - bolt_system::cpi::set_data( - CpiContext::new_with_signer( - bolt_system.to_account_info(), - bolt_system::cpi::accounts::SetData { - cpi_auth: cpi_auth.to_account_info(), - buffer: buffer.to_account_info(), - component: component.to_account_info(), - }, - &[World::cpi_auth_seeds().as_slice()], - ), - )?; + bolt_system::cpi::set_data(CpiContext::new_with_signer( + bolt_system.to_account_info(), + bolt_system::cpi::accounts::SetData { + cpi_auth: cpi_auth.to_account_info(), + buffer: buffer.to_account_info(), + component: component.to_account_info(), + }, + &[World::cpi_auth_seeds().as_slice()], + ))?; } bolt_system::cpi::bolt_execute( @@ -515,7 +528,7 @@ fn apply_impl<'info>( let mut data = buffer.try_borrow_mut_data()?; data.copy_from_slice(component.try_borrow_data()?.as_ref()); } - + bolt_system::cpi::set_owner( CpiContext::new_with_signer( bolt_system.to_account_info(), @@ -527,22 +540,20 @@ fn apply_impl<'info>( ), program.key(), )?; - + if *component.owner != program.key() { return Err(WorldError::InvalidComponentOwner.into()); } - bolt_component::cpi::set_data( - CpiContext::new_with_signer( - program.to_account_info(), - bolt_component::cpi::accounts::SetData { - cpi_auth: cpi_auth.to_account_info(), - buffer: buffer.to_account_info(), - component: component.to_account_info(), - }, - &[World::cpi_auth_seeds().as_slice()], - ), - )?; + bolt_component::cpi::set_data(CpiContext::new_with_signer( + program.to_account_info(), + bolt_component::cpi::accounts::SetData { + cpi_auth: cpi_auth.to_account_info(), + buffer: buffer.to_account_info(), + component: component.to_account_info(), + }, + &[World::cpi_auth_seeds().as_slice()], + ))?; } buffer.realloc(0, false)?; From 8da2dd88b4e74c33434ccd39a077a1a45435956c Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Thu, 14 Aug 2025 17:10:16 -0300 Subject: [PATCH 14/21] :fire: Removing debug print --- crates/bolt-cli/src/templates/component/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/bolt-cli/src/templates/component/mod.rs b/crates/bolt-cli/src/templates/component/mod.rs index 000cea4..cd518b2 100644 --- a/crates/bolt-cli/src/templates/component/mod.rs +++ b/crates/bolt-cli/src/templates/component/mod.rs @@ -39,8 +39,6 @@ pub fn component_type(idl: &Idl, component_id: &str) -> Result { None => return Err(anyhow::anyhow!("Component type not found in IDL")), }; - let component_name = component_account.name.to_upper_camel_case(); - println!("Component name: {}", component_name); let component_code = component_to_rust_code(type_def, component_id); let types_code = component_types_to_rust_code(&idl.types, &component_account.name); Ok(format!( From ec8387fef81800882a8fdc0b72647b14c02d1aea Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Mon, 18 Aug 2025 16:01:53 -0300 Subject: [PATCH 15/21] :memo: Adding docs about CPI Auth PDA seeds --- crates/programs/world/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/programs/world/src/lib.rs b/crates/programs/world/src/lib.rs index 546aa80..ccbd2d2 100644 --- a/crates/programs/world/src/lib.rs +++ b/crates/programs/world/src/lib.rs @@ -4,7 +4,7 @@ use error::WorldError; use std::collections::BTreeSet; static CPI_AUTH_ADDRESS: Pubkey = - Pubkey::from_str_const("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi"); + Pubkey::from_str_const("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi"); // Seeds: ["cpi_auth", [251]] #[cfg(not(feature = "no-entrypoint"))] use solana_security_txt::security_txt; From 9ac0e855a773af9b7f141897120d516895f72ccf Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Thu, 21 Aug 2025 16:20:33 -0300 Subject: [PATCH 16/21] :bug: Fixed case where the same component is used more than once in the system-input --- Cargo.toml | 1 - clients/typescript/test/framework.ts | 3 +- .../typescript/test/intermediate-level/ecs.ts | 12 +-- .../attribute/system-input/src/lib.rs | 7 +- crates/programs/world/src/lib.rs | 78 ++++++++++--------- .../system-with-few-components/src/lib.rs | 5 +- .../system-with-many-components/src/lib.rs | 4 +- 7 files changed, 58 insertions(+), 52 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e138c86..fa03066 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ members = [ "crates/programs/world", "crates/types", "examples/*", - "examples/system-with-many-components", ] [workspace.package] diff --git a/clients/typescript/test/framework.ts b/clients/typescript/test/framework.ts index f2e8f2f..31e6f36 100644 --- a/clients/typescript/test/framework.ts +++ b/clients/typescript/test/framework.ts @@ -80,7 +80,7 @@ export class Framework { var worldReport: number = 0; for (let index in log) { let line = log[index]; - if (line.includes(" consumed ")) { + if (line.includes(" consumed ")) { if (!line.includes("WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n")) { cpi.push(this.consume(line)); } else { @@ -93,4 +93,5 @@ export class Framework { console.log(`Total CPI Consumed: ${total}`); console.log(`Number of Instructions: ${numberOfInstructions}`); console.log(`World Report: ${worldReport}`); + } } diff --git a/clients/typescript/test/intermediate-level/ecs.ts b/clients/typescript/test/intermediate-level/ecs.ts index bf59958..296942e 100644 --- a/clients/typescript/test/intermediate-level/ecs.ts +++ b/clients/typescript/test/intermediate-level/ecs.ts @@ -307,9 +307,9 @@ export function ecs(framework: Framework) { let transactionResponse: any; do { transactionResponse = - await framework.provider.connection.getTransaction(signature, { - commitment: "confirmed", - }); + await framework.provider.connection.getTransaction(signature, { + commitment: "confirmed", + }); } while (transactionResponse?.meta?.logMessages === undefined); framework.report(transactionResponse?.meta?.logMessages); }); @@ -331,9 +331,9 @@ export function ecs(framework: Framework) { let transactionResponse: any; do { transactionResponse = - await framework.provider.connection.getTransaction(signature, { - commitment: "confirmed", - }); + await framework.provider.connection.getTransaction(signature, { + commitment: "confirmed", + }); } while (transactionResponse?.meta?.logMessages === undefined); framework.report(transactionResponse?.meta?.logMessages); }); diff --git a/crates/bolt-lang/attribute/system-input/src/lib.rs b/crates/bolt-lang/attribute/system-input/src/lib.rs index 5b9fc18..d7b4ade 100644 --- a/crates/bolt-lang/attribute/system-input/src/lib.rs +++ b/crates/bolt-lang/attribute/system-input/src/lib.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use proc_macro::TokenStream; use quote::quote; @@ -61,8 +63,9 @@ pub fn system_input(_attr: TokenStream, item: TokenStream) -> TokenStream { }) .collect(); - let bolt_accounts = fields.iter().map(|f| { - let field_type = &f.ty; + let unique_fields = fields.iter().map(|f| f.ty.clone()).collect::>(); + let bolt_accounts = unique_fields.iter().map(|f| { + let field_type = &f; quote! { pub type #field_type = bolt_lang::account::BoltAccount; } diff --git a/crates/programs/world/src/lib.rs b/crates/programs/world/src/lib.rs index ccbd2d2..b3b1b30 100644 --- a/crates/programs/world/src/lib.rs +++ b/crates/programs/world/src/lib.rs @@ -494,27 +494,29 @@ fn apply_impl<'info>( data.copy_from_slice(component.try_borrow_data()?.as_ref()); } - bolt_component::cpi::set_owner( - CpiContext::new_with_signer( - program.to_account_info(), - bolt_component::cpi::accounts::SetOwner { + if component.owner != bolt_system.key { + bolt_component::cpi::set_owner( + CpiContext::new_with_signer( + program.to_account_info(), + bolt_component::cpi::accounts::SetOwner { + cpi_auth: cpi_auth.to_account_info(), + component: component.to_account_info(), + }, + &[World::cpi_auth_seeds().as_slice()], + ), + *bolt_system.key, + )?; + + bolt_system::cpi::set_data(CpiContext::new_with_signer( + bolt_system.to_account_info(), + bolt_system::cpi::accounts::SetData { cpi_auth: cpi_auth.to_account_info(), + buffer: buffer.to_account_info(), component: component.to_account_info(), }, &[World::cpi_auth_seeds().as_slice()], - ), - *bolt_system.key, - )?; - - bolt_system::cpi::set_data(CpiContext::new_with_signer( - bolt_system.to_account_info(), - bolt_system::cpi::accounts::SetData { - cpi_auth: cpi_auth.to_account_info(), - buffer: buffer.to_account_info(), - component: component.to_account_info(), - }, - &[World::cpi_auth_seeds().as_slice()], - ))?; + ))?; + } } bolt_system::cpi::bolt_execute( @@ -529,31 +531,33 @@ fn apply_impl<'info>( data.copy_from_slice(component.try_borrow_data()?.as_ref()); } - bolt_system::cpi::set_owner( - CpiContext::new_with_signer( - bolt_system.to_account_info(), - bolt_system::cpi::accounts::SetOwner { + if *component.owner != program.key() { + bolt_system::cpi::set_owner( + CpiContext::new_with_signer( + bolt_system.to_account_info(), + bolt_system::cpi::accounts::SetOwner { + cpi_auth: cpi_auth.to_account_info(), + component: component.to_account_info(), + }, + &[World::cpi_auth_seeds().as_slice()], + ), + program.key(), + )?; + + if *component.owner != program.key() { + return Err(WorldError::InvalidComponentOwner.into()); + } + + bolt_component::cpi::set_data(CpiContext::new_with_signer( + program.to_account_info(), + bolt_component::cpi::accounts::SetData { cpi_auth: cpi_auth.to_account_info(), + buffer: buffer.to_account_info(), component: component.to_account_info(), }, &[World::cpi_auth_seeds().as_slice()], - ), - program.key(), - )?; - - if *component.owner != program.key() { - return Err(WorldError::InvalidComponentOwner.into()); + ))?; } - - bolt_component::cpi::set_data(CpiContext::new_with_signer( - program.to_account_info(), - bolt_component::cpi::accounts::SetData { - cpi_auth: cpi_auth.to_account_info(), - buffer: buffer.to_account_info(), - component: component.to_account_info(), - }, - &[World::cpi_auth_seeds().as_slice()], - ))?; } buffer.realloc(0, false)?; diff --git a/examples/system-with-few-components/src/lib.rs b/examples/system-with-few-components/src/lib.rs index bae63d7..dbec614 100644 --- a/examples/system-with-few-components/src/lib.rs +++ b/examples/system-with-few-components/src/lib.rs @@ -7,8 +7,8 @@ declare_id!("A3kNNSgmkTNA5V1qtnrbtNeqKrYHNxUMCTkqTDaQzE97"); #[system] pub mod system_with_few_components { - pub fn execute(ctx: Context, _args_p: Vec) -> Result { - Ok(ctx.accounts) + pub fn execute(_ctx: Context, _args_p: Vec) -> Result<()> { + Ok(()) } #[system_input] @@ -19,5 +19,4 @@ pub mod system_with_few_components { pub large4: Large, pub large5: Large, } - } \ No newline at end of file diff --git a/examples/system-with-many-components/src/lib.rs b/examples/system-with-many-components/src/lib.rs index 28b3650..92426f9 100644 --- a/examples/system-with-many-components/src/lib.rs +++ b/examples/system-with-many-components/src/lib.rs @@ -6,8 +6,8 @@ declare_id!("Hi4sMEb3uXhWCiLyrF7t3Z384an7YZsTj774cabAAPQB"); #[system] pub mod system_with_many_components { - pub fn execute(ctx: Context, _args_p: Vec) -> Result { - Ok(ctx.accounts) + pub fn execute(_ctx: Context, _args_p: Vec) -> Result<()> { + Ok(()) } #[system_input] From 50355edbd92eaf4dafc6835ed48f6308f203f2d6 Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Thu, 28 Aug 2025 16:07:08 +0200 Subject: [PATCH 17/21] :recycle: Optimizing apply_impl with less Vecs --- crates/programs/world/src/lib.rs | 35 ++++++++++---------------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/crates/programs/world/src/lib.rs b/crates/programs/world/src/lib.rs index b3b1b30..03f1334 100644 --- a/crates/programs/world/src/lib.rs +++ b/crates/programs/world/src/lib.rs @@ -287,7 +287,7 @@ pub mod world { args, &ctx.accounts.system_program, None, - ctx.remaining_accounts.to_vec(), + ctx.remaining_accounts, )?; Ok(()) } @@ -338,7 +338,7 @@ pub mod world { args, &ctx.accounts.system_program, Some(&ctx.accounts.session_token), - ctx.remaining_accounts.to_vec(), + ctx.remaining_accounts, )?; Ok(()) } @@ -389,7 +389,7 @@ fn apply_impl<'info>( args: Vec, system_program: &Program<'info, System>, session_token: Option<&Account<'info, session_keys::SessionToken>>, - mut remaining_accounts: Vec>, + remaining_accounts: &[AccountInfo<'info>], ) -> Result<()> { if !authority.is_signer && authority.key != &ID { return Err(WorldError::InvalidAuthority.into()); @@ -403,18 +403,10 @@ fn apply_impl<'info>( return Err(WorldError::SystemNotApproved.into()); } - let mut pairs = Vec::with_capacity(remaining_accounts.len() / 2); - while remaining_accounts.len() >= 2 { - let program = remaining_accounts.remove(0); - if program.key() == ID { - break; - } - let component = remaining_accounts.remove(0); - pairs.push((program, component)); - } + let index = remaining_accounts.iter().position(|x| x.key() == ID).unwrap_or(remaining_accounts.len()); // Authority check against component metadata (partial deserialize) - for (_, component) in &pairs { + for component in remaining_accounts[..index].iter().skip(1).step_by(2) { let data_ref = component.try_borrow_data()?; // Expect at least Anchor discriminator (8) + BoltMetadata (32) if data_ref.len() < 8 + 32 { @@ -459,14 +451,6 @@ fn apply_impl<'info>( } } - let mut components_accounts = pairs - .iter() - .map(|(_, component)| component) - .cloned() - .collect::>(); - components_accounts.append(&mut remaining_accounts); - let remaining_accounts = components_accounts; - // 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 { @@ -487,7 +471,8 @@ fn apply_impl<'info>( )?; } - for (program, component) in &pairs { + for pair in remaining_accounts[..index].chunks(2) { + let [program, component] = pair else { continue }; buffer.realloc(component.data_len(), false)?; { let mut data = buffer.try_borrow_mut_data()?; @@ -519,12 +504,14 @@ fn apply_impl<'info>( } } + let cpi_remaining_accounts = remaining_accounts[..index].iter().skip(1).step_by(2).chain(remaining_accounts[index..].iter().skip(1)).cloned().collect::>(); bolt_system::cpi::bolt_execute( - cpi_context.with_remaining_accounts(remaining_accounts), + cpi_context.with_remaining_accounts(cpi_remaining_accounts), args, )?; - for (program, component) in &pairs { + for pair in remaining_accounts[..index].chunks(2) { + let [program, component] = pair else { continue }; buffer.realloc(component.data_len(), false)?; { let mut data = buffer.try_borrow_mut_data()?; From f9f5f995b97ae25272c6cde26435275a65bb35cf Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Thu, 21 Aug 2025 16:39:24 -0300 Subject: [PATCH 18/21] :white_check_mark: Making entities/components unique in the tests --- clients/typescript/test/framework.ts | 2 - .../typescript/test/intermediate-level/ecs.ts | 76 +++++++++++++------ 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/clients/typescript/test/framework.ts b/clients/typescript/test/framework.ts index 31e6f36..359523d 100644 --- a/clients/typescript/test/framework.ts +++ b/clients/typescript/test/framework.ts @@ -44,9 +44,7 @@ export class Framework { acceleratedComponentPositionPda: PublicKey; componentPositionEntity1Pda: PublicKey; componentVelocityEntity1Pda: PublicKey; - componentLargeEntity1Pda: PublicKey; componentPositionEntity4Pda: PublicKey; - componentSmallEntity1Pda: PublicKey; constructor() { this.secondAuthority = Keypair.generate().publicKey; diff --git a/clients/typescript/test/intermediate-level/ecs.ts b/clients/typescript/test/intermediate-level/ecs.ts index 296942e..a450a0b 100644 --- a/clients/typescript/test/intermediate-level/ecs.ts +++ b/clients/typescript/test/intermediate-level/ecs.ts @@ -270,33 +270,37 @@ export function ecs(framework: Framework) { expect(position.z.toNumber()).to.equal(300); }); - it("Initialize Large Component on Entity 1", async () => { - const initializeComponent = await InitializeComponent({ - payer: framework.provider.wallet.publicKey, - entity: framework.entity1Pda, - componentId: framework.componentLarge.programId, - }); - await framework.provider.sendAndConfirm(initializeComponent.transaction); - framework.componentLargeEntity1Pda = initializeComponent.componentPda; // Saved for later - }); - - it("Initialize Small Component on Entity 1", async () => { - const initializeComponent = await InitializeComponent({ - payer: framework.provider.wallet.publicKey, - entity: framework.entity1Pda, - componentId: framework.componentSmall.programId, - }); - await framework.provider.sendAndConfirm(initializeComponent.transaction); - framework.componentSmallEntity1Pda = initializeComponent.componentPda; // Saved for later - }); - it("Apply System With Few Components on Entity 1", async () => { + let entitiesPdas: web3.PublicKey[] = []; + for (let i = 0; i < 5; i++) { + const addEntity = await AddEntity({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addEntity.transaction); + entitiesPdas.push(addEntity.entityPda); + } + + let componentsPdas: web3.PublicKey[] = []; + for (let i = 0; i < 5; i++) { + const initializeComponent = await InitializeComponent({ + payer: framework.provider.wallet.publicKey, + entity: entitiesPdas[i], + componentId: framework.componentLarge.programId, + }); + await framework.provider.sendAndConfirm( + initializeComponent.transaction, + ); + componentsPdas.push(initializeComponent.componentPda); + } + const applySystem = await ApplySystem({ authority: framework.provider.wallet.publicKey, systemId: framework.systemWithFewComponents.programId, world: framework.worldPda, - entities: new Array(5).fill(0).map(() => ({ - entity: framework.entity1Pda, + entities: entitiesPdas.map((entity) => ({ + entity, components: [{ componentId: framework.componentLarge.programId }], })), }); @@ -315,12 +319,36 @@ export function ecs(framework: Framework) { }); it("Apply System With Many Components on Entity 1", async () => { + let entitiesPdas: web3.PublicKey[] = []; + for (let i = 0; i < 10; i++) { + const addEntity = await AddEntity({ + payer: framework.provider.wallet.publicKey, + world: framework.worldPda, + connection: framework.provider.connection, + }); + await framework.provider.sendAndConfirm(addEntity.transaction); + entitiesPdas.push(addEntity.entityPda); + } + + let componentsPdas: web3.PublicKey[] = []; + for (let i = 0; i < 10; i++) { + const initializeComponent = await InitializeComponent({ + payer: framework.provider.wallet.publicKey, + entity: entitiesPdas[i], + componentId: framework.componentSmall.programId, + }); + await framework.provider.sendAndConfirm( + initializeComponent.transaction, + ); + componentsPdas.push(initializeComponent.componentPda); + } + const applySystem = await ApplySystem({ authority: framework.provider.wallet.publicKey, systemId: framework.systemWithManyComponents.programId, world: framework.worldPda, - entities: new Array(10).fill(0).map(() => ({ - entity: framework.entity1Pda, + entities: entitiesPdas.map((entity) => ({ + entity, components: [{ componentId: framework.componentSmall.programId }], })), }); From 468b49dc4daa3fd21d18270b39233a19af590cca Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Wed, 13 Aug 2025 00:27:47 -0300 Subject: [PATCH 19/21] :recycle: Authority & Session checks --- clients/typescript/test/framework.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/clients/typescript/test/framework.ts b/clients/typescript/test/framework.ts index 359523d..c5ec13b 100644 --- a/clients/typescript/test/framework.ts +++ b/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}`); - } } From d82d9c2796cc9218c46fed01c58259a04bfced65 Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Thu, 21 Aug 2025 16:20:33 -0300 Subject: [PATCH 20/21] :bug: Fixed case where the same time is used more than once in the system-input --- clients/typescript/test/framework.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/clients/typescript/test/framework.ts b/clients/typescript/test/framework.ts index c5ec13b..359523d 100644 --- a/clients/typescript/test/framework.ts +++ b/clients/typescript/test/framework.ts @@ -91,4 +91,5 @@ export class Framework { console.log(`Total CPI Consumed: ${total}`); console.log(`Number of Instructions: ${numberOfInstructions}`); console.log(`World Report: ${worldReport}`); + } } From 82575bd9b45b8dc67063e76a814c7d33da36da4a Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Fri, 22 Aug 2025 12:33:34 -0300 Subject: [PATCH 21/21] :construction: Giving components ownership to World --- .../typescript/src/generated/idl/world.json | 23 +++ .../typescript/src/generated/types/world.ts | 16 +++ clients/typescript/test/low-level/ecs.ts | 12 +- clients/typescript/test/low-level/index.ts | 4 +- .../test/low-level/permissioning/component.ts | 1 + clients/typescript/test/low-level/session.ts | 1 + clients/typescript/test/main.ts | 2 +- crates/programs/world/src/lib.rs | 135 ++++++++++-------- 8 files changed, 133 insertions(+), 61 deletions(-) diff --git a/clients/typescript/src/generated/idl/world.json b/clients/typescript/src/generated/idl/world.json index 730863a..5de44bc 100644 --- a/clients/typescript/src/generated/idl/world.json +++ b/clients/typescript/src/generated/idl/world.json @@ -325,6 +325,29 @@ "writable": true, "signer": true }, + { + "name": "buffer", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 98, + 117, + 102, + 102, + 101, + 114 + ] + }, + { + "kind": "account", + "path": "authority" + } + ] + } + }, { "name": "data", "writable": true diff --git a/clients/typescript/src/generated/types/world.ts b/clients/typescript/src/generated/types/world.ts index 7f4eb9a..b540cf6 100644 --- a/clients/typescript/src/generated/types/world.ts +++ b/clients/typescript/src/generated/types/world.ts @@ -248,6 +248,22 @@ export type World = { writable: true; signer: true; }, + { + name: "buffer"; + writable: true; + pda: { + seeds: [ + { + kind: "const"; + value: [98, 117, 102, 102, 101, 114]; + }, + { + kind: "account"; + path: "authority"; + }, + ]; + }; + }, { name: "data"; writable: true; diff --git a/clients/typescript/test/low-level/ecs.ts b/clients/typescript/test/low-level/ecs.ts index 7607a7d..7ebae98 100644 --- a/clients/typescript/test/low-level/ecs.ts +++ b/clients/typescript/test/low-level/ecs.ts @@ -105,6 +105,7 @@ export function ecs(framework) { data: framework.componentVelocityEntity1Pda, componentProgram: componentId, authority: framework.provider.wallet.publicKey, + buffer: FindBufferPda(framework.provider.wallet.publicKey), cpiAuth: FindCpiAuthPda(), }) .instruction(); @@ -126,6 +127,7 @@ export function ecs(framework) { data: framework.componentPositionEntity1Pda, componentProgram: componentId, authority: framework.worldProgram.programId, + buffer: FindBufferPda(framework.worldProgram.programId), cpiAuth: FindCpiAuthPda(), }) .instruction(); @@ -147,6 +149,7 @@ export function ecs(framework) { data: componentPda, componentProgram: componentId, authority: framework.worldProgram.programId, + buffer: FindBufferPda(framework.worldProgram.programId), cpiAuth: FindCpiAuthPda(), }) .instruction(); @@ -168,6 +171,7 @@ export function ecs(framework) { data: framework.componentPositionEntity4Pda, componentProgram: componentId, authority: framework.worldProgram.programId, + buffer: FindBufferPda(framework.worldProgram.programId), cpiAuth: FindCpiAuthPda(), }) .instruction(); @@ -210,7 +214,11 @@ export function ecs(framework) { .instruction(); const transaction = new anchor.web3.Transaction().add(instruction); - await framework.provider.sendAndConfirm(transaction); + let signature = await framework.provider.sendAndConfirm(transaction); + console.log("Signature: ", signature); + + let accountInfo = await framework.provider.connection.getAccountInfo(framework.componentPositionEntity1Pda); + console.log("Account data: ", accountInfo.data); const position = await framework.exampleComponentPosition.account.position.fetch( @@ -221,6 +229,8 @@ export function ecs(framework) { expect(position.z.toNumber()).to.equal(0); }); + return; + it("Apply Simple Movement System (Right) on Entity 1", async () => { const instruction = await framework.worldProgram.methods .apply(SerializeArgs({ direction: Direction.Right })) diff --git a/clients/typescript/test/low-level/index.ts b/clients/typescript/test/low-level/index.ts index 9d8d98f..0b39100 100644 --- a/clients/typescript/test/low-level/index.ts +++ b/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); }); diff --git a/clients/typescript/test/low-level/permissioning/component.ts b/clients/typescript/test/low-level/permissioning/component.ts index 81bec96..e1acbec 100644 --- a/clients/typescript/test/low-level/permissioning/component.ts +++ b/clients/typescript/test/low-level/permissioning/component.ts @@ -48,6 +48,7 @@ export function component(framework) { data: component, componentProgram: componentId, authority: framework.provider.wallet.publicKey, + buffer: FindBufferPda(framework.provider.wallet.publicKey), cpiAuth: FindCpiAuthPda(), }) .instruction(); diff --git a/clients/typescript/test/low-level/session.ts b/clients/typescript/test/low-level/session.ts index 4efd2b7..64e7b66 100644 --- a/clients/typescript/test/low-level/session.ts +++ b/clients/typescript/test/low-level/session.ts @@ -75,6 +75,7 @@ export function session(framework) { data: component, componentProgram: componentId, authority: framework.worldProgram.programId, + buffer: FindBufferPda(sessionSigner.publicKey), cpiAuth: FindCpiAuthPda(), }) .instruction(); diff --git a/clients/typescript/test/main.ts b/clients/typescript/test/main.ts index f119121..247672e 100644 --- a/clients/typescript/test/main.ts +++ b/clients/typescript/test/main.ts @@ -1,2 +1,2 @@ import "./low-level"; -import "./intermediate-level"; +// import "./intermediate-level"; diff --git a/crates/programs/world/src/lib.rs b/crates/programs/world/src/lib.rs index 03f1334..6d31d0c 100644 --- a/crates/programs/world/src/lib.rs +++ b/crates/programs/world/src/lib.rs @@ -264,6 +264,34 @@ pub mod world { return Err(WorldError::InvalidAuthority.into()); } bolt_component::cpi::initialize(ctx.accounts.build(&[World::cpi_auth_seeds().as_slice()]))?; + + create_buffer_if_needed(&ctx.accounts.buffer, &ctx.accounts.payer, &ctx.accounts.system_program, &[&[b"buffer", ctx.accounts.authority.key.as_ref(), &[ctx.bumps.buffer]]])?; + { + ctx.accounts.buffer.realloc(ctx.accounts.data.data_len(), false)?; + let mut data = ctx.accounts.buffer.try_borrow_mut_data()?; + data.copy_from_slice(ctx.accounts.data.try_borrow_data()?.as_ref()); + } + + bolt_component::cpi::set_owner( + CpiContext::new_with_signer( + ctx.accounts.component_program.to_account_info(), + bolt_component::cpi::accounts::SetOwner { + cpi_auth: ctx.accounts.cpi_auth.to_account_info(), + component: ctx.accounts.data.to_account_info(), + }, + &[World::cpi_auth_seeds().as_slice()], + ), + ID + )?; + + { + ctx.accounts.data.realloc(ctx.accounts.buffer.data_len(), false)?; + let mut data = ctx.accounts.data.try_borrow_mut_data()?; + data.copy_from_slice(ctx.accounts.buffer.try_borrow_data()?.as_ref()); + } + + ctx.accounts.buffer.realloc(0, false)?; + Ok(()) } @@ -288,8 +316,7 @@ pub mod world { &ctx.accounts.system_program, None, ctx.remaining_accounts, - )?; - Ok(()) + ) } #[derive(Accounts)] @@ -377,6 +404,33 @@ pub mod world { } } +fn create_buffer_if_needed<'info>( + buffer: &AccountInfo<'info>, + authority: &Signer<'info>, + system_program: &Program<'info, System>, + seeds: &[&[&[u8]]], +) -> Result<()> { + 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(), + }, + seeds, + ), + lamports, + size as u64, + &ID, + )?; + } + + Ok(()) +} + #[allow(clippy::type_complexity, clippy::too_many_arguments)] fn apply_impl<'info>( buffer: &AccountInfo<'info>, @@ -451,28 +505,9 @@ fn apply_impl<'info>( } } - // 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(), - }, - &[&[b"buffer", authority.key.as_ref(), &[buffer_bump]]], - ), - lamports, - size as u64, - &ID, - )?; - } + create_buffer_if_needed(buffer, authority, system_program, &[&[b"buffer", authority.key.as_ref(), &[buffer_bump]]])?; - for pair in remaining_accounts[..index].chunks(2) { - let [program, component] = pair else { continue }; + for component in remaining_accounts[..index].iter().skip(1).step_by(2) { buffer.realloc(component.data_len(), false)?; { let mut data = buffer.try_borrow_mut_data()?; @@ -480,18 +515,8 @@ fn apply_impl<'info>( } if component.owner != bolt_system.key { - bolt_component::cpi::set_owner( - CpiContext::new_with_signer( - program.to_account_info(), - bolt_component::cpi::accounts::SetOwner { - cpi_auth: cpi_auth.to_account_info(), - component: component.to_account_info(), - }, - &[World::cpi_auth_seeds().as_slice()], - ), - *bolt_system.key, - )?; - + component.realloc(0, false)?; + component.assign(bolt_system.key); bolt_system::cpi::set_data(CpiContext::new_with_signer( bolt_system.to_account_info(), bolt_system::cpi::accounts::SetData { @@ -510,15 +535,14 @@ fn apply_impl<'info>( args, )?; - for pair in remaining_accounts[..index].chunks(2) { - let [program, component] = pair else { continue }; - buffer.realloc(component.data_len(), false)?; - { - let mut data = buffer.try_borrow_mut_data()?; - data.copy_from_slice(component.try_borrow_data()?.as_ref()); - } - - if *component.owner != program.key() { + for component in remaining_accounts[..index].iter().skip(1).step_by(2) { + if *component.owner != ID { + { + buffer.realloc(component.data_len(), false)?; + let mut data = buffer.try_borrow_mut_data()?; + data.copy_from_slice(component.try_borrow_data()?.as_ref()); + } + bolt_system::cpi::set_owner( CpiContext::new_with_signer( bolt_system.to_account_info(), @@ -528,22 +552,16 @@ fn apply_impl<'info>( }, &[World::cpi_auth_seeds().as_slice()], ), - program.key(), + ID, )?; - if *component.owner != program.key() { - return Err(WorldError::InvalidComponentOwner.into()); - } + require_eq!(*component.owner, ID, WorldError::InvalidComponentOwner); - bolt_component::cpi::set_data(CpiContext::new_with_signer( - program.to_account_info(), - bolt_component::cpi::accounts::SetData { - cpi_auth: cpi_auth.to_account_info(), - buffer: buffer.to_account_info(), - component: component.to_account_info(), - }, - &[World::cpi_auth_seeds().as_slice()], - ))?; + { + component.realloc(buffer.data_len(), false)?; + let mut data = component.try_borrow_mut_data()?; + data.copy_from_slice(buffer.try_borrow_data()?.as_ref()); + } } } @@ -644,6 +662,9 @@ pub struct AddEntity<'info> { pub struct InitializeComponent<'info> { #[account(mut)] pub payer: Signer<'info>, + /// CHECK: buffer data check + #[account(mut, seeds = [b"buffer", authority.key.as_ref()], bump)] + pub buffer: AccountInfo<'info>, #[account(mut)] /// CHECK: component data check pub data: AccountInfo<'info>,