Skip to content

Commit

Permalink
✨ Simplify API & Fix IDL generation
Browse files Browse the repository at this point in the history
  • Loading branch information
GabrielePicco committed Feb 5, 2024
1 parent a9372a9 commit ef647d0
Show file tree
Hide file tree
Showing 29 changed files with 367 additions and 349 deletions.
2 changes: 1 addition & 1 deletion Anchor.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[toolchain]

[features]
seeds = false
seeds = true
skip-lint = false

[programs.localnet]
Expand Down
20 changes: 16 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 3 additions & 12 deletions cli/src/rust_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(),
),
)]
}
Expand All @@ -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
Expand Down Expand Up @@ -436,6 +426,7 @@ no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []
idl-build = ["anchor-lang/idl-build"]
[dependencies]
bolt-lang = "{2}"
Expand Down
2 changes: 1 addition & 1 deletion crates/bolt-lang/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

Expand Down
75 changes: 0 additions & 75 deletions crates/bolt-lang/attribute/account/src/lib.rs

This file was deleted.

14 changes: 14 additions & 0 deletions crates/bolt-lang/attribute/bolt-program/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 changes: 166 additions & 0 deletions crates/bolt-lang/attribute/bolt-program/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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")
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Loading

0 comments on commit ef647d0

Please sign in to comment.