Browse Source

:sparkles: Simplify API & Fix IDL generation

Gabriele Picco 1 year ago
parent
commit
ef647d0032

+ 1 - 1
Anchor.toml

@@ -1,7 +1,7 @@
 [toolchain]
 
 [features]
-seeds = false
+seeds = true
 skip-lint = false
 
 [programs.localnet]

+ 16 - 4
Cargo.lock

@@ -382,6 +382,7 @@ dependencies = [
  "anchor-derive-accounts 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "anchor-derive-serde 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "anchor-derive-space 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "anchor-syn 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "arrayref",
  "base64 0.13.1",
  "bincode",
@@ -908,25 +909,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
 
 [[package]]
-name = "bolt-attribute-bolt-account"
+name = "bolt-attribute-bolt-component"
 version = "0.0.1"
 dependencies = [
+ "bolt-utils",
  "proc-macro2",
  "quote",
  "syn 1.0.109",
 ]
 
 [[package]]
-name = "bolt-attribute-bolt-component"
+name = "bolt-attribute-bolt-component-deserialize"
 version = "0.0.1"
 dependencies = [
+ "bolt-utils",
  "proc-macro2",
  "quote",
  "syn 1.0.109",
 ]
 
 [[package]]
-name = "bolt-attribute-bolt-component-deserialize"
+name = "bolt-attribute-bolt-program"
 version = "0.0.1"
 dependencies = [
  "proc-macro2",
@@ -973,9 +976,9 @@ version = "0.0.1"
 dependencies = [
  "ahash 0.8.6",
  "anchor-lang 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "bolt-attribute-bolt-account",
  "bolt-attribute-bolt-component",
  "bolt-attribute-bolt-component-deserialize",
+ "bolt-attribute-bolt-program",
  "bolt-attribute-bolt-system",
  "bolt-system",
  "serde",
@@ -990,6 +993,15 @@ dependencies = [
  "anchor-lang 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "bolt-utils"
+version = "0.0.1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "borsh"
 version = "0.9.3"

+ 3 - 12
cli/src/rust_template.rs

@@ -46,14 +46,7 @@ fn create_component_template_simple(name: &str, program_path: &Path) -> Files {
 
 declare_id!("{}");
 
-#[component({})]
-#[program]
-pub mod {} {{
-    use super::*;
-}}
-
-#[account]
-#[bolt_account(component_id = "")]
+#[component]
 pub struct {} {{
     pub x: i64,
     pub y: i64,
@@ -64,8 +57,6 @@ pub struct {} {{
 "#,
             anchor_cli::rust_template::get_or_create_program_id(name),
             name.to_upper_camel_case(),
-            name.to_snake_case(),
-            name.to_upper_camel_case(),
         ),
     )]
 }
@@ -80,15 +71,14 @@ fn create_system_template_simple(name: &str, program_path: &Path) -> Files {
 declare_id!("{}");
 
 #[system]
-#[program]
 pub mod {} {{
-    use super::*;
 
     pub fn execute(ctx: Context<Component>, args: Vec<u8>) -> Result<Position> {{
         let mut position = Position::from_account_info(&ctx.accounts.position)?;
         position.x += 1;
         Ok(position)
     }}
+
 }}
 
 // Define the Account to parse from the component
@@ -436,6 +426,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 bolt-lang = "{2}"

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

@@ -9,8 +9,8 @@ license = "MIT"
 anchor-lang = { version = "0.29.0"}
 
 # Bolt Attributes
+bolt-attribute-bolt-program = { path = "./attribute/bolt-program", version = "0.0.1" }
 bolt-attribute-bolt-component = { path = "./attribute/component", version = "0.0.1" }
-bolt-attribute-bolt-account = { path = "./attribute/account", version = "0.0.1" }
 bolt-attribute-bolt-system = { path = "./attribute/system", version = "0.0.1" }
 bolt-attribute-bolt-component-deserialize = { path = "./attribute/component-deserialize", version = "0.0.1" }
 

+ 0 - 75
crates/bolt-lang/attribute/account/src/lib.rs

@@ -1,75 +0,0 @@
-use proc_macro::TokenStream;
-use quote::quote;
-use syn::{parse_macro_input, parse_quote, Attribute, DeriveInput, Lit, Meta, NestedMeta};
-
-/// This BoltAccount attribute is used to automatically generate the seed and size functions
-///
-/// The component_id define the seed used to generate the PDA which stores the component data.
-/// The macro also adds the InitSpace and Default derives to the struct.
-///
-/// #[account]
-/// #[bolt_account]
-/// pub struct Position {
-///     pub x: i64,
-///     pub y: i64,
-///     pub z: i64,
-/// }
-/// ```
-#[proc_macro_attribute]
-pub fn bolt_account(attr: TokenStream, item: TokenStream) -> TokenStream {
-    let mut input = parse_macro_input!(item as DeriveInput);
-    let mut component_id_value = None;
-
-    if !attr.is_empty() {
-        let attr_meta = parse_macro_input!(attr as Meta);
-
-        component_id_value = match attr_meta {
-            Meta::Path(_) => None,
-            Meta::NameValue(meta_name_value) if meta_name_value.path.is_ident("component_id") => {
-                if let Lit::Str(lit) = meta_name_value.lit {
-                    Some(lit.value())
-                } else {
-                    None
-                }
-            }
-            Meta::List(meta) => meta.nested.into_iter().find_map(|nested_meta| {
-                if let NestedMeta::Meta(Meta::NameValue(meta_name_value)) = nested_meta {
-                    if meta_name_value.path.is_ident("component_id") {
-                        if let Lit::Str(lit) = meta_name_value.lit {
-                            Some(lit.value())
-                        } else {
-                            None
-                        }
-                    } else {
-                        None
-                    }
-                } else {
-                    None
-                }
-            }),
-            _ => None,
-        };
-    }
-
-    let component_id_value = component_id_value.unwrap_or_else(|| "".to_string());
-
-    let additional_derives: Attribute = parse_quote! { #[derive(InitSpace, Default)] };
-    input.attrs.push(additional_derives);
-
-    let name = &input.ident;
-    let expanded = quote! {
-        #input
-
-        #[automatically_derived]
-        impl ComponentTraits for #name {
-            fn seed() -> &'static [u8] {
-                #component_id_value.as_bytes()
-            }
-
-            fn size() -> usize {
-                8 + <#name>::INIT_SPACE
-            }
-        }
-    };
-    expanded.into()
-}

+ 14 - 0
crates/bolt-lang/attribute/bolt-program/Cargo.toml

@@ -0,0 +1,14 @@
+[package]
+name = "bolt-attribute-bolt-program"
+version = "0.0.1"
+edition = "2021"
+description = "Bolt attribute-bolt-program"
+license = "MIT"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+syn = { version = "1.0", features = ["full"] }
+quote = "1.0"
+proc-macro2 = "1.0"

+ 166 - 0
crates/bolt-lang/attribute/bolt-program/src/lib.rs

@@ -0,0 +1,166 @@
+use proc_macro::TokenStream;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{quote, ToTokens};
+use syn::{
+    parse_macro_input, parse_quote, Attribute, AttributeArgs, Field, Fields, ItemMod, ItemStruct,
+    NestedMeta, Type,
+};
+
+/// This macro attribute is used to define a BOLT component.
+///
+/// Bolt components are themselves programs that can be called by other programs.
+///
+/// # Example
+/// ```ignore
+/// #[bolt_program(Position)]
+/// #[program]
+/// pub mod component_position {
+///     use super::*;
+/// }
+///
+/// #[account]
+/// #[component]
+/// pub struct Position {
+///     pub x: i64,
+///     pub y: i64,
+///     pub z: i64,
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn bolt_program(args: TokenStream, input: TokenStream) -> TokenStream {
+    let ast = parse_macro_input!(input as syn::ItemMod);
+    let args = parse_macro_input!(args as syn::AttributeArgs);
+    let component_type =
+        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] };
+    TokenStream::from(quote! {
+        #additional_macro
+        #modified
+    })
+}
+
+/// Modifies the component module and adds the necessary functions and structs.
+fn modify_component_module(mut module: ItemMod, component_type: &Type) -> ItemMod {
+    let (initialize_fn, initialize_struct) = generate_initialize(component_type);
+    //let (apply_fn, apply_struct, apply_impl, update_fn, update_struct) = generate_instructions(component_type);
+    let (update_fn, update_struct) = generate_update(component_type);
+
+    module.content = module.content.map(|(brace, mut items)| {
+        items.extend(
+            vec![initialize_fn, initialize_struct, update_fn, update_struct]
+                .into_iter()
+                .map(|item| syn::parse2(item).unwrap())
+                .collect::<Vec<_>>(),
+        );
+
+        let modified_items = items
+            .into_iter()
+            .map(|item| match item {
+                syn::Item::Struct(mut struct_item) if struct_item.ident == "Apply" => {
+                    modify_apply_struct(&mut struct_item);
+                    syn::Item::Struct(struct_item)
+                }
+                _ => item,
+            })
+            .collect();
+        (brace, modified_items)
+    });
+
+    module
+}
+
+/// Extracts the type name from attribute arguments.
+fn extract_type_name(args: &AttributeArgs) -> Option<Type> {
+    args.iter().find_map(|arg| {
+        if let NestedMeta::Meta(syn::Meta::Path(path)) = arg {
+            Some(Type::Path(syn::TypePath {
+                qself: None,
+                path: path.clone(),
+            }))
+        } else {
+            None
+        }
+    })
+}
+
+/// Modifies the Apply struct, change the bolt system to accept any compatible system.
+fn modify_apply_struct(struct_item: &mut ItemStruct) {
+    if let Fields::Named(fields_named) = &mut struct_item.fields {
+        fields_named
+            .named
+            .iter_mut()
+            .filter(|field| is_expecting_program(field))
+            .for_each(|field| {
+                field.ty = syn::parse_str("UncheckedAccount<'info>").expect("Failed to parse type");
+                field.attrs.push(create_check_attribute());
+            });
+    }
+}
+
+/// Creates the check attribute.
+fn create_check_attribute() -> Attribute {
+    parse_quote! {
+        #[doc = "CHECK: This program can modify the data of the component"]
+    }
+}
+
+/// Generates the initialize function and struct.
+fn generate_initialize(component_type: &Type) -> (TokenStream2, TokenStream2) {
+    (
+        quote! {
+            #[automatically_derived]
+            pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
+                ctx.accounts.data.set_inner(<#component_type>::default());
+                if let Some(authority) = &ctx.accounts.authority {
+                    if authority.key != ctx.accounts.payer.key {
+                        panic!("The authority does not match the payer.");
+                    }
+                    ctx.accounts.data.bolt_metadata.authority = *authority.key;
+                }
+                Ok(())
+            }
+        },
+        quote! {
+            #[automatically_derived]
+            #[derive(Accounts)]
+            pub struct Initialize<'info>  {
+                #[account(mut)]
+                pub payer: Signer<'info>,
+                #[account(init_if_needed, payer = payer, space = <#component_type>::size(), seeds = [<#component_type>::seed(), entity.key().as_ref()], bump)]
+                pub data: Account<'info, #component_type>,
+                #[account()]
+                pub entity: Account<'info, Entity>,
+                #[account()]
+                pub authority: Option<AccountInfo<'info>>,
+                pub system_program: Program<'info, System>,
+            }
+        },
+    )
+}
+
+/// Generates the instructions and related structs to inject in the component.
+fn generate_update(component_type: &Type) -> (TokenStream2, TokenStream2) {
+    (
+        quote! {
+            #[automatically_derived]
+            pub fn update(ctx: Context<Update>, data: Vec<u8>) -> Result<()> {
+                ctx.accounts.bolt_component.set_inner(<#component_type>::try_from_slice(&data)?);
+                Ok(())
+            }
+        },
+        quote! {
+            #[automatically_derived]
+            #[derive(Accounts)]
+            pub struct Update<'info> {
+                #[account(mut)]
+                pub bolt_component: Account<'info, #component_type>,
+            }
+        },
+    )
+}
+
+/// Checks if the field is expecting a program.
+fn is_expecting_program(field: &Field) -> bool {
+    field.ty.to_token_stream().to_string().contains("Program")
+}

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

@@ -10,5 +10,6 @@ proc-macro = true
 
 [dependencies]
 syn = { version = "1.0", features = ["full"] }
+bolt-utils = { path = "../../utils", version = "0.0.1" }
 quote = "1.0"
 proc-macro2 = "1.0"

+ 3 - 0
crates/bolt-lang/attribute/component-deserialize/src/lib.rs

@@ -1,6 +1,7 @@
 use proc_macro::TokenStream;
 use quote::quote;
 use syn::{parse_macro_input, Attribute, DeriveInput};
+use bolt_utils::add_bolt_metadata;
 
 #[proc_macro_attribute]
 pub fn component_deserialize(_attr: TokenStream, item: TokenStream) -> TokenStream {
@@ -10,6 +11,8 @@ pub fn component_deserialize(_attr: TokenStream, item: TokenStream) -> TokenStre
     let additional_derives: Attribute = syn::parse_quote! { #[derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)] };
     input.attrs.push(additional_derives);
 
+    add_bolt_metadata(&mut input);
+
     let name = &input.ident;
     let expanded = quote! {
         #input

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

@@ -10,5 +10,6 @@ proc-macro = true
 
 [dependencies]
 syn = { version = "1.0", features = ["full"] }
+bolt-utils = { path = "../../utils", version = "0.0.1" }
 quote = "1.0"
-proc-macro2 = "1.0"
+proc-macro2 = "1.0"

+ 66 - 130
crates/bolt-lang/attribute/component/src/lib.rs

@@ -1,25 +1,14 @@
 use proc_macro::TokenStream;
-use proc_macro2::TokenStream as TokenStream2;
-use quote::{quote, ToTokens};
-use syn::{
-    parse_macro_input, parse_quote, Attribute, AttributeArgs, Field, Fields, ItemMod, ItemStruct,
-    NestedMeta, Type,
-};
+use quote::quote;
+use syn::{Attribute, DeriveInput, Lit, Meta, NestedMeta, parse_macro_input, parse_quote};
+use bolt_utils::{add_bolt_metadata};
 
-/// This macro attribute is used to define a BOLT component.
+/// This Component attribute is used to automatically generate the seed and size functions
 ///
-/// Bolt components are themselves programs that can be called by other programs.
+/// The component_id can be used to define the seed used to generate the PDA which stores the component data.
+/// The macro also adds the InitSpace and Default derives to the struct.
 ///
-/// # Example
-/// ```ignore
-/// #[component(Position)]
-/// #[program]
-/// pub mod component_position {
-///     use super::*;
-/// }
-///
-/// #[account]
-/// #[bolt_account]
+/// #[component]
 /// pub struct Position {
 ///     pub x: i64,
 ///     pub y: i64,
@@ -27,128 +16,75 @@ use syn::{
 /// }
 /// ```
 #[proc_macro_attribute]
-pub fn component(args: TokenStream, input: TokenStream) -> TokenStream {
-    let ast = parse_macro_input!(input as syn::ItemMod);
-    let args = parse_macro_input!(args as syn::AttributeArgs);
-    let component_type =
-        extract_type_name(&args).expect("Expected a component type in macro arguments");
-    let modified = modify_component_module(ast, &component_type);
-    TokenStream::from(quote! { #modified })
-}
+pub fn component(attr: TokenStream, item: TokenStream) -> TokenStream {
+    let mut input = parse_macro_input!(item as DeriveInput);
+    let mut component_id_value = None;
 
-/// Modifies the component module and adds the necessary functions and structs.
-fn modify_component_module(mut module: ItemMod, component_type: &Type) -> ItemMod {
-    let (initialize_fn, initialize_struct) = generate_initialize(component_type);
-    //let (apply_fn, apply_struct, apply_impl, update_fn, update_struct) = generate_instructions(component_type);
-    let (update_fn, update_struct) = generate_update(component_type);
+    if !attr.is_empty() {
+        let attr_meta = parse_macro_input!(attr as Meta);
 
-    module.content = module.content.map(|(brace, mut items)| {
-        items.extend(
-            vec![initialize_fn, initialize_struct, update_fn, update_struct]
-                .into_iter()
-                .map(|item| syn::parse2(item).unwrap())
-                .collect::<Vec<_>>(),
-        );
-
-        let modified_items = items
-            .into_iter()
-            .map(|item| match item {
-                syn::Item::Struct(mut struct_item) if struct_item.ident == "Apply" => {
-                    modify_apply_struct(&mut struct_item);
-                    syn::Item::Struct(struct_item)
+        component_id_value = match attr_meta {
+            Meta::Path(_) => None,
+            Meta::NameValue(meta_name_value) if meta_name_value.path.is_ident("component_id") => {
+                if let Lit::Str(lit) = meta_name_value.lit {
+                    Some(lit.value())
+                } else {
+                    None
+                }
+            }
+            Meta::List(meta) => meta.nested.into_iter().find_map(|nested_meta| {
+                if let NestedMeta::Meta(Meta::NameValue(meta_name_value)) = nested_meta {
+                    if meta_name_value.path.is_ident("component_id") {
+                        if let Lit::Str(lit) = meta_name_value.lit {
+                            Some(lit.value())
+                        } else {
+                            None
+                        }
+                    } else {
+                        None
+                    }
+                } else {
+                    None
                 }
-                _ => item,
-            })
-            .collect();
-        (brace, modified_items)
-    });
+            }),
+            _ => None,
+        };
+    }
 
-    module
-}
+    let component_id_value = component_id_value.unwrap_or_else(|| "".to_string());
+
+    let additional_macro: Attribute = parse_quote! { #[account] };
+    let additional_derives: Attribute = parse_quote! { #[derive(InitSpace, Default)] };
+    input.attrs.push(additional_derives);
+
+    add_bolt_metadata(&mut input);
+
+    let name = &input.ident;
+    let component_name = syn::Ident::new(&name.to_string().to_lowercase(), input.ident.span());
 
-/// Extracts the type name from attribute arguments.
-fn extract_type_name(args: &AttributeArgs) -> Option<Type> {
-    args.iter().find_map(|arg| {
-        if let NestedMeta::Meta(syn::Meta::Path(path)) = arg {
-            Some(Type::Path(syn::TypePath {
-                qself: None,
-                path: path.clone(),
-            }))
-        } else {
-            None
+    let anchor_program = quote! {
+        #[bolt_program(#name)]
+        pub mod #component_name {
+            use super::*;
         }
-    })
-}
+    };
 
-/// Modifies the Apply struct, change the bolt system to accept any compatible system.
-fn modify_apply_struct(struct_item: &mut ItemStruct) {
-    if let Fields::Named(fields_named) = &mut struct_item.fields {
-        fields_named
-            .named
-            .iter_mut()
-            .filter(|field| is_expecting_program(field))
-            .for_each(|field| {
-                field.ty = syn::parse_str("UncheckedAccount<'info>").expect("Failed to parse type");
-                field.attrs.push(create_check_attribute());
-            });
-    }
-}
+    let expanded = quote! {
+        #anchor_program
 
-/// Creates the check attribute.
-fn create_check_attribute() -> Attribute {
-    parse_quote! {
-        #[doc = "CHECK: This program can modify the data of the component"]
-    }
-}
+        #additional_macro
+        #input
 
-/// Generates the initialize function and struct.
-fn generate_initialize(component_type: &Type) -> (TokenStream2, TokenStream2) {
-    (
-        quote! {
-            #[automatically_derived]
-            pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
-                ctx.accounts.data.set_inner(<#component_type>::default());
-                Ok(())
-            }
-        },
-        quote! {
-            #[automatically_derived]
-            #[derive(Accounts)]
-            pub struct Initialize<'info>  {
-                #[account(mut)]
-                pub payer: Signer<'info>,
-                #[account(init_if_needed, payer = payer, space = <#component_type>::size(), seeds = [<#component_type>::seed(), entity.key().as_ref()], bump)]
-                pub data: Account<'info, #component_type>,
-                #[account()]
-                pub entity: Account<'info, Entity>,
-                pub system_program: Program<'info, System>,
+        #[automatically_derived]
+        impl ComponentTraits for #name {
+            fn seed() -> &'static [u8] {
+                #component_id_value.as_bytes()
             }
-        },
-    )
-}
 
-/// Generates the instructions and related structs to inject in the component.
-fn generate_update(component_type: &Type) -> (TokenStream2, TokenStream2) {
-    (
-        quote! {
-            #[automatically_derived]
-            pub fn update(ctx: Context<Update>, data: Vec<u8>) -> Result<()> {
-                ctx.accounts.bolt_component.set_inner(<#component_type>::try_from_slice(&data)?);
-                Ok(())
-            }
-        },
-        quote! {
-            #[automatically_derived]
-            #[derive(Accounts)]
-            pub struct Update<'info> {
-                #[account(mut)]
-                pub bolt_component: Account<'info, #component_type>,
+            fn size() -> usize {
+                8 + <#name>::INIT_SPACE
             }
-        },
-    )
-}
-
-/// Checks if the field is expecting a program.
-fn is_expecting_program(field: &Field) -> bool {
-    field.ty.to_token_stream().to_string().contains("Program")
+        }
+    };
+    expanded.into()
 }

+ 14 - 2
crates/bolt-lang/attribute/system/src/lib.rs

@@ -15,7 +15,6 @@ struct SystemTransform;
 /// # Example
 /// ```ignore
 /// #[system]
-/// #[program]
 /// pub mod system_fly {
 ///     use super::*;
 ///
@@ -33,9 +32,22 @@ struct SystemTransform;
 pub fn system(attr: TokenStream, item: TokenStream) -> TokenStream {
     let mut input = parse_macro_input!(item as ItemMod);
     let _attr = parse_macro_input!(attr as syn::AttributeArgs);
+
+    let use_super = syn::parse_quote! { use super::*; };
+    if let Some(ref mut content) = input.content {
+        content.1.insert(0, syn::Item::Use(use_super));
+    }
+
     let mut transform = SystemTransform;
     transform.visit_item_mod_mut(&mut input);
-    TokenStream::from(quote! { #input })
+
+    // Add `#[program]` macro
+    let expanded = quote! {
+        #[program]
+        #input
+    };
+
+    TokenStream::from(expanded)
 }
 
 /// Visits the AST and modifies the system function

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

@@ -1,7 +1,7 @@
 pub use anchor_lang::prelude::*;
 
-pub use bolt_attribute_bolt_account::bolt_account;
 pub use bolt_attribute_bolt_component::component;
+pub use bolt_attribute_bolt_program::bolt_program;
 pub use bolt_attribute_bolt_component_deserialize::component_deserialize;
 pub use bolt_attribute_bolt_system::system;
 
@@ -36,3 +36,9 @@ pub trait ComponentDeserialize: Sized {
     /// `Account`.
     fn from_account_info(account: &anchor_lang::prelude::AccountInfo) -> Result<Self>;
 }
+
+/// Metadata for the component.
+#[derive(InitSpace, AnchorSerialize, AnchorDeserialize, Default, Copy, Clone)]
+pub struct BoltMetadata {
+    pub authority: Pubkey
+}

+ 2 - 3
crates/bolt-lang/attribute/account/Cargo.toml → crates/bolt-lang/utils/Cargo.toml

@@ -1,12 +1,11 @@
 [package]
-name = "bolt-attribute-bolt-account"
+name = "bolt-utils"
 version = "0.0.1"
 edition = "2021"
-description = "Bolt attribute-bolt-account"
+description = "Bolt utils"
 license = "MIT"
 
 [lib]
-proc-macro = true
 
 [dependencies]
 syn = { version = "1.0", features = ["full"] }

+ 22 - 0
crates/bolt-lang/utils/src/lib.rs

@@ -0,0 +1,22 @@
+use syn::{DeriveInput, Field, Type, Visibility};
+use proc_macro2::Ident;
+
+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);
+        }
+    }
+}

+ 1 - 0
examples/component-position/Cargo.toml

@@ -14,6 +14,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { version = "0.29.0" }

+ 1 - 8
examples/component-position/src/lib.rs

@@ -2,14 +2,7 @@ use bolt_lang::*;
 
 declare_id!("Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ");
 
-#[component(Position)]
-#[program]
-pub mod component_position {
-    use super::*;
-}
-
-#[account]
-#[bolt_account]
+#[component]
 #[derive(Copy)]
 pub struct Position {
     pub x: i64,

+ 1 - 0
examples/component-velocity/Cargo.toml

@@ -14,6 +14,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = "0.29.0"

+ 2 - 74
examples/component-velocity/src/lib.rs

@@ -2,70 +2,7 @@ use bolt_lang::*;
 
 declare_id!("CbHEFbSQdRN4Wnoby9r16umnJ1zWbULBHg4yqzGQonU1");
 
-#[program]
-pub mod component_velocity {
-    use super::*;
-
-    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
-        Ok(())
-    }
-
-    pub fn apply(ctx: Context<Apply>, args: Vec<u8>) -> Result<()> {
-        let result = bolt_system::cpi::execute(ctx.accounts.set_data_ctx(), args)?;
-        let res = Velocity::try_from_slice(&result.get())?;
-        ctx.accounts.bolt_component.set_inner(res);
-        Ok(())
-    }
-
-    #[derive(Accounts)]
-    pub struct Apply<'info> {
-        #[account(mut)]
-        pub bolt_component: Account<'info, Velocity>,
-        /// CHECK: The system can modify the data of the component
-        pub bolt_system: UncheckedAccount<'info>,
-    }
-
-    impl<'info> Apply<'info> {
-        pub fn set_data_ctx(
-            &self,
-        ) -> CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::SetData<'info>> {
-            let cpi_program = self.bolt_system.to_account_info();
-            let cpi_accounts = bolt_system::cpi::accounts::SetData {
-                component: self.bolt_component.to_account_info().clone(),
-            };
-            CpiContext::new(cpi_program, cpi_accounts)
-        }
-    }
-
-    pub fn update(ctx: Context<Update>, data: Vec<u8>) -> Result<()> {
-        ctx.accounts
-            .bolt_component
-            .set_inner(Velocity::try_from_slice(&data)?);
-        Ok(())
-    }
-
-    #[derive(Accounts)]
-    pub struct Update<'info> {
-        #[account(mut)]
-        pub bolt_component: Account<'info, Velocity>,
-    }
-}
-
-#[derive(Accounts)]
-pub struct Initialize<'info> {
-    #[account(mut)]
-    pub payer: Signer<'info>,
-    #[account(init_if_needed, payer = payer, space = Velocity::size(), seeds = [Velocity::seed(), entity.key().as_ref()], bump)]
-    pub data: Account<'info, Velocity>,
-    #[account()]
-    /// CHECK: A generic entity account
-    pub entity: UncheckedAccount<'info>,
-    pub system_program: Program<'info, System>,
-}
-
-// Component data
-#[account]
-#[derive(InitSpace, Default)]
+#[component(component_id="component-velocity")]
 pub struct Velocity {
     pub x: i64,
     pub y: i64,
@@ -73,13 +10,4 @@ pub struct Velocity {
     pub last_applied: i64,
     #[max_len(20)]
     pub description: String,
-}
-
-impl Velocity {
-    pub fn size() -> usize {
-        8 + Velocity::INIT_SPACE
-    }
-    pub fn seed() -> &'static [u8] {
-        b"component-velocity"
-    }
-}
+}

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

@@ -14,6 +14,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = "0.29.0"

+ 0 - 3
examples/system-apply-velocity/src/lib.rs

@@ -5,10 +5,7 @@ use component_velocity::Velocity;
 declare_id!("6LHhFVwif6N9Po3jHtSmMVtPjF6zRfL3xMosSzcrQAS8");
 
 #[system]
-#[program]
 pub mod system_apply_velocity {
-    use super::*;
-
     pub fn execute(ctx: Context<Component>, _args: Vec<u8>) -> Result<(Velocity, Position)> {
         ctx.accounts.velocity.x = 10;
         let clock = Clock::get()?;

+ 1 - 0
examples/system-fly/Cargo.toml

@@ -14,6 +14,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { version = "0.29.0" }

+ 2 - 8
examples/system-fly/src/lib.rs

@@ -4,16 +4,10 @@ use component_position::Position;
 declare_id!("HT2YawJjkNmqWcLNfPAMvNsLdWwPvvvbKA5bpMw4eUpq");
 
 #[system]
-#[program]
 pub mod system_fly {
-    use super::*;
-
     pub fn execute(ctx: Context<Component>, _args: Vec<u8>) -> Result<Position> {
-        let pos = Position {
-            x: ctx.accounts.position.x,
-            y: ctx.accounts.position.y,
-            z: ctx.accounts.position.z + 1,
-        };
+        let pos = &mut ctx.accounts.position;
+        pos.z += 1;
         Ok(pos)
     }
 }

+ 1 - 0
examples/system-simple-movement/Cargo.toml

@@ -14,6 +14,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = "0.29.0"

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

@@ -3,10 +3,7 @@ use bolt_lang::*;
 declare_id!("FSa6qoJXFBR3a7ThQkTAMrC15p6NkchPEjBdd4n6dXxA");
 
 #[system]
-#[program]
 pub mod system_simple_movement {
-    use super::*;
-
     pub fn execute(ctx: Context<Component>, args_p: Vec<u8>) -> Result<Position> {
         let args = parse_args::<Args>(&args_p);
 

+ 1 - 0
programs/bolt-component/Cargo.toml

@@ -15,6 +15,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { version = "0.29.0", features = ["init-if-needed"] }

+ 25 - 10
programs/bolt-component/src/lib.rs

@@ -6,7 +6,14 @@ declare_id!("CmP2djJgABZ4cRokm4ndxuq6LerqpNHLBsaUv2XKEJua");
 pub mod bolt_component {
     use super::*;
 
-    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
+    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
+        // ctx.accounts.data.to_account_info().assign(::ID); TODO: Test delegate to the world program
+        if let Some(authority) = &ctx.accounts.authority {
+            if authority.key != ctx.accounts.payer.key {
+                panic!("Authority mismatch");
+            }
+            ctx.accounts.data.bolt_metadata.authority = *authority.key;
+        }
         Ok(())
     }
 
@@ -17,7 +24,7 @@ pub mod bolt_component {
     #[derive(Accounts)]
     pub struct Apply<'info> {
         #[account(mut)]
-        pub bolt_component: Account<'info, ComponentData>,
+        pub bolt_component: Account<'info, Component>,
         /// CHECK: The system can modify the data of the component
         pub bolt_system: UncheckedAccount<'info>,
     }
@@ -41,7 +48,7 @@ pub mod bolt_component {
     #[derive(Accounts)]
     pub struct Update<'info> {
         #[account(mut)]
-        pub bolt_component: Account<'info, ComponentData>,
+        pub bolt_component: Account<'info, Component>,
     }
 }
 
@@ -49,25 +56,28 @@ pub mod bolt_component {
 pub struct Initialize<'info> {
     #[account(mut)]
     pub payer: Signer<'info>,
-    #[account(init_if_needed, payer = payer, space = ComponentData::size(), seeds = [ComponentData::seed(), entity.key().as_ref()], bump)]
-    pub data: Account<'info, ComponentData>,
+    #[account(init_if_needed, payer = payer, space = Component::size(), seeds = [Component::seed(), entity.key().as_ref()], bump)]
+    pub data: Account<'info, Component>,
     #[account()]
     /// CHECK: A generic entity account
-    pub entity: UncheckedAccount<'info>,
+    pub entity: AccountInfo<'info>,
+    #[account()]
+    /// CHECK: The authority of the component
+    pub authority: Option<AccountInfo<'info>>,
     pub system_program: Program<'info, System>,
 }
 
 // Component data
 #[account]
 #[derive(InitSpace, Default, Copy)]
-pub struct ComponentData {
-    pub id: u64,
+pub struct Component {
     pub position: Position,
+    pub bolt_metadata: BoltMetadata,
 }
 
-impl ComponentData {
+impl Component {
     pub fn size() -> usize {
-        8 + ComponentData::INIT_SPACE
+        8 + Component::INIT_SPACE
     }
     pub fn seed() -> &'static [u8] {
         b"origin-component"
@@ -80,3 +90,8 @@ pub struct Position {
     pub y: i64,
     pub z: i64,
 }
+
+#[derive(InitSpace, AnchorSerialize, AnchorDeserialize, Default, Copy, Clone)]
+pub struct BoltMetadata {
+    pub authority: Pubkey
+}

+ 5 - 11
programs/world/src/lib.rs

@@ -31,17 +31,8 @@ pub mod world {
 
     #[allow(unused_variables)]
     pub fn add_entity(ctx: Context<AddEntity>, extra_seed: Option<String>) -> Result<()> {
-        msg!(
-            "extra seeds: {:?}",
-            match extra_seed {
-                Some(ref seed) => seed.as_bytes(),
-                None => &[],
-            }
-        );
         ctx.accounts.entity.id = ctx.accounts.world.entities;
         ctx.accounts.world.entities += 1;
-        msg!("entity id: {}", ctx.accounts.entity.id);
-        msg!("world entities: {}", ctx.accounts.world.entities);
         Ok(())
     }
 
@@ -240,11 +231,13 @@ pub struct InitializeComponent<'info> {
     pub payer: Signer<'info>,
     #[account(mut)]
     /// CHECK: component data check
-    pub data: UncheckedAccount<'info>,
+    pub data: AccountInfo<'info>,
     #[account()]
     pub entity: Account<'info, Entity>,
     /// CHECK: component program check
-    pub component_program: UncheckedAccount<'info>,
+    pub component_program: AccountInfo<'info>,
+    /// CHECK: component authority check
+    pub authority: Option<AccountInfo<'info>>,
     pub system_program: Program<'info, System>,
 }
 
@@ -257,6 +250,7 @@ impl<'info> InitializeComponent<'info> {
             payer: self.payer.to_account_info(),
             data: self.data.to_account_info(),
             entity: self.entity.to_account_info(),
+            authority: self.authority.as_ref().map(|a| a.to_account_info()),
             system_program: self.system_program.to_account_info(),
         };
         CpiContext::new(cpi_program, cpi_accounts)

+ 8 - 2
tests/bolt.ts

@@ -180,6 +180,7 @@ describe("bolt", () => {
         data: componentEntity1,
         componentProgram: boltComponentProgramOrigin.programId,
         entity: entity1,
+        authority: provider.wallet.publicKey,
       })
       .rpc();
   });
@@ -197,6 +198,7 @@ describe("bolt", () => {
         data: componentEntity2,
         componentProgram: boltComponentProgramOrigin.programId,
         entity: entity2,
+        authority: provider.wallet.publicKey,
       })
       .rpc();
   });
@@ -216,8 +218,9 @@ describe("bolt", () => {
         data: componentPositionEntity1,
         componentProgram: boltComponentPositionProgram.programId,
         entity: entity1,
+        authority: boltComponentPositionProgram.programId,
       })
-      .rpc();
+      .rpc({ skipPreflight: true });
   });
 
   it("Initialize Velocity Component on Entity 1", async () => {
@@ -234,6 +237,7 @@ describe("bolt", () => {
         data: componentVelocityEntity1,
         componentProgram: boltComponentVelocityProgram.programId,
         entity: entity1,
+        authority: boltComponentVelocityProgram.programId,
       })
       .rpc();
   });
@@ -251,6 +255,7 @@ describe("bolt", () => {
         data: componentPositionEntity2,
         componentProgram: boltComponentPositionProgram.programId,
         entity: entity2,
+        authority: boltComponentPositionProgram.programId,
       })
       .rpc();
   });
@@ -290,7 +295,7 @@ describe("bolt", () => {
         boltSystem: systemSimpleMovement,
         boltComponent: componentPositionEntity1,
       })
-      .rpc({ skipPreflight: true });
+      .rpc();
 
     expect(
       (
@@ -320,6 +325,7 @@ describe("bolt", () => {
     console.log("+----------------+------------+");
     console.log("|                             |");
     console.log("+-----------------------------+");
+    console.log("Component Position: ", componentPositionEntity1.toString());
   });
 
   it("Simple Movement System and Right direction on Entity 1", async () => {