Skip to content

Commit

Permalink
✨ Simplify Components & Systems API
Browse files Browse the repository at this point in the history
  • Loading branch information
GabrielePicco committed Feb 9, 2024
1 parent 48fed65 commit 9f1bfff
Show file tree
Hide file tree
Showing 19 changed files with 399 additions and 289 deletions.
37 changes: 37 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion crates/bolt-helpers/attribute/system-template/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bolt-helpers-system-template"
version = "0.1.0"
version = "0.0.1"
edition = "2021"

[lib]
Expand Down
8 changes: 3 additions & 5 deletions crates/bolt-helpers/attribute/system-template/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ use syn::parse::{Parse, ParseStream, Result};
use syn::{parse_macro_input, Ident, LitInt, Token};

/// This macro attribute is a helper used for defining BOLT systems execute proxy instructions.
///
/// ```
#[proc_macro_attribute]
pub fn system_template(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr_p = parse_macro_input!(attr as SystemTemplateInput);
Expand All @@ -20,7 +18,7 @@ pub fn system_template(attr: TokenStream, item: TokenStream) -> TokenStream {
// Generate a function for execute instruction
let funcs = (2..=max_components).map(|i| {
let func_name = syn::Ident::new(&format!("execute_{}", i), proc_macro2::Span::call_site());
let data_struct = syn::Ident::new("SetData", proc_macro2::Span::call_site());
let data_struct = syn::Ident::new(&format!("SetData{}", i), proc_macro2::Span::call_site());
let return_values = vec![quote!(Vec::<u8>::new()); i];
let return_types = vec![quote!(Vec<u8>); i];
quote! {
Expand Down Expand Up @@ -49,11 +47,11 @@ pub fn system_template(attr: TokenStream, item: TokenStream) -> TokenStream {
quote! {
#[account()]
/// CHECK: unchecked account
pub #field_name: anchor_lang::prelude::UncheckedAccount<'info>,
pub #field_name: UncheckedAccount<'info>,
}
});
let struct_def = quote! {
#[derive(Accounts, BorshDeserialize, BorshSerialize, Clone)]
#[derive(Accounts)]
pub struct #data_struct<'info> {
#(#fields)*
}
Expand Down
12 changes: 12 additions & 0 deletions crates/bolt-helpers/attribute/world-apply/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "bolt-helpers-world-apply"
version = "0.0.1"
edition = "2021"

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = "1.0"
140 changes: 140 additions & 0 deletions crates/bolt-helpers/attribute/world-apply/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream, Result};
use syn::{parse_macro_input, Ident, LitInt, Token};

/// This macro attribute is a helper used for defining BOLT apply proxy instructions.
#[proc_macro_attribute]
pub fn apply_system(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr_p = parse_macro_input!(attr as SystemTemplateInput);

let max_components = attr_p.max_components;

// Parse the original module content
let mut input: syn::ItemMod = syn::parse(item).expect("Failed to parse input module");

// Generate a function for execute instruction
let funcs = (2..=max_components).map(|i| {
let apply_func_name = syn::Ident::new(&format!("apply{}", i), proc_macro2::Span::call_site());
let execute_func_name = syn::Ident::new(&format!("execute_{}", i), proc_macro2::Span::call_site());
let data_struct = syn::Ident::new(&format!("ApplySystem{}", i), proc_macro2::Span::call_site());

let updates = (1..=i).enumerate().map(|(index, n)| {
let component_program_name = syn::Ident::new(&format!("component_program_{}", n), proc_macro2::Span::call_site());
let bolt_component_name = syn::Ident::new(&format!("bolt_component_{}", n), proc_macro2::Span::call_site());

quote! {
let update_result = bolt_component::cpi::update(
build_update_context(
ctx.accounts.#component_program_name.clone(),
ctx.accounts.#bolt_component_name.clone(),
ctx.accounts.instruction_sysvar_account.clone(),
),
res[#index].to_owned()
)?;
}
});

quote! {
pub fn #apply_func_name(ctx: Context<#data_struct>, args: Vec<u8>) -> Result<()> {
let res = bolt_system::cpi::#execute_func_name(ctx.accounts.build(), args)?.get().to_vec();
#(#updates)*
Ok(())
}
}
});


// Append each generated function to the module's items
if let Some((brace, mut content)) = input.content.take() {
for func in funcs {
let parsed_func: syn::Item =
syn::parse2(func).expect("Failed to parse generated function");
content.push(parsed_func);
}

input.content = Some((brace, content));
}

let data_def = (2..=max_components).map(|i| {
let data_struct = syn::Ident::new(&format!("ApplySystem{}", i), proc_macro2::Span::call_site());
let fields = (1..=i).map(|n| {
let component_program_name = syn::Ident::new(&format!("component_program_{}", n), proc_macro2::Span::call_site());
let component_name = syn::Ident::new(&format!("bolt_component_{}", n), proc_macro2::Span::call_site());
quote! {
/// CHECK: bolt component program check
pub #component_program_name: UncheckedAccount<'info>,
#[account(mut)]
/// CHECK: component account
pub #component_name: UncheckedAccount<'info>,
}
});
let struct_def = quote! {
#[derive(Accounts)]
pub struct #data_struct<'info> {
/// CHECK: bolt system program check
pub bolt_system: UncheckedAccount<'info>,
#(#fields)*
/// CHECK: authority check
pub authority: AccountInfo<'info>,
#[account(address = anchor_lang::solana_program::sysvar::instructions::id())]
/// CHECK: instruction sysvar check
pub instruction_sysvar_account: UncheckedAccount<'info>,
}
};
quote! {
#struct_def
}
});

let impl_build_def = (2..=max_components).map(|i| {
let data_struct = syn::Ident::new(&format!("ApplySystem{}", i), proc_macro2::Span::call_site());
let set_data_struct = syn::Ident::new(&format!("SetData{}", i), proc_macro2::Span::call_site());
let fields: Vec<_> = (1..=i).map(|n| {
let component_key = syn::Ident::new(&format!("component{}", n), proc_macro2::Span::call_site());
let component_name = syn::Ident::new(&format!("bolt_component_{}", n), proc_macro2::Span::call_site());
quote! {
#component_key: self.#component_name.to_account_info(),
}
}).collect();
quote! {
impl<'info> #data_struct<'info> {
pub fn build(&self) -> CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::#set_data_struct<'info>> {
let cpi_program = self.bolt_system.to_account_info();
let cpi_accounts = bolt_system::cpi::accounts::#set_data_struct {
#(#fields)*
};
CpiContext::new(cpi_program, cpi_accounts)
}
}
}
});

// Return the modified module
let output = quote! {
#input
#(#data_def)*
#(#impl_build_def)*
};
output.into()
}

// Define a struct to parse macro input
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 max_value = max_components.base10_parse()?;
Ok(SystemTemplateInput {
max_components: max_value,
})
}
}
1 change: 1 addition & 0 deletions crates/bolt-lang/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ anchor-lang = { version = "0.29.0"}
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-system = { path = "./attribute/system", version = "0.0.1" }
bolt-attribute-bolt-system-input = { path = "./attribute/system-input", version = "0.0.1" }
bolt-attribute-bolt-component-deserialize = { path = "./attribute/component-deserialize", version = "0.0.1" }

# Bolt Programs
Expand Down
30 changes: 23 additions & 7 deletions crates/bolt-lang/attribute/bolt-program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,16 @@ fn generate_initialize(component_type: &Type) -> (TokenStream2, TokenStream2) {
(
quote! {
#[automatically_derived]
pub fn initialize(ctx: Context<Initialize>) -> Result<Vec<u8>> {
let mut serialized_data = Vec::new();
<#component_type>::default().serialize(&mut serialized_data).expect("Failed to serialize");
Ok(serialized_data)
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let instruction = anchor_lang::solana_program::sysvar::instructions::get_instruction_relative(
0, &ctx.accounts.instruction_sysvar_account.to_account_info()
).unwrap();
if instruction.program_id != World::id() {
panic!("The instruction must be called from the world program");
}
ctx.accounts.data.set_inner(<#component_type>::default());
ctx.accounts.data.bolt_metadata.authority = *ctx.accounts.authority.key;
Ok(())
}
},
quote! {
Expand All @@ -122,12 +128,14 @@ fn generate_initialize(component_type: &Type) -> (TokenStream2, TokenStream2) {
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(init_if_needed, owner=World::id(), payer = payer, space = <#component_type>::size(), seeds = [<#component_type>::seed(), entity.key().as_ref()], bump)]
pub data: AccountInfo<'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 authority: AccountInfo<'info>,
#[account(address = anchor_lang::solana_program::sysvar::instructions::id())]
pub instruction_sysvar_account: UncheckedAccount<'info>,
pub system_program: Program<'info, System>,
}
},
Expand All @@ -140,6 +148,12 @@ fn generate_update(component_type: &Type) -> (TokenStream2, TokenStream2) {
quote! {
#[automatically_derived]
pub fn update(ctx: Context<Update>, data: Vec<u8>) -> Result<()> {
let instruction = anchor_lang::solana_program::sysvar::instructions::get_instruction_relative(
0, &ctx.accounts.instruction_sysvar_account.to_account_info()
).unwrap();
if instruction.program_id != World::id() {
panic!("The instruction must be called from the world program");
}
ctx.accounts.bolt_component.set_inner(<#component_type>::try_from_slice(&data)?);
Ok(())
}
Expand All @@ -150,6 +164,8 @@ fn generate_update(component_type: &Type) -> (TokenStream2, TokenStream2) {
pub struct Update<'info> {
#[account(mut)]
pub bolt_component: Account<'info, #component_type>,
#[account(address = anchor_lang::solana_program::sysvar::instructions::id())]
pub instruction_sysvar_account: UncheckedAccount<'info>,
}
},
)
Expand Down
14 changes: 14 additions & 0 deletions crates/bolt-lang/attribute/system-input/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "bolt-attribute-bolt-system-input"
version = "0.0.1"
edition = "2021"
description = "Bolt attribute-bolt-system-input"
license = "MIT"

[lib]
proc-macro = true

[dependencies]
syn = { version = "1.0", features = ["full", "visit-mut"] }
quote = "1.0"
proc-macro2 = "1.0"
Loading

0 comments on commit 9f1bfff

Please sign in to comment.