浏览代码

:sparkles: Dynamic types resolver (#24)

* :sparkles: Dynamic types resolver

* :recycle: Code Refactoring

* :recycle: Code Refactor

* :recycle: Use bolt_lang in macro definition
Gabriele Picco 1 年之前
父节点
当前提交
e802f53690

+ 7 - 16
Anchor.toml

@@ -4,17 +4,17 @@
 seeds = true
 seeds = true
 skip-lint = false
 skip-lint = false
 
 
+[programs.devnet]
+world = "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n"
+
 [programs.localnet]
 [programs.localnet]
 bolt-component = "CmP2djJgABZ4cRokm4ndxuq6LerqpNHLBsaUv2XKEJua"
 bolt-component = "CmP2djJgABZ4cRokm4ndxuq6LerqpNHLBsaUv2XKEJua"
 bolt-system = "7X4EFsDJ5aYTcEjKzJ94rD8FRKgQeXC89fkpeTS4KaqP"
 bolt-system = "7X4EFsDJ5aYTcEjKzJ94rD8FRKgQeXC89fkpeTS4KaqP"
 position = "Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ"
 position = "Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ"
-velocity = "CbHEFbSQdRN4Wnoby9r16umnJ1zWbULBHg4yqzGQonU1"
 system-apply-velocity = "6LHhFVwif6N9Po3jHtSmMVtPjF6zRfL3xMosSzcrQAS8"
 system-apply-velocity = "6LHhFVwif6N9Po3jHtSmMVtPjF6zRfL3xMosSzcrQAS8"
 system-fly = "HT2YawJjkNmqWcLNfPAMvNsLdWwPvvvbKA5bpMw4eUpq"
 system-fly = "HT2YawJjkNmqWcLNfPAMvNsLdWwPvvvbKA5bpMw4eUpq"
 system-simple-movement = "FSa6qoJXFBR3a7ThQkTAMrC15p6NkchPEjBdd4n6dXxA"
 system-simple-movement = "FSa6qoJXFBR3a7ThQkTAMrC15p6NkchPEjBdd4n6dXxA"
-world = "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n"
-
-[programs.devnet]
+velocity = "CbHEFbSQdRN4Wnoby9r16umnJ1zWbULBHg4yqzGQonU1"
 world = "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n"
 world = "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n"
 
 
 [registry]
 [registry]
@@ -24,17 +24,8 @@ url = "https://api.apr.dev"
 cluster = "localnet"
 cluster = "localnet"
 wallet = "./tests/fixtures/provider.json"
 wallet = "./tests/fixtures/provider.json"
 
 
+[workspace]
+members = ["programs/bolt-component", "programs/bolt-system", "programs/world", "examples/component-position", "examples/component-velocity", "examples/system-apply-velocity", "examples/system-fly", "examples/system-simple-movement"]
+
 [scripts]
 [scripts]
 test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/bolt.ts"
 test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/bolt.ts"
-
-[workspace]
-members = [
-    "programs/bolt-component",
-    "programs/bolt-system",
-    "programs/world",
-    "examples/component-position",
-    "examples/component-velocity",
-    "examples/system-apply-velocity",
-    "examples/system-fly",
-    "examples/system-simple-movement",
-]

+ 11 - 0
Cargo.lock

@@ -970,9 +970,11 @@ version = "0.1.0"
 dependencies = [
 dependencies = [
  "anchor-cli",
  "anchor-cli",
  "anchor-client",
  "anchor-client",
+ "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git)",
  "anyhow",
  "anyhow",
  "clap 4.4.11",
  "clap 4.4.11",
  "heck 0.4.1",
  "heck 0.4.1",
+ "serde_json",
  "syn 1.0.109",
  "syn 1.0.109",
 ]
 ]
 
 
@@ -1032,6 +1034,14 @@ dependencies = [
  "bolt-helpers-system-template",
  "bolt-helpers-system-template",
 ]
 ]
 
 
+[[package]]
+name = "bolt-types"
+version = "0.1.0"
+dependencies = [
+ "anchor-lang 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bolt-lang",
+]
+
 [[package]]
 [[package]]
 name = "bolt-utils"
 name = "bolt-utils"
 version = "0.1.0"
 version = "0.1.0"
@@ -5068,6 +5078,7 @@ version = "0.1.0"
 dependencies = [
 dependencies = [
  "anchor-lang 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "anchor-lang 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bolt-lang",
  "bolt-lang",
+ "bolt-types",
  "serde",
  "serde",
 ]
 ]
 
 

+ 3 - 1
cli/Cargo.toml

@@ -23,7 +23,9 @@ dev = []
 [dependencies]
 [dependencies]
 anchor-cli = { git = "https://github.com/coral-xyz/anchor.git" }
 anchor-cli = { git = "https://github.com/coral-xyz/anchor.git" }
 anchor-client = { 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" }
 anyhow = { workspace = true }
 anyhow = { workspace = true }
+serde_json = { workspace = true }
 heck = { workspace = true }
 heck = { workspace = true }
 clap = { workspace = true }
 clap = { workspace = true }
-syn = { workspace = true, features = ["full", "extra-traits"] }
+syn = { workspace = true, features = ["full", "extra-traits"] }

+ 192 - 3
cli/src/lib.rs

@@ -1,17 +1,21 @@
 mod rust_template;
 mod rust_template;
 
 
 use crate::rust_template::{create_component, create_system};
 use crate::rust_template::{create_component, create_system};
+use anchor_cli::config;
 use anchor_cli::config::{
 use anchor_cli::config::{
     BootstrapMode, Config, ConfigOverride, GenesisEntry, ProgramArch, ProgramDeployment,
     BootstrapMode, Config, ConfigOverride, GenesisEntry, ProgramArch, ProgramDeployment,
     TestValidator, Validator, WithPath,
     TestValidator, Validator, WithPath,
 };
 };
 use anchor_client::Cluster;
 use anchor_client::Cluster;
+use anchor_syn::idl::types::Idl;
 use anyhow::{anyhow, Result};
 use anyhow::{anyhow, Result};
 use clap::{Parser, Subcommand};
 use clap::{Parser, Subcommand};
 use heck::{ToKebabCase, ToSnakeCase};
 use heck::{ToKebabCase, ToSnakeCase};
 use std::collections::BTreeMap;
 use std::collections::BTreeMap;
-use std::fs::{self, File};
+use std::fs::{self, create_dir_all, File, OpenOptions};
 use std::io::Write;
 use std::io::Write;
+use std::io::{self, BufRead};
+use std::path::{Path, PathBuf};
 use std::process::Stdio;
 use std::process::Stdio;
 
 
 pub const VERSION: &str = env!("CARGO_PKG_VERSION");
 pub const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -49,6 +53,9 @@ pub struct SystemCommand {
 #[derive(Debug, Parser)]
 #[derive(Debug, Parser)]
 #[clap(version = VERSION)]
 #[clap(version = VERSION)]
 pub struct Opts {
 pub struct Opts {
+    /// Rebuild the auto-generated types
+    #[clap(global = true, long, action)]
+    pub rebuild_types: bool,
     #[clap(flatten)]
     #[clap(flatten)]
     pub cfg_override: ConfigOverride,
     pub cfg_override: ConfigOverride,
     #[clap(subcommand)]
     #[clap(subcommand)]
@@ -105,6 +112,7 @@ pub fn entry(opts: Opts) -> Result<()> {
                 cargo_args,
                 cargo_args,
                 no_docs,
                 no_docs,
                 arch,
                 arch,
+                opts.rebuild_types,
             ),
             ),
             _ => {
             _ => {
                 let opts = anchor_cli::Opts {
                 let opts = anchor_cli::Opts {
@@ -415,7 +423,23 @@ pub fn build(
     cargo_args: Vec<String>,
     cargo_args: Vec<String>,
     no_docs: bool,
     no_docs: bool,
     arch: ProgramArch,
     arch: ProgramArch,
+    rebuild_types: bool,
 ) -> Result<()> {
 ) -> Result<()> {
+    let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
+    let types_path = "crates/types/src";
+
+    // If rebuild_types is true and the types directory exists, remove it
+    if rebuild_types && Path::new(types_path).exists() {
+        fs::remove_dir_all(
+            PathBuf::from(types_path)
+                .parent()
+                .ok_or_else(|| anyhow::format_err!("Failed to remove types directory"))?,
+        )?;
+    }
+    create_dir_all(types_path)?;
+    build_dynamic_types(cfg, cfg_override, types_path)?;
+
+    // Build the programs
     anchor_cli::build(
     anchor_cli::build(
         cfg_override,
         cfg_override,
         idl,
         idl,
@@ -472,9 +496,9 @@ fn new_component(cfg_override: &ConfigOverride, name: String) -> Result<()> {
 
 
                 programs.insert(
                 programs.insert(
                     name.clone(),
                     name.clone(),
-                    anchor_cli::config::ProgramDeployment {
+                    ProgramDeployment {
                         address: {
                         address: {
-                            rust_template::create_component(&name)?;
+                            create_component(&name)?;
                             anchor_cli::rust_template::get_or_create_program_id(&name)
                             anchor_cli::rust_template::get_or_create_program_id(&name)
                         },
                         },
                         path: None,
                         path: None,
@@ -581,3 +605,168 @@ fn set_workspace_dir_or_exit() {
         }
         }
     }
     }
 }
 }
+
+fn discover_cluster_url(cfg_override: &ConfigOverride) -> Result<String> {
+    let url = match Config::discover(cfg_override)? {
+        Some(cfg) => cluster_url(&cfg, &cfg.test_validator),
+        None => {
+            if let Some(cluster) = cfg_override.cluster.clone() {
+                cluster.url().to_string()
+            } else {
+                config::get_solana_cfg_url()?
+            }
+        }
+    };
+    Ok(url)
+}
+
+fn cluster_url(cfg: &Config, test_validator: &Option<TestValidator>) -> String {
+    let is_localnet = cfg.provider.cluster == Cluster::Localnet;
+    match is_localnet {
+        // Cluster is Localnet, assume the intent is to use the configuration
+        // for solana-test-validator
+        true => test_validator_rpc_url(test_validator),
+        false => cfg.provider.cluster.url().to_string(),
+    }
+}
+
+// Return the URL that solana-test-validator should be running on given the
+// configuration
+fn test_validator_rpc_url(test_validator: &Option<TestValidator>) -> String {
+    match test_validator {
+        Some(TestValidator {
+            validator: Some(validator),
+            ..
+        }) => format!("http://{}:{}", validator.bind_address, validator.rpc_port),
+        _ => "http://127.0.0.1:8899".to_string(),
+    }
+}
+
+fn build_dynamic_types(
+    cfg: WithPath<Config>,
+    cfg_override: &ConfigOverride,
+    types_path: &str,
+) -> Result<()> {
+    let cur_dir = std::env::current_dir()?;
+    for p in cfg.get_rust_program_list()? {
+        process_program_path(&p, cfg_override, types_path)?;
+    }
+    let types_path = PathBuf::from(types_path);
+    let cargo_path = types_path
+        .parent()
+        .unwrap_or(&types_path)
+        .join("Cargo.toml");
+    if !cargo_path.exists() {
+        let mut file = File::create(cargo_path)?;
+        file.write_all(rust_template::types_cargo_toml().as_bytes())?;
+    }
+    std::env::set_current_dir(cur_dir)?;
+    Ok(())
+}
+
+fn process_program_path(
+    program_path: &Path,
+    cfg_override: &ConfigOverride,
+    types_path: &str,
+) -> Result<()> {
+    let lib_rs_path = Path::new(types_path).join("lib.rs");
+    let file = File::open(program_path.join("src").join("lib.rs"))?;
+    let lines = io::BufReader::new(file).lines();
+    let mut contains_dynamic_components = false;
+    for line in lines.map_while(Result::ok) {
+        if let Some(component_id) = extract_component_id(&line) {
+            let file_path = PathBuf::from(format!("{}/component_{}.rs", types_path, component_id));
+            if !file_path.exists() {
+                println!("Generating type for Component: {}", component_id);
+                generate_component_type_file(&file_path, cfg_override, component_id)?;
+                append_component_to_lib_rs(&lib_rs_path, component_id)?;
+            }
+            contains_dynamic_components = true;
+        }
+    }
+    if contains_dynamic_components {
+        let program_name = program_path.file_name().unwrap().to_str().unwrap();
+        add_types_crate_dependency(program_name, &types_path.replace("/src", ""))?;
+    }
+
+    Ok(())
+}
+
+fn add_types_crate_dependency(program_name: &str, types_path: &str) -> Result<()> {
+    std::process::Command::new("cargo")
+        .arg("add")
+        .arg("--package")
+        .arg(program_name)
+        .arg("--path")
+        .arg(types_path)
+        .stdout(Stdio::null())
+        .stderr(Stdio::null())
+        .spawn()
+        .map_err(|e| {
+            anyhow::format_err!(
+                "error adding types as dependency to the program: {}",
+                e.to_string()
+            )
+        })?;
+    Ok(())
+}
+
+fn extract_component_id(line: &str) -> Option<&str> {
+    let component_id_marker = "#[component_id(";
+    line.find(component_id_marker).map(|start| {
+        let start = start + component_id_marker.len();
+        let end = line[start..].find(')').unwrap() + start;
+        line[start..end].trim_matches('"')
+    })
+}
+
+fn fetch_idl_for_component(component_id: &str, url: &str) -> Result<String> {
+    let output = std::process::Command::new("bolt")
+        .arg("idl")
+        .arg("fetch")
+        .arg(component_id)
+        .arg("--provider.cluster")
+        .arg(url)
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()?;
+
+    if output.status.success() {
+        let idl_string = String::from_utf8(output.stdout)
+            .map_err(|e| anyhow!("Failed to decode IDL output as UTF-8: {}", e))?
+            .to_string();
+        Ok(idl_string)
+    } else {
+        let error_message = String::from_utf8(output.stderr)
+            .unwrap_or(format!(
+                "Error trying to dynamically generate the type \
+            for component {}, unable to fetch the idl. \nEnsure that the idl is available. Specify \
+            the appropriate cluster using the --provider.cluster option",
+                component_id
+            ))
+            .to_string();
+        Err(anyhow!("Command failed with error: {}", error_message))
+    }
+}
+
+fn generate_component_type_file(
+    file_path: &Path,
+    cfg_override: &ConfigOverride,
+    component_id: &str,
+) -> Result<()> {
+    let url = discover_cluster_url(cfg_override)?;
+    let idl_string = fetch_idl_for_component(component_id, &url)?;
+    let idl: Idl = serde_json::from_str(&idl_string)?;
+    let mut file = File::create(file_path)?;
+    file.write_all(rust_template::component_type(&idl, component_id)?.as_bytes())?;
+    Ok(())
+}
+
+fn append_component_to_lib_rs(lib_rs_path: &Path, component_id: &str) -> Result<()> {
+    let mut file = OpenOptions::new()
+        .create(true)
+        .append(true)
+        .open(lib_rs_path)?;
+    file.write_all(rust_template::component_type_import(component_id).as_bytes())?;
+    Ok(())
+}

+ 186 - 1
cli/src/rust_template.rs

@@ -1,6 +1,9 @@
 use crate::ANCHOR_VERSION;
 use crate::ANCHOR_VERSION;
 use crate::VERSION;
 use crate::VERSION;
 use anchor_cli::Files;
 use anchor_cli::Files;
+use anchor_syn::idl::types::{
+    Idl, IdlDefinedTypeArg, IdlField, IdlType, IdlTypeDefinition, IdlTypeDefinitionTy,
+};
 use anyhow::Result;
 use anyhow::Result;
 use heck::{ToSnakeCase, ToUpperCamelCase};
 use heck::{ToSnakeCase, ToUpperCamelCase};
 use std::path::{Path, PathBuf};
 use std::path::{Path, PathBuf};
@@ -404,7 +407,7 @@ fn cargo_toml(name: &str) -> String {
     format!(
     format!(
         r#"[package]
         r#"[package]
 name = "{0}"
 name = "{0}"
-version = "0.0.1"
+version = "{2}"
 description = "Created with Bolt"
 description = "Created with Bolt"
 edition = "2021"
 edition = "2021"
 
 
@@ -480,3 +483,185 @@ pub fn registry_account() -> &'static str {
 }
 }
 "#
 "#
 }
 }
+
+/// Automatic generation of crates from the components idl
+
+pub fn component_type(idl: &Idl, component_id: &str) -> Result<String> {
+    let component_account = &idl.accounts[0];
+    let component_code = component_to_rust_code(component_account, component_id);
+    let types_code = component_types_to_rust_code(&idl.types);
+    Ok(format!(
+        r#"use bolt_lang::*;
+
+#[component_deserialize]
+#[derive(Clone, Copy)]
+{}
+
+{}
+"#,
+        component_code, types_code
+    ))
+}
+
+/// Convert the component type definition to rust code
+fn component_to_rust_code(component: &IdlTypeDefinition, component_id: &str) -> String {
+    let mut code = String::new();
+    // Add documentation comments, if any
+    if let Some(docs) = &component.docs {
+        for doc in docs {
+            code += &format!("/// {}\n", doc);
+        }
+    }
+    // Handle generics
+    let generics = if let Some(gen) = &component.generics {
+        format!("<{}>", gen.join(", "))
+    } else {
+        String::new()
+    };
+    let composite_name = format!("Component{}", component_id);
+    if let IdlTypeDefinitionTy::Struct { fields } = &component.ty {
+        code += &format!("pub struct {}{} {{\n", composite_name, generics);
+        code += &*component_fields_to_rust_code(fields);
+        code += "}\n\n";
+        code += &format!("pub use {} as {};", composite_name, component.name);
+    }
+    code
+}
+
+/// Code to expose the generated type, to be added to lib.rs
+pub fn component_type_import(component_id: &str) -> String {
+    format!(
+        r#"#[allow(non_snake_case)]
+mod component_{0};
+pub use component_{0}::*;
+"#,
+        component_id,
+    )
+}
+
+/// Convert fields to rust code
+fn component_fields_to_rust_code(fields: &[IdlField]) -> String {
+    let mut code = String::new();
+    for field in fields {
+        // Skip BoltMetadata field, as it is added automatically
+        if field.name.to_lowercase() == "boltmetadata" {
+            continue;
+        }
+        if let Some(docs) = &field.docs {
+            for doc in docs {
+                code += &format!("    /// {}\n", doc);
+            }
+        }
+        let field_type = idl_type_to_rust_type(&field.ty);
+        code += &format!("    pub {}: {},\n", field.name, field_type);
+    }
+    code
+}
+
+/// Map Idl type to rust type
+fn idl_type_to_rust_type(idl_type: &IdlType) -> String {
+    match idl_type {
+        IdlType::Bool => "bool".to_string(),
+        IdlType::U8 => "u8".to_string(),
+        IdlType::I8 => "i8".to_string(),
+        IdlType::U16 => "u16".to_string(),
+        IdlType::I16 => "i16".to_string(),
+        IdlType::U32 => "u32".to_string(),
+        IdlType::I32 => "i32".to_string(),
+        IdlType::F32 => "f32".to_string(),
+        IdlType::U64 => "u64".to_string(),
+        IdlType::I64 => "i64".to_string(),
+        IdlType::F64 => "f64".to_string(),
+        IdlType::U128 => "u128".to_string(),
+        IdlType::I128 => "i128".to_string(),
+        IdlType::U256 => "U256".to_string(),
+        IdlType::I256 => "I256".to_string(),
+        IdlType::Bytes => "Vec<u8>".to_string(),
+        IdlType::String => "String".to_string(),
+        IdlType::PublicKey => "PublicKey".to_string(),
+        IdlType::Defined(name) => name.clone(),
+        IdlType::Option(ty) => format!("Option<{}>", idl_type_to_rust_type(ty)),
+        IdlType::Vec(ty) => format!("Vec<{}>", idl_type_to_rust_type(ty)),
+        IdlType::Array(ty, size) => format!("[{}; {}]", idl_type_to_rust_type(ty), size),
+        IdlType::GenericLenArray(ty, _) => format!("Vec<{}>", idl_type_to_rust_type(ty)),
+        IdlType::Generic(name) => name.clone(),
+        IdlType::DefinedWithTypeArgs { name, args } => {
+            let args_str = args
+                .iter()
+                .map(idl_defined_type_arg_to_rust_type)
+                .collect::<Vec<_>>()
+                .join(", ");
+            format!("{}<{}>", name, args_str)
+        }
+    }
+}
+
+/// Map type args
+fn idl_defined_type_arg_to_rust_type(arg: &IdlDefinedTypeArg) -> String {
+    match arg {
+        IdlDefinedTypeArg::Generic(name) => name.clone(),
+        IdlDefinedTypeArg::Value(value) => value.clone(),
+        IdlDefinedTypeArg::Type(ty) => idl_type_to_rust_type(ty),
+    }
+}
+
+/// Convert the component types definition to rust code
+fn component_types_to_rust_code(types: &[IdlTypeDefinition]) -> String {
+    types
+        .iter()
+        // Skip BoltMetadata type, as it is added automatically
+        .filter(|ty| ty.name.to_lowercase() != "boltmetadata")
+        .map(component_type_to_rust_code)
+        .collect::<Vec<_>>()
+        .join("\n")
+}
+
+/// Convert the component type definition to rust code
+fn component_type_to_rust_code(component_type: &IdlTypeDefinition) -> String {
+    let mut code = String::new();
+    // Add documentation comments, if any
+    if let Some(docs) = &component_type.docs {
+        for doc in docs {
+            code += &format!("/// {}\n", doc);
+        }
+    }
+    // Handle generics
+    let generics = if let Some(gen) = &component_type.generics {
+        format!("<{}>", gen.join(", "))
+    } else {
+        String::new()
+    };
+    if let IdlTypeDefinitionTy::Struct { fields } = &component_type.ty {
+        code += &format!(
+            "#[component_deserialize]\n#[derive(Clone, Copy)]\npub struct {}{} {{\n",
+            component_type.name, generics
+        );
+        code += &*component_fields_to_rust_code(fields);
+        code += "}\n\n";
+    }
+    code
+}
+
+pub(crate) fn types_cargo_toml() -> String {
+    let name = "bolt-types";
+    format!(
+        r#"[package]
+name = "{0}"
+version = "{2}"
+description = "Autogenerate types for the bolt language"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "{1}"
+
+[dependencies]
+bolt-lang = "{2}"
+anchor-lang = "{3}"
+"#,
+        name,
+        name.to_snake_case(),
+        VERSION,
+        anchor_cli::VERSION,
+    )
+}

+ 26 - 7
crates/bolt-lang/attribute/component-deserialize/src/lib.rs

@@ -18,32 +18,51 @@ pub fn component_deserialize(_attr: TokenStream, item: TokenStream) -> TokenStre
     let mut input = parse_macro_input!(item as DeriveInput);
     let mut input = parse_macro_input!(item as DeriveInput);
 
 
     // Add the AnchorDeserialize and AnchorSerialize derives to the struct
     // Add the AnchorDeserialize and AnchorSerialize derives to the struct
-    let additional_derives: Attribute = syn::parse_quote! { #[derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)] };
+    let additional_derives: Attribute =
+        syn::parse_quote! { #[derive(bolt_lang::AnchorDeserialize, bolt_lang::AnchorSerialize)] };
     input.attrs.push(additional_derives);
     input.attrs.push(additional_derives);
 
 
     add_bolt_metadata(&mut input);
     add_bolt_metadata(&mut input);
 
 
     let name = &input.ident;
     let name = &input.ident;
+    // Assume that the component_id is the same as the struct name, minus the "Component" prefix
+    let name_str = name.to_string();
+    let component_id = name_str.strip_prefix("Component").unwrap();
     let expanded = quote! {
     let expanded = quote! {
         #input
         #input
 
 
         #[automatically_derived]
         #[automatically_derived]
         impl bolt_lang::ComponentDeserialize for #name{
         impl bolt_lang::ComponentDeserialize for #name{
-            fn from_account_info(account: &anchor_lang::prelude::AccountInfo) -> anchor_lang::Result<#name> {
+            fn from_account_info(account: &bolt_lang::AccountInfo) -> bolt_lang::Result<#name> {
                 #name::try_deserialize_unchecked(&mut &*(*account.data.borrow()).as_ref()).map_err(Into::into)
                 #name::try_deserialize_unchecked(&mut &*(*account.data.borrow()).as_ref()).map_err(Into::into)
             }
             }
         }
         }
 
 
         #[automatically_derived]
         #[automatically_derived]
-        impl anchor_lang::AccountDeserialize for #name {
-            fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
+        impl bolt_lang::AccountDeserialize for #name {
+            fn try_deserialize(buf: &mut &[u8]) -> bolt_lang::Result<Self> {
                 Self::try_deserialize_unchecked(buf)
                 Self::try_deserialize_unchecked(buf)
             }
             }
 
 
-            fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
+            fn try_deserialize_unchecked(buf: &mut &[u8]) -> bolt_lang::Result<Self> {
                 let mut data: &[u8] = &buf[8..];
                 let mut data: &[u8] = &buf[8..];
-                anchor_lang::AnchorDeserialize::deserialize(&mut data)
-                    .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into())
+                bolt_lang::AnchorDeserialize::deserialize(&mut data)
+                    .map_err(|_| bolt_lang::AccountDidNotDeserializeErrorCode.into())
+            }
+        }
+
+        #[automatically_derived]
+        impl bolt_lang::AccountSerialize for #name {
+            fn try_serialize<W>(&self, _writer: &mut W) -> Result<()> {
+                Ok(())
+            }
+        }
+
+        use std::str::FromStr;
+        #[automatically_derived]
+        impl Owner for #name {
+            fn owner() -> Pubkey {
+                Pubkey::from_str(#component_id).unwrap()
             }
             }
         }
         }
     };
     };

+ 34 - 39
crates/bolt-lang/attribute/system-input/src/lib.rs

@@ -1,6 +1,7 @@
 use proc_macro::TokenStream;
 use proc_macro::TokenStream;
+
 use quote::quote;
 use quote::quote;
-use syn::{parse_macro_input, Fields, ItemStruct, Lit, Meta, MetaNameValue};
+use syn::{parse_macro_input, Fields, ItemStruct, Lit, Meta, NestedMeta};
 
 
 /// This macro attribute is used to define a BOLT system input.
 /// This macro attribute is used to define a BOLT system input.
 ///
 ///
@@ -21,50 +22,44 @@ pub fn system_input(_attr: TokenStream, item: TokenStream) -> TokenStream {
     let input = parse_macro_input!(item as ItemStruct);
     let input = parse_macro_input!(item as ItemStruct);
 
 
     // Ensure the struct has named fields
     // Ensure the struct has named fields
-    let fields = if let Fields::Named(fields) = &input.fields {
-        &fields.named
-    } else {
-        panic!("system_input macro only supports structs with named fields");
+    let fields = match &input.fields {
+        Fields::Named(fields) => &fields.named,
+        _ => panic!("system_input macro only supports structs with named fields"),
     };
     };
     let name = &input.ident;
     let name = &input.ident;
 
 
-    // Impls Owner for each account and
-    let owners_impls = fields.iter().filter_map(|field| {
-        field.attrs.iter().find_map(|attr| {
-            if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
-                if meta_list.path.is_ident("component_id") {
-                    for nested_meta in meta_list.nested.iter() {
-                        if let syn::NestedMeta::Meta(Meta::NameValue(MetaNameValue {
-                            path,
-                            lit: Lit::Str(lit_str),
-                            ..
-                        })) = nested_meta
-                        {
-                            if path.is_ident("address") {
-                                let address = lit_str.value();
-                                let field_type = &field.ty;
-                                return Some(quote! {
-                                    use std::str::FromStr;
-                                    impl Owner for #field_type {
-
-                                        fn owner() -> Pubkey {
-                                            Pubkey::from_str(#address).unwrap()
-                                        }
-                                    }
-                                    impl AccountSerialize for #field_type {
-                                        fn try_serialize<W>(&self, _writer: &mut W) -> Result<()> {
-                                            Ok(())
-                                        }
-                                    }
-                                });
+    // Collect imports for components
+    let components_imports: Vec<_> = fields
+        .iter()
+        .filter_map(|field| {
+            field.attrs.iter().find_map(|attr| {
+                if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
+                    if meta_list.path.is_ident("component_id") {
+                        meta_list.nested.first().and_then(|nested_meta| {
+                            if let NestedMeta::Lit(Lit::Str(lit_str)) = nested_meta {
+                                let component_type =
+                                    format!("bolt_types::Component{}", lit_str.value());
+                                if let Ok(parsed_component_type) =
+                                    syn::parse_str::<syn::Type>(&component_type)
+                                {
+                                    let field_type = &field.ty;
+                                    let component_import = quote! {
+                                        use #parsed_component_type as #field_type;
+                                    };
+                                    return Some(component_import);
+                                }
                             }
                             }
-                        }
+                            None
+                        })
+                    } else {
+                        None
                     }
                     }
+                } else {
+                    None
                 }
                 }
-            }
-            None
+            })
         })
         })
-    });
+        .collect();
 
 
     // Transform fields for the struct definition
     // Transform fields for the struct definition
     let transformed_fields = fields.iter().map(|f| {
     let transformed_fields = fields.iter().map(|f| {
@@ -114,7 +109,7 @@ pub fn system_input(_attr: TokenStream, item: TokenStream) -> TokenStream {
     let output = quote! {
     let output = quote! {
         #output_struct
         #output_struct
         #output_impl
         #output_impl
-        #(#owners_impls)*
+        #(#components_imports)*
     };
     };
 
 
     TokenStream::from(output)
     TokenStream::from(output)

+ 4 - 0
crates/bolt-lang/src/lib.rs

@@ -1,4 +1,8 @@
+pub use anchor_lang::error::ErrorCode::AccountDidNotDeserialize as AccountDidNotDeserializeErrorCode;
 pub use anchor_lang::prelude::*;
 pub use anchor_lang::prelude::*;
+pub use anchor_lang::{
+    AccountDeserialize, AccountSerialize, AnchorDeserialize, AnchorSerialize, Result,
+};
 
 
 pub use bolt_attribute_bolt_component::component;
 pub use bolt_attribute_bolt_component::component;
 pub use bolt_attribute_bolt_component_deserialize::component_deserialize;
 pub use bolt_attribute_bolt_component_deserialize::component_deserialize;

+ 13 - 0
crates/types/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+name = "bolt-types"
+version = "0.1.0"
+description = "Autogenerate types for the bolt language"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "bolt_types"
+
+[dependencies]
+bolt-lang = { path = "../bolt-lang" }
+anchor-lang = "0.29.0"

+ 11 - 0
crates/types/src/component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs

@@ -0,0 +1,11 @@
+use bolt_lang::*;
+
+#[component_deserialize]
+#[derive(Clone, Copy)]
+pub struct ComponentFn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ {
+    pub x: i64,
+    pub y: i64,
+    pub z: i64,
+}
+
+pub use ComponentFn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ as Position;

+ 3 - 0
crates/types/src/lib.rs

@@ -0,0 +1,3 @@
+#[allow(non_snake_case)]
+mod component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ;
+pub use component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ::*;

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

@@ -23,4 +23,5 @@ idl-build = ["anchor-lang/idl-build"]
 [dependencies]
 [dependencies]
 anchor-lang = { workspace = true }
 anchor-lang = { workspace = true }
 bolt-lang = { path = "../../crates/bolt-lang" }
 bolt-lang = { path = "../../crates/bolt-lang" }
+bolt-types = { version = "0.1.0", path = "../../crates/types" }
 serde = { version = "*", features = ["derive"] }
 serde = { version = "*", features = ["derive"] }

+ 1 - 9
examples/system-simple-movement/src/lib.rs

@@ -23,18 +23,10 @@ pub mod system_simple_movement {
 
 
     #[system_input]
     #[system_input]
     pub struct Components {
     pub struct Components {
-        #[component_id(address = "Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ")]
+        #[component_id("Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ")]
         pub position: Position,
         pub position: Position,
     }
     }
 
 
-    #[component_deserialize]
-    #[derive(Clone)]
-    pub struct Position {
-        pub x: i64,
-        pub y: i64,
-        pub z: i64,
-    }
-
     // Define the structs to deserialize the arguments
     // Define the structs to deserialize the arguments
     #[derive(BoltSerialize, BoltDeserialize)]
     #[derive(BoltSerialize, BoltDeserialize)]
     struct Args {
     struct Args {