Răsfoiți Sursa

:sparkles: Macro to define and access extra accounts (#26)

* :sparkles: Forward remaining accounts

* :sparkles: Propagate remaining accounts to all apply* instructions

* :sparkles: Add macro for defining extrac accounts and inject helper functions

* :recycle: Code Refactoring

* :sparkles: Inject extra account init fn with th system macro

Inject extra account init fn with th system macro, to generate a correct idl wich contains also the extra accounts
Gabriele Picco 1 an în urmă
părinte
comite
68b3c0aef1

+ 1 - 0
.github/workflows/publish-bolt-crates.yml

@@ -196,6 +196,7 @@ jobs:
           cargo publish $DRY_RUN_FLAG --manifest-path=crates/bolt-lang/attribute/component-id/Cargo.toml --token $CRATES_TOKEN $NO_VERIFY_FLAG
           cargo publish $DRY_RUN_FLAG --manifest-path=crates/bolt-lang/attribute/system/Cargo.toml --token $CRATES_TOKEN $NO_VERIFY_FLAG
           cargo publish $DRY_RUN_FLAG --manifest-path=crates/bolt-lang/attribute/system-input/Cargo.toml --token $CRATES_TOKEN $NO_VERIFY_FLAG
+          cargo publish $DRY_RUN_FLAG --manifest-path=crates/bolt-lang/attribute/extra-accounts/Cargo.toml --token $CRATES_TOKEN $NO_VERIFY_FLAG
           cargo publish $DRY_RUN_FLAG --manifest-path=crates/bolt-lang/attribute/bolt-program/Cargo.toml --token $CRATES_TOKEN $NO_VERIFY_FLAG
           cargo publish $DRY_RUN_FLAG --manifest-path=crates/bolt-helpers/attribute/system-template/Cargo.toml --token $CRATES_TOKEN $NO_VERIFY_FLAG
           cargo publish $DRY_RUN_FLAG --manifest-path=crates/bolt-helpers/attribute/world-apply/Cargo.toml --token $CRATES_TOKEN $NO_VERIFY_FLAG

+ 74 - 36
Cargo.lock

@@ -126,9 +126,9 @@ dependencies = [
 [[package]]
 name = "anchor-attribute-access-control"
 version = "0.29.0"
-source = "git+https://github.com/coral-xyz/anchor.git#169264d730ffeae16c12a33c0c353b7d3a461532"
+source = "git+https://github.com/coral-xyz/anchor.git?rev=e212105#e21210538d0415674c5c474ec36b8dca066ea62e"
 dependencies = [
- "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
+ "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
  "proc-macro2",
  "quote",
  "syn 1.0.109",
@@ -150,9 +150,9 @@ dependencies = [
 [[package]]
 name = "anchor-attribute-account"
 version = "0.29.0"
-source = "git+https://github.com/coral-xyz/anchor.git#169264d730ffeae16c12a33c0c353b7d3a461532"
+source = "git+https://github.com/coral-xyz/anchor.git?rev=e212105#e21210538d0415674c5c474ec36b8dca066ea62e"
 dependencies = [
- "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
+ "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
  "bs58 0.5.0",
  "proc-macro2",
  "quote",
@@ -173,9 +173,9 @@ dependencies = [
 [[package]]
 name = "anchor-attribute-constant"
 version = "0.29.0"
-source = "git+https://github.com/coral-xyz/anchor.git#169264d730ffeae16c12a33c0c353b7d3a461532"
+source = "git+https://github.com/coral-xyz/anchor.git?rev=e212105#e21210538d0415674c5c474ec36b8dca066ea62e"
 dependencies = [
- "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
+ "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
  "quote",
  "syn 1.0.109",
 ]
@@ -194,9 +194,9 @@ dependencies = [
 [[package]]
 name = "anchor-attribute-error"
 version = "0.29.0"
-source = "git+https://github.com/coral-xyz/anchor.git#169264d730ffeae16c12a33c0c353b7d3a461532"
+source = "git+https://github.com/coral-xyz/anchor.git?rev=e212105#e21210538d0415674c5c474ec36b8dca066ea62e"
 dependencies = [
- "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
+ "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
  "quote",
  "syn 1.0.109",
 ]
@@ -216,9 +216,9 @@ dependencies = [
 [[package]]
 name = "anchor-attribute-event"
 version = "0.29.0"
-source = "git+https://github.com/coral-xyz/anchor.git#169264d730ffeae16c12a33c0c353b7d3a461532"
+source = "git+https://github.com/coral-xyz/anchor.git?rev=e212105#e21210538d0415674c5c474ec36b8dca066ea62e"
 dependencies = [
- "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
+ "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
  "proc-macro2",
  "quote",
  "syn 1.0.109",
@@ -238,9 +238,9 @@ dependencies = [
 [[package]]
 name = "anchor-attribute-program"
 version = "0.29.0"
-source = "git+https://github.com/coral-xyz/anchor.git#169264d730ffeae16c12a33c0c353b7d3a461532"
+source = "git+https://github.com/coral-xyz/anchor.git?rev=e212105#e21210538d0415674c5c474ec36b8dca066ea62e"
 dependencies = [
- "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
+ "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
  "quote",
  "syn 1.0.109",
 ]
@@ -248,11 +248,11 @@ dependencies = [
 [[package]]
 name = "anchor-cli"
 version = "0.29.0"
-source = "git+https://github.com/coral-xyz/anchor.git#169264d730ffeae16c12a33c0c353b7d3a461532"
+source = "git+https://github.com/coral-xyz/anchor.git?rev=e212105#e21210538d0415674c5c474ec36b8dca066ea62e"
 dependencies = [
  "anchor-client",
- "anchor-lang 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
- "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
+ "anchor-lang 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
+ "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
  "anyhow",
  "base64 0.21.5",
  "bincode",
@@ -285,9 +285,9 @@ dependencies = [
 [[package]]
 name = "anchor-client"
 version = "0.29.0"
-source = "git+https://github.com/coral-xyz/anchor.git#169264d730ffeae16c12a33c0c353b7d3a461532"
+source = "git+https://github.com/coral-xyz/anchor.git?rev=e212105#e21210538d0415674c5c474ec36b8dca066ea62e"
 dependencies = [
- "anchor-lang 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
+ "anchor-lang 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
  "anyhow",
  "futures",
  "regex",
@@ -314,9 +314,9 @@ dependencies = [
 [[package]]
 name = "anchor-derive-accounts"
 version = "0.29.0"
-source = "git+https://github.com/coral-xyz/anchor.git#169264d730ffeae16c12a33c0c353b7d3a461532"
+source = "git+https://github.com/coral-xyz/anchor.git?rev=e212105#e21210538d0415674c5c474ec36b8dca066ea62e"
 dependencies = [
- "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
+ "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
  "quote",
  "syn 1.0.109",
 ]
@@ -337,10 +337,10 @@ dependencies = [
 [[package]]
 name = "anchor-derive-serde"
 version = "0.29.0"
-source = "git+https://github.com/coral-xyz/anchor.git#169264d730ffeae16c12a33c0c353b7d3a461532"
+source = "git+https://github.com/coral-xyz/anchor.git?rev=e212105#e21210538d0415674c5c474ec36b8dca066ea62e"
 dependencies = [
- "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
- "borsh-derive-internal 0.10.3",
+ "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
+ "borsh-derive-internal 0.9.3",
  "proc-macro2",
  "quote",
  "syn 1.0.109",
@@ -360,7 +360,7 @@ dependencies = [
 [[package]]
 name = "anchor-derive-space"
 version = "0.29.0"
-source = "git+https://github.com/coral-xyz/anchor.git#169264d730ffeae16c12a33c0c353b7d3a461532"
+source = "git+https://github.com/coral-xyz/anchor.git?rev=e212105#e21210538d0415674c5c474ec36b8dca066ea62e"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -396,28 +396,42 @@ dependencies = [
 [[package]]
 name = "anchor-lang"
 version = "0.29.0"
-source = "git+https://github.com/coral-xyz/anchor.git#169264d730ffeae16c12a33c0c353b7d3a461532"
+source = "git+https://github.com/coral-xyz/anchor.git?rev=e212105#e21210538d0415674c5c474ec36b8dca066ea62e"
 dependencies = [
  "ahash 0.8.6",
- "anchor-attribute-access-control 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
- "anchor-attribute-account 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
- "anchor-attribute-constant 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
- "anchor-attribute-error 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
- "anchor-attribute-event 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
- "anchor-attribute-program 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
- "anchor-derive-accounts 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
- "anchor-derive-serde 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
- "anchor-derive-space 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
+ "anchor-attribute-access-control 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
+ "anchor-attribute-account 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
+ "anchor-attribute-constant 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
+ "anchor-attribute-error 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
+ "anchor-attribute-event 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
+ "anchor-attribute-program 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
+ "anchor-derive-accounts 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
+ "anchor-derive-serde 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
+ "anchor-derive-space 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
  "arrayref",
  "base64 0.21.5",
  "bincode",
- "borsh 0.10.3",
+ "borsh 0.9.3",
  "bytemuck",
  "getrandom 0.2.10",
  "solana-program",
  "thiserror",
 ]
 
+[[package]]
+name = "anchor-spl"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c4fd6e43b2ca6220d2ef1641539e678bfc31b6cc393cf892b373b5997b6a39a"
+dependencies = [
+ "anchor-lang 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mpl-token-metadata",
+ "solana-program",
+ "spl-associated-token-account",
+ "spl-token",
+ "spl-token-2022",
+]
+
 [[package]]
 name = "anchor-syn"
 version = "0.29.0"
@@ -439,7 +453,7 @@ dependencies = [
 [[package]]
 name = "anchor-syn"
 version = "0.29.0"
-source = "git+https://github.com/coral-xyz/anchor.git#169264d730ffeae16c12a33c0c353b7d3a461532"
+source = "git+https://github.com/coral-xyz/anchor.git?rev=e212105#e21210538d0415674c5c474ec36b8dca066ea62e"
 dependencies = [
  "anyhow",
  "bs58 0.5.0",
@@ -937,6 +951,15 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "bolt-attribute-bolt-extra-accounts"
+version = "0.1.1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "bolt-attribute-bolt-program"
 version = "0.1.1"
@@ -970,7 +993,7 @@ version = "0.1.1"
 dependencies = [
  "anchor-cli",
  "anchor-client",
- "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
+ "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git?rev=e212105)",
  "anyhow",
  "clap 4.4.11",
  "heck 0.4.1",
@@ -1017,6 +1040,7 @@ dependencies = [
  "bolt-attribute-bolt-component",
  "bolt-attribute-bolt-component-deserialize",
  "bolt-attribute-bolt-component-id",
+ "bolt-attribute-bolt-extra-accounts",
  "bolt-attribute-bolt-program",
  "bolt-attribute-bolt-system",
  "bolt-attribute-bolt-system-input",
@@ -2712,6 +2736,19 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "mpl-token-metadata"
+version = "3.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba8ee05284d79b367ae8966d558e1a305a781fc80c9df51f37775169117ba64f"
+dependencies = [
+ "borsh 0.9.3",
+ "num-derive 0.3.3",
+ "num-traits",
+ "solana-program",
+ "thiserror",
+]
+
 [[package]]
 name = "new_debug_unreachable"
 version = "1.0.4"
@@ -5037,6 +5074,7 @@ name = "system-apply-velocity"
 version = "0.1.1"
 dependencies = [
  "anchor-lang 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "anchor-spl",
  "bolt-lang",
  "position",
  "velocity",

+ 2 - 0
Cargo.toml

@@ -19,6 +19,7 @@ bolt-attribute-bolt-program = { path = "crates/bolt-lang/attribute/bolt-program"
 bolt-attribute-bolt-component = { path = "crates/bolt-lang/attribute/component", version = "=0.1.1" }
 bolt-attribute-bolt-system = { path = "crates/bolt-lang/attribute/system", version = "=0.1.1"}
 bolt-attribute-bolt-system-input = { path = "crates/bolt-lang/attribute/system-input", version = "=0.1.1" }
+bolt-attribute-bolt-extra-accounts = { path = "crates/bolt-lang/attribute/extra-accounts", version = "=0.1.1" }
 bolt-attribute-bolt-component-deserialize = { path = "crates/bolt-lang/attribute/component-deserialize", version = "=0.1.1" }
 bolt-attribute-bolt-component-id = { path = "crates/bolt-lang/attribute/component-id", version = "=0.1.1" }
 bolt-helpers-system-template = { path = "crates/bolt-helpers/attribute/system-template", version = "=0.1.1" }
@@ -30,6 +31,7 @@ bolt-component = { path = "programs/bolt-component", features = ["cpi"], version
 
 ## External crates
 anchor-lang = "0.29.0"
+anchor-spl = "0.29.0"
 solana-security-txt = "1.1.1"
 tuple-conv = "1.0.1"
 syn = { version = "1.0.60", features = ["full"] }

+ 3 - 3
cli/Cargo.toml

@@ -21,9 +21,9 @@ lto = true
 dev = []
 
 [dependencies]
-anchor-cli = { git = "https://github.com/coral-xyz/anchor.git" }
-anchor-client = { git = "https://github.com/coral-xyz/anchor.git" }
-anchor-syn = { git = "https://github.com/coral-xyz/anchor.git" }
+anchor-cli = { git = "https://github.com/coral-xyz/anchor.git", rev = "e212105" }
+anchor-client = { git = "https://github.com/coral-xyz/anchor.git", rev = "e212105" }
+anchor-syn = { git = "https://github.com/coral-xyz/anchor.git", rev = "e212105" }
 anyhow = { workspace = true }
 serde_json = { workspace = true }
 heck = { workspace = true }

+ 9 - 6
crates/bolt-helpers/attribute/world-apply/src/lib.rs

@@ -39,11 +39,15 @@ pub fn apply_system(attr: TokenStream, item: TokenStream) -> TokenStream {
         });
 
         quote! {
-        pub fn #apply_func_name(ctx: Context<#data_struct>, args: Vec<u8>) -> Result<()> {
+        pub fn #apply_func_name<'info>(ctx: Context<'_, '_, '_, 'info, #data_struct<'info>>, args: Vec<u8>) -> Result<()> {
             if !ctx.accounts.authority.is_signer && ctx.accounts.authority.key != &ID {
                 return Err(WorldError::InvalidAuthority.into());
             }
-            let res = bolt_system::cpi::#execute_func_name(ctx.accounts.build(), args)?.get().to_vec();
+            let remaining_accounts: Vec<AccountInfo<'info>> = ctx.remaining_accounts.to_vec();
+            let res = bolt_system::cpi::#execute_func_name(
+                    ctx.accounts
+                    .build()
+                    .with_remaining_accounts(remaining_accounts),args)?.get().to_vec();
             #(#updates)*
             Ok(())
         }
@@ -136,12 +140,11 @@ struct SystemTemplateInput {
     max_components: usize,
 }
 
-// Implement parsing for the macro input
 impl Parse for SystemTemplateInput {
     fn parse(input: ParseStream) -> Result<Self> {
-        let _ = input.parse::<Ident>()?; // Parse the key (e.g., "max_components")
-        let _ = input.parse::<Token![=]>()?; // Parse the '='
-        let max_components: LitInt = input.parse()?; // Parse the value
+        let _ = input.parse::<Ident>()?;
+        let _ = input.parse::<Token![=]>()?;
+        let max_components: LitInt = input.parse()?;
         let max_value = max_components.base10_parse()?;
         Ok(SystemTemplateInput {
             max_components: max_value,

+ 1 - 0
crates/bolt-lang/Cargo.toml

@@ -18,6 +18,7 @@ bolt-attribute-bolt-system = { workspace = true }
 bolt-attribute-bolt-system-input = { workspace = true }
 bolt-attribute-bolt-component-deserialize = { workspace = true }
 bolt-attribute-bolt-component-id = { workspace = true }
+bolt-attribute-bolt-extra-accounts = { workspace = true }
 
 # Bolt Programs
 world = { workspace = true }

+ 17 - 0
crates/bolt-lang/attribute/extra-accounts/Cargo.toml

@@ -0,0 +1,17 @@
+[package]
+name = "bolt-attribute-bolt-extra-accounts"
+description = "Bolt attribute-bolt-extra-accounts"
+version = { workspace = true }
+authors = { workspace = true }
+repository = { workspace = true }
+homepage = { workspace = true }
+license = { workspace = true }
+edition = { workspace = true }
+
+[lib]
+proc-macro = true
+
+[dependencies]
+syn = { workspace = true, features = ["visit-mut"] }
+quote = { workspace = true }
+proc-macro2 = { workspace = true }

+ 106 - 0
crates/bolt-lang/attribute/extra-accounts/src/lib.rs

@@ -0,0 +1,106 @@
+use proc_macro::TokenStream;
+
+use quote::quote;
+use syn::{parse_macro_input, Fields, ItemStruct, LitStr};
+
+/// This macro attribute is used to define a BOLT system extra accounts.
+///
+/// The extra account struct define the accounts (which are not components), that the system expect.
+///
+///
+/// # Example
+/// ```ignore
+///#[extra_accounts]
+///pub struct ExtraAccounts {
+/// #[account(address = bolt_lang::solana_program::sysvar::clock::id())]
+/// pub clock: AccountInfo<'info>,
+/// #[account(address = pubkey!("CbHEFbSQdRN4Wnoby9r16umnJ1zWbULBHg4yqzGQonU1"), signer)]
+/// pub my_account: AccountInfo<'info>,
+/// #[account(address = Metadata::id())]
+/// pub metadata_program: Program<'info, Metadata>,
+///}
+///
+/// ```
+#[proc_macro_attribute]
+pub fn extra_accounts(_attr: TokenStream, item: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(item as ItemStruct);
+    let extra_accounts_struct_name = &input.ident;
+
+    // Ensure the struct has named fields
+    let fields = match &input.fields {
+        Fields::Named(fields) => &fields.named,
+        _ => panic!("extra_accounts macro only supports structs with named fields"),
+    };
+
+    // Transform fields for the struct definition
+    let transformed_fields = fields.iter().map(|f| {
+        let field_name = &f.ident;
+        let attrs = &f.attrs;
+        quote! {
+            #(#attrs)*
+            pub #field_name: AccountInfo<'info>,
+        }
+    });
+
+    // Generate the new struct with the Accounts derive and transformed fields
+    let output_struct = quote! {
+        #[derive(Accounts)]
+        pub struct #extra_accounts_struct_name<'info> {
+            #(#transformed_fields)*
+        }
+    };
+
+    // Generate the trait for the helper functions
+    let helper_functions = fields.iter().map(|f| {
+        let field_name = &f.ident;
+        quote! {
+            fn #field_name(&self) -> Result<&'c AccountInfo<'info>>;
+        }
+    });
+
+    let output_trait = quote! {
+        pub trait ContextExtensions<'a, 'b, 'c, 'info, T>
+        {
+            #(#helper_functions)*
+        }
+    };
+
+    // Generate the helper functions for the struct
+    let helper_functions_impl = fields.iter().enumerate().map(|(index, f)| {
+        let field_name = &f.ident;
+        let index = syn::Index::from(index); // Create a compile-time index representation
+        quote! {
+            fn #field_name(&self) -> Result<&'c AccountInfo<'info>> {
+                self.remaining_accounts.get(#index).ok_or_else(|| ErrorCode::ConstraintAccountIsNone.into())
+            }
+        }
+    });
+
+    let output_trait_implementation = quote! {
+        impl<'a, 'b, 'c, 'info, T: bolt_lang::Bumps> ContextExtensions<'a, 'b, 'c, 'info, T> for Context<'a, 'b, 'c, 'info, T> {
+            #(#helper_functions_impl)*
+        }
+    };
+
+    // Combine the struct definition and its implementation into the final TokenStream
+    let output = quote! {
+        #output_struct
+        #output_trait
+        #output_trait_implementation
+    };
+
+    TokenStream::from(output)
+}
+
+#[proc_macro]
+pub fn pubkey(input: TokenStream) -> TokenStream {
+    let input_lit_str = parse_macro_input!(input as LitStr);
+    let pubkey_str = input_lit_str.value();
+
+    // Example using solana_program to create a Pubkey from a string
+    let expanded = quote! {
+        bolt_lang::pubkey_from_str(#pubkey_str)
+    };
+
+    TokenStream::from(expanded)
+}

+ 32 - 4
crates/bolt-lang/attribute/system/src/lib.rs

@@ -119,13 +119,41 @@ impl VisitMut for SystemTransform {
         }
     }
 
-    // Visit all the functions inside the system module
+    // Visit all the functions inside the system module and inject the init_extra_accounts function
+    // if the module contains a struct with the `extra_accounts` attribute
     fn visit_item_mod_mut(&mut self, item_mod: &mut ItemMod) {
-        for item in &mut item_mod.content.as_mut().unwrap().1 {
-            if let syn::Item::Fn(item_fn) = item {
-                self.visit_item_fn_mut(item_fn)
+        let content = match item_mod.content.as_mut() {
+            Some(content) => &mut content.1,
+            None => return,
+        };
+
+        let mut extra_accounts_struct_name = None;
+
+        for item in content.iter_mut() {
+            match item {
+                syn::Item::Fn(item_fn) => self.visit_item_fn_mut(item_fn),
+                syn::Item::Struct(item_struct)
+                    if item_struct
+                        .attrs
+                        .iter()
+                        .any(|attr| attr.path.is_ident("extra_accounts")) =>
+                {
+                    extra_accounts_struct_name = Some(&item_struct.ident);
+                    break;
+                }
+                _ => {}
             }
         }
+
+        if let Some(struct_name) = extra_accounts_struct_name {
+            let initialize_extra_accounts = quote! {
+            #[automatically_derived]
+                pub fn init_extra_accounts(_ctx: Context<#struct_name>) -> Result<()> {
+                    Ok(())
+                }
+            };
+            content.push(syn::parse2(initialize_extra_accounts).unwrap());
+        }
     }
 }
 

+ 14 - 1
crates/bolt-lang/src/lib.rs

@@ -1,12 +1,14 @@
 pub use anchor_lang::error::ErrorCode::AccountDidNotDeserialize as AccountDidNotDeserializeErrorCode;
 pub use anchor_lang::prelude::*;
 pub use anchor_lang::{
-    AccountDeserialize, AccountSerialize, AnchorDeserialize, AnchorSerialize, Result,
+    AccountDeserialize, AccountSerialize, AnchorDeserialize, AnchorSerialize, Bumps, Result,
 };
 
 pub use bolt_attribute_bolt_component::component;
 pub use bolt_attribute_bolt_component_deserialize::component_deserialize;
 pub use bolt_attribute_bolt_component_id::component_id;
+pub use bolt_attribute_bolt_extra_accounts::extra_accounts;
+pub use bolt_attribute_bolt_extra_accounts::pubkey;
 pub use bolt_attribute_bolt_program::bolt_program;
 pub use bolt_attribute_bolt_system::system;
 pub use bolt_attribute_bolt_system_input::system_input;
@@ -20,10 +22,16 @@ pub use serde;
 pub use serde::{Deserialize as BoltDeserialize, Serialize as BoltSerialize};
 
 use std::str;
+use std::str::FromStr;
 
 mod errors;
 pub use crate::errors::BoltError;
 
+/// Export of the solana_program crate.
+pub mod solana_program {
+    pub use anchor_lang::solana_program::*;
+}
+
 /// Parses the arguments from a byte array.
 pub fn parse_args<T: serde::de::DeserializeOwned>(args_p: &[u8]) -> T {
     let args_string = str::from_utf8(args_p).expect("Failed to convert to string");
@@ -52,3 +60,8 @@ pub trait ComponentDeserialize: Sized {
 pub struct BoltMetadata {
     pub authority: Pubkey,
 }
+
+/// Wrapper method to create a pubkey from a string
+pub fn pubkey_from_str(s: &str) -> solana_program::pubkey::Pubkey {
+    solana_program::pubkey::Pubkey::from_str(s).unwrap()
+}

+ 1 - 0
examples/system-apply-velocity/Cargo.toml

@@ -22,6 +22,7 @@ idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { workspace = true }
+anchor-spl = { workspace = true, features = ["metadata"]}
 bolt-lang = { path = "../../crates/bolt-lang" }
 velocity = { path = "../component-velocity", features = ["cpi"]}
 position = { path = "../component-position", features = ["cpi"]}

+ 16 - 1
examples/system-apply-velocity/src/lib.rs

@@ -1,3 +1,4 @@
+use anchor_spl::metadata::Metadata;
 use bolt_lang::*;
 use position::Position;
 use velocity::Velocity;
@@ -9,7 +10,11 @@ pub mod system_apply_velocity {
 
     pub fn execute(ctx: Context<Components>, _args: Vec<u8>) -> Result<Components> {
         ctx.accounts.velocity.x = 10;
-        let clock = Clock::get()?;
+        let mut clock = Clock::get()?;
+        if let Ok(clock_account_info) = ctx.sysvar_clock() {
+            clock = Clock::from_account_info(clock_account_info)?;
+            ctx.accounts.position.z = 300;
+        }
         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);
@@ -23,4 +28,14 @@ pub mod system_apply_velocity {
         pub velocity: Velocity,
         pub position: Position,
     }
+
+    #[extra_accounts]
+    pub struct ExtraAccounts {
+        #[account(address = bolt_lang::solana_program::sysvar::clock::id())]
+        pub sysvar_clock: AccountInfo,
+        #[account(address = pubkey!("6LHhFVwif6N9Po3jHtSmMVtPjF6zRfL3xMosSzcrQAS8"))]
+        pub some_extra_account: AccountInfo,
+        #[account(address = Metadata::id())]
+        pub program_metadata: Program<Metadata>,
+    }
 }

+ 12 - 2
programs/world/src/lib.rs

@@ -50,11 +50,21 @@ pub mod world {
         Ok(())
     }
 
-    pub fn apply(ctx: Context<ApplySystem>, args: Vec<u8>) -> Result<()> {
+    pub fn apply<'info>(
+        ctx: Context<'_, '_, '_, 'info, ApplySystem<'info>>,
+        args: Vec<u8>,
+    ) -> Result<()> {
         if !ctx.accounts.authority.is_signer && ctx.accounts.authority.key != &ID {
             return Err(WorldError::InvalidAuthority.into());
         }
-        let res = bolt_system::cpi::execute(ctx.accounts.build(), args)?;
+        let remaining_accounts: Vec<AccountInfo<'info>> = ctx.remaining_accounts.to_vec();
+        let res = bolt_system::cpi::execute(
+            ctx.accounts
+                .build()
+                .with_remaining_accounts(remaining_accounts),
+            args,
+        )?;
+
         bolt_component::cpi::update(
             build_update_context(
                 ctx.accounts.component_program.clone(),

+ 33 - 12
tests/bolt.ts

@@ -1,5 +1,5 @@
 import * as anchor from "@coral-xyz/anchor";
-import { type Program } from "@coral-xyz/anchor";
+import { type Program, web3 } from "@coral-xyz/anchor";
 import { type PublicKey } from "@solana/web3.js";
 import { type Position } from "../target/types/position";
 import { type Velocity } from "../target/types/velocity";
@@ -229,8 +229,6 @@ describe("bolt", () => {
       entity1
     );
 
-    console.log("Component Position E1: ", componentPositionEntity1.toBase58());
-
     await worldProgram.methods
       .initializeComponent()
       .accounts({
@@ -478,17 +476,8 @@ describe("bolt", () => {
         instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
         authority: worldProgram.programId,
       })
-      .remainingAccounts([
-        {
-          pubkey: componentPositionEntity1,
-          isWritable: false,
-          isSigner: false,
-        },
-      ])
       .rpc();
 
-    console.log("Component Velocity: ", componentVelocityEntity1.toBase58());
-
     const componentData =
       await boltComponentVelocityProgram.account.velocity.fetch(
         componentVelocityEntity1
@@ -533,6 +522,38 @@ describe("bolt", () => {
     console.log("+----------------+------------+");
     console.log("|                             |");
     console.log("+-----------------------------+");
+    expect(positionData.z.toNumber()).to.not.equal(300);
+  });
+
+  it("Apply Velocity on Entity 1, with Clock external account", async () => {
+    await worldProgram.methods
+      .apply2(Buffer.alloc(0))
+      .accounts({
+        componentProgram1: boltComponentVelocityProgram.programId,
+        componentProgram2: boltComponentPositionProgram.programId,
+        boltSystem: applyVelocity,
+        boltComponent1: componentVelocityEntity1,
+        boltComponent2: componentPositionEntity1,
+        instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
+        authority: worldProgram.programId,
+      })
+      .remainingAccounts([
+        {
+          pubkey: new web3.PublicKey(
+            "SysvarC1ock11111111111111111111111111111111"
+          ),
+          isWritable: false,
+          isSigner: false,
+        },
+      ])
+      .rpc();
+
+    const positionData =
+      await boltComponentPositionProgram.account.position.fetch(
+        componentPositionEntity1
+      );
+    // Check if the position has changed to 300 (which means the account clock was used)
+    expect(positionData.z.toNumber()).to.equal(300);
   });
 
   // Check illegal authority usage